目录
一、核心配置文件 (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_id
WHERE 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_id
WHERE u.id = #{id}
</select>
动态sql
(1) <if> 条件判断
<select id="findUser" resultType="User">
SELECT * FROM user
WHERE 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 user
WHERE
<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 user
WHERE 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 user
WHERE 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 注入
测试覆盖:确保所有条件分支都被测试到

2066

被折叠的 条评论
为什么被折叠?



