MyBatis(前身 iBatis)是優秀的 Java 持久層框架,本質上是對 JDBC 的深度封裝。開發者專注於 SQL 本身,無需處理驅動註冊、連接創建、參數綁定、結果集解析等繁瑣流程代碼。
MyBatis 支援 XML 與註解兩種方式配置 Statement,將執行結果自動映射為 Java 對象,大幅降低數據庫開發複雜度。

<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.13</version></dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency>
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mybatis/mapper/UserDAO.xml"/>
</mappers>
</configuration>
public class User {
private Integer id;
private String name;
private String password;
// 構造器、Getter/Setter、toString 省略
}
<mapper namespace="com.fq.mybatis.UserDAO">
<select id="selectUserById" parameterType="java.lang.Integer" resultType="com.fq.domain.User">
SELECT * FROM user WHERE id = #{id}
</select>
<select id="selectUserByName" parameterType="java.lang.String" resultType="com.fq.domain.User">
SELECT * FROM user WHERE name LIKE '%${value}%'
</select>
<insert id="insertUser" parameterType="com.fq.domain.User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user(name, password) VALUES(#{name}, #{password})
</insert>
<update id="updateUserById" parameterType="com.fq.domain.User">
UPDATE user SET name = #{name}, password = #{password} WHERE id = #{id}
</update>
<delete id="deleteUserById" parameterType="java.lang.Integer">
DELETE FROM user WHERE id = #{id}
</delete>
</mapper>
| 屬性 | 說明 |
|---|---|
| namespace | 命名空間,隔離 SQL,Mapper 接口模式下需與接口全限定名一致 |
| parameterType | 輸入參數類型,MyBatis 通過 OGNL 解析對象屬性 |
| resultType | 輸出映射類型,將查詢結果每行映射為指定類型 |
手寫接口與實現類,代碼中硬編碼 Statement ID,維護成本高:
public class UserDAOImpl implements UserDAO {
private SqlSessionFactory factory;
public UserDAOImpl(SqlSessionFactory factory) { this.factory = factory; }
@Override
public User selectUserById(Integer id) {
SqlSession session = factory.openSession();
try {
return session.selectOne("namespace.selectUserById", id);
} finally {
session.close();
}
}
}
僅定義接口,MyBatis 根據命名規約動態生成代理實現:
public interface UserDAO {
User selectUserById(Integer id);
List<User> selectUserByName(String name);
void insertUser(User user);
void updateUserById(User user);
void deleteUserById(Integer id);
}
映射規約(必須遵守):
1. Mapper XML 的namespace= 接口全限定名
2. Statementid= 接口方法名
3.parameterType/resultType= 方法參數/返回類型
客戶端調用極簡:
SqlSession session = factory.openSession();
UserDAO dao = session.getMapper(UserDAO.class);
User user = dao.selectUserById(1); // 無硬編碼,類型安全
session.close();
| 場景 | Mapper 寫法 | 接口定義 |
|---|---|---|
| 單一簡單類型 | WHERE id = #{id} 或 #{value} | User selectById(Integer id) |
| 多個簡單類型 | UPDATE user SET name=#{1}, pwd=#{2} WHERE id=#{0} | void update(Integer id, String name, String pwd) |
| POJO 對象 | WHERE name=#{name} AND pwd=#{password} | User selectByUser(User user) |
| Map 集合 | WHERE name=#{name} AND pwd=#{password} | User selectByMap(Map<String,Object> map) |
#{ } vs ${ } 核心區別:
#{ }:預編譯佔位符,自動類型轉換,防止 SQL 注入。
${ }:字符串直接拼接,不進行類型轉換,用於動態表名、列名、排序字段等無法使用預編譯的場景(需自行防注入)。
<select id="selectUserCount" parameterType="String" resultType="java.lang.Integer">
SELECT count(*) FROM user WHERE name LIKE '%${value}%'
</select>
要求查詢結果僅一行一列,自動映射首列值。
selectOne,結果必須唯一selectList,返回 List<User> 或 Set<User><select id="selectUserLikeName" resultType="java.util.Map">
SELECT * FROM user WHERE name LIKE '%${value}%'
</select>
返回 List<Map<String,Object>>,鍵為列名,值為列值。
<resultMap id="userMap" type="com.fq.domain.User">
<id column="user_id" property="id"/>
<result column="user_name" property="name"/>
<result column="user_password" property="password"/>
</resultMap>
<select id="selectUserByName" parameterType="String" resultMap="userMap">
SELECT id AS user_id, name AS user_name, password AS user_password FROM user WHERE name = #{name}
</select>
| 標籤 | 用途 |
|---|---|
<id> | 結果集唯一標識(主鍵),優化嵌套查詢性能 |
<result> | 普通列映射 |
column | SQL 查詢列名(或別名) |
property | POJO 屬性名 |
<configuration>
<property name="logRoot" value="/data/logs"/>
<property name="pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{0} - %msg%n"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder><pattern>${pattern}</pattern></encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logRoot}/mybatis-demo.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder><pattern>${pattern}</pattern></encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
</configuration>