MyBatis基于XML配置详细使用指南
目录
一、核心配置文件 (mybatis-config.xml)
二、Mapper XML 文件
1. 基本结构
2. CRUD 操作
通用属性
参数传递
主键处理
结果映射(ResultMap)
动态sql
一、核心配置文件 (mybatis-config.xml)
1. 基本结构
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!-- 配置内容 -->
</configuration>
2. 主要配置项
<configuration><!-- 1. 属性配置 --><properties resource="db.properties"/><!-- 2. 设置 --><settings><setting name="cacheEnabled" value="true"/><setting name="lazyLoadingEnabled" value="true"/><!-- 更多设置 --></settings><!-- 3. 类型别名 --><typeAliases><typeAlias type="com.example.User" alias="User"/><!-- 或包扫描 --><package name="com.example.model"/></typeAliases><!-- 4. 类型处理器 --><typeHandlers><typeHandler handler="com.example.MyTypeHandler"/></typeHandlers><!-- 5. 对象工厂 --><objectFactory type="com.example.MyObjectFactory"/><!-- 6. 插件 --><plugins><plugin interceptor="com.example.MyPlugin"/></plugins><!-- 7. 环境配置 --><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><!-- 8. 数据库厂商标识 --><databaseIdProvider type="DB_VENDOR"><property name="MySQL" value="mysql"/><property name="Oracle" value="oracle"/></databaseIdProvider><!-- 9. 映射器 --><mappers><mapper resource="com/example/UserMapper.xml"/><!-- 或包扫描 --><package name="com.example.mapper"/></mappers>
</configuration>
3. 常用 settings 配置
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局缓存开关 | true/false | true |
lazyLoadingEnabled | 延迟加载开关 | true/false | false |
aggressiveLazyLoading | 积极加载开关 | true/false | false |
mapUnderscoreToCamelCase | 下划线转驼峰 | true/false | false |
logImpl | 日志实现 | SLF4J/LOG4J/LOG4J2/JDK_LOGGING... | 未设置 |
二、Mapper XML 文件
1. 基本结构
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper"><!-- SQL 定义 -->
</mapper>
namespace(命名空间)是 Mapper.xml 文件的根元素 <mapper> 的一个必须属性,用于定义这个映射文件的唯一标识。
主要作用
1. 绑定对应的 Mapper 接口:
- 当使用接口式编程时,namespace 应该设置为对应 Mapper 接口的全限定名(包括包路径)
- 例如:<mapper namespace="com.example.dao.UserMapper">
2. 避免 SQL 语句 ID 冲突:
- 不同 Mapper 文件中可以有相同 ID 的 SQL 语句,通过 namespace 可以区分它们
3. MyBatis 查找映射的依据:
- MyBatis 通过 namespace + SQL 语句 ID 的组合来定位具体的 SQL 语句
示例:
<mapper namespace="com.example.dao.UserMapper"><select id="selectUserById" resultType="User">SELECT * FROM user WHERE id = #{id}</select>
</mapper>
对应的 Java 接口:
package com.example.dao;public interface UserMapper {User selectUserById(int id);
}
注意事项
- 如果不使用接口式编程,namespace 可以是任意唯一字符串,但推荐使用接口全限定名的规范
- namespace 必须与对应的 Mapper 接口完全一致(包括大小写)
- 一个 namespace 对应一个 Mapper 接口或一个 DAO 类
属性名 | 是否必须 | 作用 | 示例 |
---|---|---|---|
namespace | 必须 | 绑定对应的 Mapper 接口全限定名 | namespace="com.example.dao.UserMapper" |
cache | 可选 | 是否启用二级缓存(true /false ),默认 false | cache="true" |
cache-ref | 可选 | 引用其他 Mapper 的缓存配置 | cache-ref="com.example.dao.BaseCache" |
2. CRUD 操作
查询
<!-- 简单查询 -->
<select id="selectUser" parameterType="int" resultType="User">SELECT * FROM user WHERE id = #{id}
</select><!-- 带条件查询 -->
<select id="selectUsers" parameterType="map" resultType="User">SELECT * FROM user WHERE name = #{name} AND age > #{age}
</select><!-- 结果映射 -->
<resultMap id="userResultMap" type="User"><id property="id" column="user_id"/><result property="name" column="user_name"/><!-- 其他属性映射 -->
</resultMap><select id="selectUserWithResultMap" resultMap="userResultMap">SELECT user_id, user_name FROM user WHERE id = #{id}
</select>
插入
<!-- 简单插入 -->
<insert id="insertUser" parameterType="User">INSERT INTO user(name, age) VALUES(#{name}, #{age})
</insert><!-- 获取自增主键 (MySQL) -->
<insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id">INSERT INTO user(name, age) VALUES(#{name}, #{age})
</insert><!-- 获取自增主键 (Oracle) -->
<insert id="insertUser" parameterType="User"><selectKey keyProperty="id" resultType="int" order="BEFORE">SELECT seq_user.nextval FROM dual</selectKey>INSERT INTO user(id, name, age) VALUES(#{id}, #{name}, #{age})
</insert>
更新
<update id="updateUser" parameterType="User">UPDATE user SET name=#{name}, age=#{age} WHERE id=#{id}
</update>
删除
<delete id="deleteUser" parameterType="int">DELETE FROM user WHERE id=#{id}
</delete>
通用属性
属性名 | 作用 | 示例 |
---|---|---|
id | 必须,SQL 语句的唯一标识,对应接口方法名 | id="selectUserById" |
parameterType | 传入参数的类型(可省略,MyBatis 通常能自动推断) | parameterType="int" |
resultType | 返回结果的类型(简单类型或实体类全限定名) | resultType="com.example.User" |
resultMap | 引用自定义的 <resultMap> (与 resultType 二选一) | resultMap="userResultMap" |
flushCache | 是否清空本地/二级缓存(默认 false ) | flushCache="true" |
useCache | 是否使用二级缓存(仅 <select> 有效,默认 true ) | useCache="false" |
timeout | 超时时间(秒),默认无限制 | timeout="10" |
fetchSize | 批量获取的行数(优化性能) | fetchSize="100" |
statementType | SQL 执行方式(STATEMENT /PREPARED /CALLABLE ,默认 PREPARED ) | statementType="CALLABLE" |
- 必须属性:所有 SQL 语句必须指定 id。
- 结果映射:优先用 resultMap(复杂映射),简单场景用 resultType。
- 主键处理:插入时用 useGeneratedKeys + keyProperty 获取自增 ID。
- 缓存控制:通过 flushCache 和 useCache 管理缓存行为。
flushCache(清空缓存)
作用:是否在执行该 SQL 语句后清空 MyBatis 的本地缓存(一级缓存)和二级缓存。
默认值:
<select> 语句:flushCache="false"(查询默认不清空缓存)
<insert>/<update>/<delete> 语句:flushCache="true"(增删改默认清空缓存)
timeout(超时时间)
作用:设置 SQL 语句在数据库中的最大执行时间(秒),超时则抛出异常。
默认值:无超时限制(依赖数据库或驱动配置)。
使用场景:
防止长查询阻塞系统:
<select id="getComplexReport" resultMap="reportMap" timeout="30"><!-- 复杂统计查询 -->
</select>
fetchSize(批量获取行数)
作用:控制 JDBC 驱动每次从数据库返回的行数,优化大数据量查询性能。
默认值:依赖 JDBC 驱动(通常为 10~100)。
使用场景:
查询大量数据时减少内存占用:
<select id="exportAllUsers" resultType="User" fetchSize="1000">SELECT * FROM user
</select>
-
设置
fetchSize=1000
表示 JDBC 每次从数据库获取 1000 行,而不是一次性加载全部结果。 -
类似游标(Cursor)的分批处理,避免 OOM。
statementType(语句执行方式)
作用:指定 MyBatis 如何执行 SQL 语句,可选值:
- STATEMENT:普通语句(直接拼接 SQL,有 SQL 注入风险)。
- PREPARED(默认):预编译语句(使用 ? 占位符,防注入)。
- CALLABLE:调用存储过程。
参数传递
1. parameterType
可省略:
-
使用
@Param
注解时,MyBatis 会通过反射获取参数名和类型,无需手动指定parameterType
。
不省略时:
- parameterType 通常设为 map 或 object(多参数会被 MyBatis 封装为 Map 或 ParamMap)。
示例
使用 @Param 注解
// 接口方法
User selectUserByNameAndAge(@Param("name") String name, @Param("age") int age
);
<!-- Mapper.xml:省略 parameterType -->
<select id="selectUserByNameAndAge" resultType="User">SELECT * FROM user WHERE name = #{name} AND age = #{age}
</select>
不使用 @Param 注解(需注意参数顺序)
// 接口方法(按参数顺序引用:arg0, arg1 或 param1, param2)
User selectUserByNameAndAge(String name, int age);
<!-- Mapper.xml:需按顺序引用参数 -->
<select id="selectUserByNameAndAge" resultType="User">SELECT * FROM user WHERE name = #{param1} AND age = #{param2}
</select>
指定 parameterType
<insert id="insertUser" parameterType="User">INSERT INTO user(name, age) VALUES(#{name}, #{age})
</insert>
如果参数是简单类型(如 int、String),MyBatis 可自动推断。
如果参数是 POJO 或 Map,建议显式指定
场景 | 显式指定的好处 |
---|---|
代码可读性 | 明确告知开发者该 SQL 操作的参数类型,减少理解成本。 |
动态 SQL 安全 | 在 <if> 等标签中,直接使用属性名(如 test="name != null" )更安全。 |
IDE 支持 | 部分 IDE 能基于 parameterType 提供属性名的智能提示(如 #{name} 补全)。 |
2. resultType 或 resultMap
至少需要其中一个来定义返回结果映射(除非返回值是void)
主键处理
<insert id="addUser" useGeneratedKeys="true" keyProperty="id">INSERT INTO users(name) VALUES(#{name})
</insert>
- useGeneratedKeys="true":去把数据库自动生成的主键值拿回来
- keyProperty="id":拿回来后,放到参数的id属性里
keyProperty(必须):只要对象里有这个属性(比如 groupId、dogId),MyBatis 就把数据库生成的主键值塞进去。
keyColumn(大多数情况不用管),只有一种情况需要它:
- 数据库的主键列名 不是 id(比如叫 user_id、pk_id)
- 并且 你的 SQL 里没提这个列名(比如直接 INSERT INTO users (name) VALUES (...),没写 user_id)
这时候才要加。
结果映射(ResultMap)
<resultMap id="userResultMap" type="User"><id property="id" column="id"/><result property="username" column="name"/><result property="userAge" column="age"/><result property="email" column="email"/>
</resultMap><select id="selectUserWithResultMap" resultMap="userResultMap">SELECT * FROM user WHERE id = #{id}
</select>
关联查询(一对一)
<resultMap id="userWithRoleMap" type="User"><id property="id" column="id"/><result property="name" column="name"/><!-- 其他属性 --><association property="role" javaType="com.example.model.Role"><id property="roleId" column="role_id"/><result property="roleName" column="role_name"/></association>
</resultMap><select id="selectUserWithRole" resultMap="userWithRoleMap">SELECT u.*, r.role_id, r.role_name FROM user u LEFT JOIN role r ON u.role_id = r.role_idWHERE u.id = #{id}
</select>
集合查询(一对多)
<resultMap id="userWithOrdersMap" type="User"><id property="id" column="id"/><result property="name" column="name"/><!-- 其他属性 --><collection property="orders" ofType="com.example.model.Order"><id property="orderId" column="order_id"/><result property="orderNo" column="order_no"/><!-- 其他订单属性 --></collection>
</resultMap><select id="selectUserWithOrders" resultMap="userWithOrdersMap">SELECT u.*, o.order_id, o.order_no FROM user u LEFT JOIN orders o ON u.id = o.user_idWHERE u.id = #{id}
</select>
动态sql
(1) <if> 条件判断
<select id="findUser" resultType="User">SELECT * FROM userWHERE 1=1<if test="name != null">AND name = #{name}</if><if test="age != null">AND age = #{age}</if>
</select>
(2) <choose>/<when>/<otherwise> 多选一
<select id="findUser" resultType="User">SELECT * FROM userWHERE<choose><when test="name != null">name = #{name}</when><when test="age != null">age = #{age}</when><otherwise>1=1</otherwise></choose>
</select>
特性 | Java 的 switch-case | MyBatis 的 <choose> |
---|---|---|
匹配逻辑 | 会继续执行后续 case (穿透现象) | 只执行第一个匹配的 <when> |
是否需要 break | 需要手动 break 防止穿透 | 自动"break",无需干预 |
默认分支 | 用 default: | 用 <otherwise> |
(3)<foreach> 遍历集合
<select id="findUsersByIds" resultType="User">SELECT * FROM userWHERE id IN<foreach item="id" collection="ids" open="(" separator="," close=")">#{id}</foreach>
</select>
参数:
- collection:集合参数名(如 List、Array、Map)
- item:当前元素变量名
- index:当前索引(可选)
- open/close:循环开始/结束时的字符串
- separator:元素间的分隔符
批量插入示例
<insert id="batchInsert">INSERT INTO user(name, age) VALUES<foreach item="user" collection="list" separator=",">(#{user.name}, #{user.age})</foreach>
</insert>
(4)<where> 智能 WHERE 子句
<select id="findUser" resultType="User">SELECT * FROM user<where><if test="name != null">AND name = #{name}</if><if test="age != null">AND age = #{age}</if></where>
</select>
作用:自动去除开头的 AND/OR,若无条件则不生成 WHERE 关键字
(5) <set> 智能 SET 子句
<update id="updateUser">UPDATE user<set><if test="name != null">name = #{name},</if><if test="age != null">age = #{age},</if></set>WHERE id = #{id}
</update>
作用:自动去除末尾的逗号
(6)<trim>
<trim> 用于对 SQL 片段进行精确的字符串修剪,可以:
去除多余的前缀/后缀(如 AND、OR、,)
添加必要的前缀/后缀(如 WHERE、SET)
自由组合,处理其他标签无法应对的复杂场景
<trim> 的四个属性
属性名 | 作用 | 示例值 | |
---|---|---|---|
prefix | 最终结果添加前缀 | prefix="WHERE" | |
suffix | 最终结果添加后缀 | suffix=")" | |
prefixOverrides | 去除开头部分的指定字符串(多个用竖线分隔) | `prefixOverrides="AND | OR"` |
suffixOverrides | 去除末尾部分的指定字符串(多个用竖线分隔) | suffixOverrides="," |
用法示例
<insert id="insertUser">INSERT INTO user<trim prefix="(" suffix=")" suffixOverrides=","><if test="name != null">name,</if><if test="age != null">age,</if><if test="email != null">email,</if></trim>VALUES<trim prefix="(" suffix=")" suffixOverrides=","><if test="name != null">#{name},</if><if test="age != null">#{age},</if><if test="email != null">#{email},</if></trim>
</insert>
功能 | <trim> 能否实现 | <where> /<set> 能否实现 |
---|---|---|
去除开头 AND/OR | (prefixOverrides ) | (内置功能) |
去除末尾 , | (suffixOverrides ) | (仅<set> ) |
添加 WHERE 前缀 | (prefix="WHERE" ) | (内置) |
添加 SET 前缀 | (prefix="SET" ) | (内置) |
自定义前缀/后缀 | 可以 | 不行 |
处理非 WHERE/SET 场景 | 可以 (如动态字段列表) | 不行 |
OGNL (Object-Graph Navigation Language) 表达式
在 MyBatis 的动态 SQL 中,test 属性用于条件判断,它使用的是 OGNL (Object-Graph Navigation Language) 表达式。允许你通过简洁的语法访问和操作对象图中的数据。
OGNL 是一种表达式语言,类似于:
- Java 中的 EL(Expression Language)
- JavaScript 中的对象属性访问
- Spring 中的 SpEL
它可以:
- 访问对象的属性/方法(如 user.name)
- 调用静态方法(如 @java.util.Collections@isEmpty(list))
- 进行逻辑/数学运算(如 age > 18)
基本属性判断
<select id="findUser" resultType="User">SELECT * FROM user<where><if test="name != null"> <!-- OGNL 表达式 -->AND name = #{name}</if><if test="age gt 18"> <!-- gt 表示 > -->AND age > #{age}</if></where>
</select>
集合判断:
<if test="list != null and list.size() > 0">AND id IN <foreach item="id" collection="list" open="(" separator="," close=")">#{id}</foreach>
</if>
调用静态方法:
<if test="@com.example.StringUtils@isNotBlank(email)">AND email = #{email}
</if>
常用语法
场景 | OGNL 表达式示例 | 等效 Java 代码 |
---|---|---|
属性访问 | user.name | user.getName() |
集合大小 | list.size() | list.size() |
逻辑运算 | age > 18 and name != null | age > 18 && name != null |
调用静态方法 | @java.util.UUID@randomUUID() | UUID.randomUUID() |
字符串判空 | name != null and name != '' | name != null && !name.isEmpty() |
参数是 Map 或 POJO:
- 如果传入参数是 User 对象,直接写属性名(如 test="name != null")。
- 如果传入参数是 Map,写键名(如 test="mapKey != null")。
多参数无 @Param 时:
<if test="param1 != null"> <!-- 相当于第一个参数 -->AND name = #{param1}
</if>
安全限制:OGNL 默认不允许调用任意类方法
特殊场景下
(1)<bind> 创建变量
<select id="findUser" resultType="User"><bind name="pattern" value="'%' + name + '%'" />SELECT * FROM userWHERE name LIKE #{pattern}
</select>
(2)<sql> + <include> 复用片段
<!-- 定义可重用片段 -->
<sql id="userColumns">id, name, age</sql><!-- 引用片段 -->
<select id="findUser" resultType="User">SELECT <include refid="userColumns"/>FROM user
</select>
避免过度动态化:复杂逻辑可考虑移到 Java 代码中
性能注意:大量 <if> 可能影响 SQL 解析效率
参数校验:动态 SQL 仍需防范 SQL 注入
测试覆盖:确保所有条件分支都被测试到