Article

数据访问与切片 MyBatis

更新于:2026-07-14

第1章:MyBatis 简介与环境搭建

1.1 什么是 MyBatis

概念名称说明注意事项
ORM 框架对象关系映射(Object Relational Mapping),将数据库记录映射为 Java 对象MyBatis 是半自动 ORM,需手动编写 SQL,但结果可自动映射到对象
MyBatis 定义一个支持普通 SQL 查询、存储过程和高级映射的持久层框架不是全自动 ORM(如 Hibernate),更灵活,适合复杂 SQL 和性能要求高的场景
核心思想将 SQL 语句从 Java 代码中分离,通过 XML 或注解配置 SQL 映射关系提高 SQL 可维护性,便于优化和调试
轻量级相比 Hibernate,MyBatis 更轻量,学习成本低,易于集成适用于中小型项目或对 SQL 控制要求高的大型项目

1.2 MyBatis 的核心组件

组件名称说明注意事项
SqlSessionFactoryBuilder用于构建 SqlSessionFactory,通过读取配置文件或 Java 配置创建实例构建完成后即可丢弃,通常为局部变量
SqlSessionFactory创建 SqlSession 的工厂,是线程安全的,应作为单例存在应用中通常只创建一个实例,建议使用静态常量或 Spring 管理
SqlSession执行 SQL 命令的接口,包含增删改查方法,非线程安全每个线程应有独立的 SqlSession 实例,使用后必须关闭
Mapper 接口数据访问接口,方法对应 SQL 操作,由 MyBatis 动态代理实现接口方法名需与 Mapper XML 中的 id 一致,参数和返回值需匹配
Mapper XML 文件定义 SQL 语句及映射规则的 XML 文件,与 Mapper 接口绑定namespace 必须等于接口全限定名,statementId 等于方法名
Configuration包含 MyBatis 所有配置信息的对象,由 mybatis-config.xml 解析生成在 SqlSessionFactory 初始化时创建,贯穿整个应用生命周期

1.3 搭建 MyBatis 开发环境

步骤说明注意事项
引入依赖(Maven)添加 mybatis 和数据库驱动依赖确保版本兼容,例如 MySQL 驱动为 mysql-connector-java
创建数据库表准备测试用的数据表(如 user 表)表结构清晰,字段命名符合规范
编写实体类创建与数据库表对应的 Java Bean 类属性名与字段名一致或通过 resultMap 映射
创建 Mapper 接口定义数据操作方法(如 selectUserById)方法签名需与 SQL 映射匹配
编写 Mapper XML在 resources 目录下创建 XML 文件,定义 SQL 语句文件路径与接口包结构一致,namespace 正确
配置 mybatis-config.xml配置 environments、mappers 等信息environment 指定事务管理器和数据源,mappers 注册映射文件
测试运行使用 SqlSessionFactory 构建 SqlSession 并调用 Mapper 方法记得 commit() 写操作,finally 中 close() SqlSession

1.4 第一个 MyBatis 程序(Hello World)

方法/步骤语法用途代码示例注意事项
Resources.getResourceAsStreamResources.getResourceAsStream("mybatis-config.xml")加载 MyBatis 配置文件流InputStream in = Resources.getResourceAsStream("mybatis-config.xml");配置文件需在 classpath 下(如 resources 目录)
SqlSessionFactoryBuilder.buildnew SqlSessionFactoryBuilder().build(inputStream)从配置流构建 SqlSessionFactorySqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);Builder 为临时对象,构建后可回收
SqlSessionFactory.openSessionfactory.openSession()获取 SqlSession 实例SqlSession session = factory.openSession();查询可不传参,增删改建议使用 openSession(true) 自动提交
SqlSession.getMappersession.getMapper(UserMapper.class)获取 Mapper 接口的代理实现UserMapper mapper = session.getMapper(UserMapper.class);接口必须已注册到 configuration 中
Mapper 接口方法调用mapper.selectUserById(1)执行 SQL 查询User user = mapper.selectUserById(1);方法名、参数类型、返回类型必须与 XML 中定义一致
SqlSession.closesession.close()关闭 SqlSession,释放资源try { ... } finally { session.close(); }必须关闭,否则可能导致连接泄露

第2章:MyBatis 核心配置文件详解

2.1 mybatis-config.xml 文件结构

元素名称说明注意事项
<configuration>根元素,包含所有其他配置节点所有子元素必须按固定顺序排列
<properties>外部属性配置,用于引入外部 .properties 文件或定义键值对可用于替换配置中的 ${key} 占位符,提高可维护性
<settings>全局行为配置,控制 MyBatis 运行时行为如开启缓存、延迟加载、日志等,影响全局
<typeAliases>类型别名,为 Java 类设置简短名字减少 XML 中全限定类名的重复书写,如为 com.example.User 设 alias=“User”
<typeHandlers>类型处理器,处理 Java 类型与 JDBC 类型之间的转换可自定义处理特殊类型(如枚举、JSON)
<objectFactory>对象工厂,创建结果对象的实例一般使用默认即可,可自定义对象创建逻辑
<plugins>插件配置,用于拦截 Executor、StatementHandler 等核心对象实现拦截器扩展功能,如分页、日志等
<environments>环境配置,可配置多个 environment,指定事务管理和数据源每个 environment 有唯一 id,通过 default 指定默认环境
<databaseIdProvider>数据库厂商标识提供者,根据数据库类型执行不同 SQL支持多数据库兼容,如 Oracle、MySQL 使用不同方言
<mappers>注册 Mapper 映射文件或接口必须注册才能被 MyBatis 扫描到,支持 class、resource、url、package 方式

⚠️ 注意<configuration> 子元素必须按上述顺序声明,否则解析报错。

2.2 核心配置项

配置项名称语法格式用途示例值注意事项
environment id<environment id="development">定义环境唯一标识development, test, production必须与 <environments default="..."> 中的值匹配
transactionManager type<transactionManager type="JDBC"/>指定事务管理器类型JDBC, MANAGEDJDBC 使用 Connection.commit()/rollback(),MANAGED 交由容器管理
dataSource type<dataSource type="POOLED">指定数据源类型UNPOOLED, POOLED, JNDIPOOLED 使用连接池,UNPOOLED 每次新建连接,JNDI 用于 Web 容器环境
mappers resource<mapper resource="UserMapper.xml"/>注册基于 classpath 的 Mapper XML 文件路径以 / 分隔,如 com/example/UserMapper.xml文件必须存在于 classpath,且 namespace 正确
mappers class<mapper class="com.example.UserMapper"/>注册 Mapper 接口接口全限定名接口需与 XML 同名同包,或使用注解方式
mappers package<package name="com.example.mapper"/>批量注册指定包下的所有 Mapper 接口包名推荐方式,减少重复配置

2.3 数据源(DataSource)配置

数据源类型说明配置示例注意事项
UNPOOLED每次请求都打开新连接,使用后立即关闭<dataSource type="UNPOOLED">性能差,仅用于测试或简单场景
POOLED使用连接池管理数据库连接,复用连接提升性能<dataSource type="POOLED">推荐生产环境使用,内置简单连接池,默认最大连接数 10
JNDI从 Web 容器(如 Tomcat)的 JNDI 查找数据源<dataSource type="JNDI">适用于 Web 应用部署在应用服务器中
属性名说明常用值注意事项
driverJDBC 驱动类名com.mysql.cj.jdbc.Driver根据数据库选择正确驱动
url数据库连接 URLjdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC注意字符集、时区、SSL 等参数
username数据库用户名root建议使用专用账号
password数据库密码123456生产环境应加密存储
poolMaximumActiveConnections最大活跃连接数10默认 10,可根据负载调整
poolMaximumIdleConnections最大空闲连接数5避免连接浪费

2.4 事务管理器(TransactionManager)配置

事务管理器类型说明配置方式注意事项
JDBC使用底层 JDBC 的事务机制,通过 Connection 的 commit/rollback 控制<transactionManager type="JDBC"/>适用于简单应用或与 Spring 未集成时
MANAGED将事务管理委托给容器(如 JBoss、Tomcat),MyBatis 不主动提交或回滚<transactionManager type="MANAGED"/>通常与 Spring 集成时使用,Spring 会覆盖此设置
属性名说明示例值注意事项
closeConnection是否由 MyBatis 关闭连接(仅 MANAGED 有效)false若容器管理连接,设为 false,避免容器报错

2.5 设置(settings)详解

设置项名称说明可选值默认值注意事项
cacheEnabled是否启用全局二级缓存true / falsetrue关闭后所有 Mapper 二级缓存失效
lazyLoadingEnabled是否启用延迟加载true / falsefalse需配合 aggressiveLazyLoading 使用
aggressiveLazyLoading是否积极加载,true 则访问任一属性触发加载true / falsetrue设为 false 可避免无谓加载
multipleResultSetsEnabled是否允许单个语句返回多个结果集true / falsetrue通常无需修改
useColumnLabel是否优先使用列标签(alias)而非列名true / falsetrue推荐开启,SQL 中使用 AS 定义别名
useGeneratedKeys是否使用 JDBC getGeneratedKeys() 获取自增主键true / falsefalse插入记录后获取主键值需开启
autoMappingBehavior自动映射级别(NONE, PARTIAL, FULL)NONE, PARTIAL, FULLPARTIALPARTIAL 不映射嵌套结果,FULL 映射全部
defaultExecutorType默认执行器类型SIMPLE, REUSE, BATCHSIMPLEBATCH 用于批量操作,提升性能
defaultStatementTimeoutSQL 执行超时时间(秒)整数值null防止慢查询阻塞
mapUnderscoreToCamelCase是否开启下划线转驼峰命名true / falsefalse数据库字段 user_name → Java 属性 userName
logImpl指定日志实现类SLF4J, LOG4J, LOG4J2, JDK_LOGGING 等未设置推荐使用 SLF4J + Logback

提示:这些设置可通过 Java 代码动态配置,但通常在 XML 中统一管理。

第3章:Mapper 映射文件与 SQL 执行

3.1 Mapper 接口与 XML 映射文件

概念名称说明注意事项
Mapper 接口定义数据访问方法的 Java 接口,由 MyBatis 动态代理实现方法名必须与 XML 中的 id 一致,参数和返回类型需匹配
XML 映射文件包含 SQL 语句和映射规则的 XML 文件,与 Mapper 接口绑定文件路径应与接口包结构一致(如 com/example/UserMapper.xml)
namespaceXML 文件根元素 <mapper> 的 namespace 属性必须等于对应 Mapper 接口的全限定类名(如 com.example.mapper.UserMapper)
statementId<select>, <insert> 等元素的 id 属性必须与接口中方法名完全一致
绑定机制MyBatis 通过 namespace + id 定位 SQL 语句若未正确绑定,调用接口方法时会抛出 BindingException
注解 vs XML可使用注解(如 @Select)替代 XML,但复杂 SQL 建议用 XML混合使用时,XML 优先级高于注解

3.2 select 元素详解

属性名称语法格式用途示例值注意事项
idid="selectUserById"唯一标识该 SQL 语句,对应接口方法名selectUserById必须唯一,且与接口方法名一致
parameterTypeparameterType="int"指定输入参数类型(可选,MyBatis 可自动推断)int, com.example.User, map基本类型可写别名(如 int, string),复杂类型写全限定名或别名
resultTyperesultType="com.example.User"指定返回值类型,MyBatis 自动映射列到属性User, int, java.util.Map若字段名非驼峰,建议开启 mapUnderscoreToCamelCase
resultMapresultMap="userResultMap"引用外部 <resultMap> 定义,用于复杂映射userResultMap优先级高于 resultType,适用于关联查询、嵌套对象等
timeouttimeout="10"设置 SQL 执行超时时间(秒)10超时后抛出异常,防止长时间阻塞
fetchSizefetchSize="100"提示 JDBC 驱动每次从数据库获取的行数100用于优化大结果集查询性能
statementTypestatementType="PREPARED"指定使用 Statement 类型STATEMENT, PREPARED, CALLABLE推荐 PREPARED(预编译),防止 SQL 注入

3.3 insert 元素详解

属性名称语法格式用途示例值注意事项
idid="insertUser"唯一标识插入语句insertUser必须与接口方法名一致
parameterTypeparameterType="User"指定传入参数类型User, map支持对象、Map、基本类型
keyPropertykeyProperty="id"指定将自增主键值回填到对象的哪个属性id用于获取数据库生成的主键(如 MySQL AUTO_INCREMENT)
keyColumnkeyColumn="user_id"指定数据库主键列名(多列主键时使用)user_id单列主键可省略
useGeneratedKeysuseGeneratedKeys="true"是否使用 JDBC getGeneratedKeys() 获取主键true / false需数据库支持,且配合 keyProperty 使用
flushCacheflushCache="true"是否清空本地缓存(一级缓存)true / false插入操作默认为 true

3.4 update 元素详解

属性名称语法格式用途示例值注意事项
idid="updateUser"唯一标识更新语句updateUser必须与接口方法名一致
parameterTypeparameterType="User"指定传入参数类型User, map支持对象、Map、基本类型
timeouttimeout="5"设置执行超时时间(秒)5防止长时间阻塞
flushCacheflushCache="true"是否清空本地缓存true / false更新操作默认为 true
statementTypestatementType="PREPARED"使用预编译语句PREPARED推荐使用,防止 SQL 注入

3.5 delete 元素详解

属性名称语法格式用途示例值注意事项
idid="deleteUserById"唯一标识删除语句deleteUserById必须与接口方法名一致
parameterTypeparameterType="int"指定传入参数类型int, string支持基本类型、对象、Map
flushCacheflushCache="true"是否清空本地缓存true / false删除操作默认为 true
timeouttimeout="5"设置执行超时时间5防止慢查询阻塞

3.6 参数传递(parameterType)

参数类型语法格式用途代码示例注意事项
单个基本类型#{id}, ${value}传递单个参数(int, String 等)select * from user where id = #{id}可使用任意名称(如 #{id}),MyBatis 自动识别
单个 POJO 对象#{property}传递 Java Bean 对象,通过属性访问insert into user(name, age) values(#{name}, #{age})属性名对应对象字段
Map 类型#{key}传递 Map,key 为参数名Map<String, Object> param = new HashMap<>(); param.put("name", "Tom");灵活,适合动态参数
多个参数#{param1}, #{param2}@Param 注解传递多个参数select * from user where name=#{name} and age=#{age}建议使用 @Param(“name”) 指定名称,避免 param1、param2 不直观
Collection / List#{collection}, #{list}, #{array}用于 <foreach> 遍历foreach collection="list" item="item"MyBatis 自动封装为 Map,key 为 collection/list/array

3.7 结果映射(resultType 与 resultMap)

映射方式说明代码示例注意事项
resultType简单类型自动映射,MyBatis 根据列名自动匹配属性<select id="..." resultType="User">要求列名与属性名一致或开启 mapUnderscoreToCamelCase
resultMap自定义结果映射,支持复杂关系(一对一、一对多)<resultMap id="userMap" type="User">...</resultMap>适用于嵌套对象、字段名不一致、关联查询等场景
<id> 子元素映射主键列,提升性能<id property="id" column="user_id"/>建议显式定义主键映射
<result> 子元素映射普通列<result property="name" column="user_name"/>用于字段名与属性名不一致的情况
autoMapping控制单个 resultMap 是否启用自动映射<resultMap ... autoMapping="true">可与手动映射共存

第4章:动态 SQL

4.1 if 标签

属性名称语法格式用途代码示例注意事项
testtest="condition"判断条件是否成立,成立则包含其内容<if test="name != null">AND name = #{name}</if>常用于 WHERE 条件拼接,注意避免多余的 AND/OR

4.2 choose when otherwise 标签

标签名称语法格式用途代码示例注意事项
<choose><choose>...</choose>类似 Java 中的 switch-case,选择一个分支执行<choose><when test="...">...</when><otherwise>...</otherwise></choose>只执行第一个满足条件的 <when>
<when><when test="condition">SQL</when>判断条件,成立则执行<when test="type == 1">AND status = 'ACTIVE'</when>test 属性为 OGNL 表达式
<otherwise><otherwise>SQL</otherwise>默认分支,当所有 <when> 不满足时执行<otherwise>AND status = 'INACTIVE'</otherwise>相当于 default

4.3 where 标签

说明代码示例注意事项
自动处理 WHERE 关键字,并移除开头的 AND 或 OR<where><if test="name != null">AND name = #{name}</if></where>避免手动判断条件是否存在,防止 SQL 语法错误

4.4 set 标签

说明代码示例注意事项
用于 UPDATE 语句,自动添加 SET 关键字,并移除末尾多余的逗号<set><if test="name != null">name = #{name},</if></set>解决动态更新字段时的逗号问题

4.5 trim 标签

属性名称语法格式用途代码示例注意事项
prefixprefix="WHERE"添加前缀<trim prefix="WHERE" prefixOverrides="AND">常用于自定义 WHERE 或 SET 逻辑
prefixOverridesprefixOverrides="AND"移除指定的前缀内容移除开头的 AND 或 OR多个用竖线分隔(如 `AND
suffixsuffix=";"添加后缀suffix=")"如添加括号或分号
suffixOverridessuffixOverrides=","移除指定的后缀内容移除末尾的逗号常用于 <set> 替代

4.6 foreach 集合标签

属性名称语法格式用途代码示例注意事项
collectioncollection="list"指定要遍历的集合(List, Set, Array, Map)collection="ids"若参数为单个 List,直接用 “list”;多个参数需用 @Param
itemitem="item"当前元素的变量名#{item}在循环体内使用
openopen="("循环开始前添加的内容open="("常用于 IN 查询
closeclose=")"循环结束后添加的内容close=")"匹配 open
separatorseparator=","元素之间的分隔符separator=","避免末尾多出分隔符

4.7 bind 属性

属性名称语法格式用途代码示例注意事项
namename="usernameLike"定义变量名name="pattern"在当前 SQL 片段中可用
valuevalue="'%' + username + '%'"OGNL 表达式计算值value="'%" + _parameter + "%'"常用于模糊查询,避免在 Java 层拼接字符串

示例:<bind name="likeName" value="'%' + name + '%'"/> AND name LIKE #{likeName}

第5章:结果映射与关联查询

5.1 resultMap 基础配置

元素/属性语法格式用途示例值注意事项
<resultMap><resultMap id="..." type="...">...</resultMap>定义一个结果映射规则id="userResultMap" type="User"type 可为别名或全限定类名
<id><id property="id" column="user_id"/>映射主键字段,提升性能和唯一性识别property 对应 Java 属性,column 对应数据库列建议显式定义,尤其在关联查询中
<result><result property="name" column="user_name"/>映射普通字段支持基本类型、String 等用于字段名与属性名不一致的情况
extendsextends="baseResultMap"继承另一个 resultMap,实现复用baseResultMap避免重复定义公共字段
autoMappingautoMapping="true"控制该 resultMap 是否启用自动映射(可覆盖全局设置)true / false优先级高于全局 autoMapping 设置

使用场景:字段名下划线 vs 属性名驼峰、主键非 id、需要继承复用等。

5.2 association 关联映射(一对一)

属性/元素说明代码示例注意事项
propertyJava 对象中的关联属性名property="dept"必须与 POJO 中的字段名一致
javaType关联对象的 Java 类型javaType="Department"可省略(MyBatis 自动推断),复杂时建议指定
resultType / resultMap指定返回类型或引用外部映射resultMap="deptResultMap"推荐使用 resultMap 实现复杂嵌套
嵌套查询(select)通过另一个 SQL 查询加载关联对象<association property="dept" column="dept_id" select="findDeptById"/>需配合 column 传递参数,易产生 N+1 查询问题
嵌套结果(内联)使用 JOIN 一次性查出所有数据<association property="dept" javaType="Dept"><id .../><result .../></association>推荐方式,避免多次数据库访问

⚠️ N+1 问题:若主查询返回 N 条记录,每条都触发一次关联查询,则共执行 N+1 次 SQL。

5.3 collection 集合映射(一对多)

属性/元素说明代码示例注意事项
property集合属性名(如 List<User> usersproperty="users"必须是集合类型(List, Set, Array)
ofType / javaType集合中元素的类型(MyBatis 3.5+ 推荐 ofType)ofType="User"替代旧版的 javaType=“ArrayList”
resultType / resultMap映射集合元素resultMap="userResultMap"复杂对象建议使用 resultMap
嵌套查询(select)通过独立 SQL 查询加载集合<collection property="orders" column="user_id" select="findOrders"/>存在 N+1 问题,可通过 fetchType=“lazy” 延迟加载优化
嵌套结果(JOIN)使用 LEFT JOIN 一次性查出主从数据内联 <id>, <result> 定义映射规则推荐用于一对多,注意避免笛卡尔积

最佳实践:优先使用 JOIN + 嵌套结果,性能更优。

5.4 嵌套查询与延迟加载

概念说明配置方式注意事项
嵌套查询(Nested Select)利用 select 属性调用另一个 mapped statement 加载关联数据<association><collection> 中使用 select="xxx"简单但可能导致 N+1 查询
延迟加载(Lazy Loading)关联对象在真正被访问时才加载,提升初始查询性能全局配置:<setting name="lazyLoadingEnabled" value="true"/>必须同时开启 aggressiveLazyLoading=false(默认)
fetchType控制单个关联是否立即加载或延迟加载fetchType="lazy""eager"可覆盖全局设置,灵活控制
代理机制MyBatis 使用 CGLIB 或 JAVASSIST 创建代理对象无需手动干预,自动实现注意:需将关联对象设为 protected,避免序列化问题

📌 配置示例

<settings>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="aggressiveLazyLoading" value="false"/>
</settings>

5.5 自动映射(autoMapping)

级别配置方式行为说明注意事项
全局级别<setting name="autoMappingBehavior" value="PARTIAL"/>控制整个 SqlSessionFactory 的自动映射行为可选值:NONE, PARTIAL, FULL
resultMap 级别<resultMap id="..." type="..." autoMapping="true">覆盖全局设置,仅对该 resultMap 生效更加灵活,推荐使用
映射规则数据库列名 → Java 属性名默认按”下划线转驼峰”匹配(如 user_name → userName)需开启 mapUnderscoreToCamelCase=true
级别PARTIALFULL
PARTIAL忽略带 <association> / <collection> 的自动映射推荐 PARTIAL,避免复杂映射出错
FULL全部自动映射可能导致意外映射

适用场景:简单 POJO、字段命名规范、快速开发原型。

第6章:缓存机制

6.1 一级缓存(SqlSession 级别)

特性说明生命周期注意事项
作用范围同一个 SqlSession 内从创建到关闭不同 SqlSession 之间不共享
缓存机制基于 PerpetualCache 的 HashMap 实现缓存 key:MappedStatement ID + 查询参数执行 commit()、close() 或 DML 操作会清空缓存
自动启用默认开启,无需配置查询语句自动缓存结果同一 SqlSession 中相同查询只查一次数据库
清空时机insert/update/delete 操作后、手动 clearCache()、SqlSession 关闭保证数据一致性若跨 SqlSession 更新数据,可能读到旧值

验证方式:同一 SqlSession 中执行两次相同 select,第二次无 SQL 输出。

6.2 二级缓存(Mapper 级别)

特性说明配置方式注意事项
作用范围同一个 namespace(即同一个 Mapper 接口)所有 SqlSession 共享需手动启用
启用方式在 Mapper XML 中添加 <cache/>或使用注解 @CacheNamespace必须实现 Serializable 接口
缓存策略默认 LRU(Least Recently Used)可配置 eviction=“LRU/FIFO/SOFT/WEAK”结合 size、flushInterval 等属性控制
flushInterval缓存刷新间隔(毫秒)flushInterval="60000"定时清空,防止内存溢出
readOnly是否只读(影响序列化)readOnly=“true” → 返回相同实例;false → 可序列化副本false 更安全,避免并发修改

🔐 安全性:涉及敏感数据的 Mapper 应禁用二级缓存。

6.3 缓存配置与刷新策略

配置项语法格式用途示例值
sizesize="1024"最多缓存对象数1024
flushIntervalflushInterval="60000"每隔多少毫秒清空一次缓存60000(1分钟)
evictioneviction="LRU"回收策略:LRU(最近最少)、FIFO(先进先出)、SOFT(软引用)、WEAK(弱引用)LRU
blockingblocking="true"若缓存未命中,是否阻塞其他线程等待加载true / false
刷新策略触发方式说明
DML 操作执行 insert/update/delete 后自动清空该 namespace 缓存保证数据一致性
手动清空sqlSession.getMapper(UserMapper.class).clearCache()强制刷新缓存
定时刷新配置 flushInterval适用于变化较少的数据

6.4 自定义缓存(Cache 接口)

步骤说明示例
实现 Cache 接口自定义缓存逻辑(如 Redis、Ehcache)public class RedisCache implements Cache { ... }
使用装饰器模式可包装现有缓存增强功能(如同步、日志)继承 Decorator 类
配置方式<cache> 中指定 type<cache type="com.example.cache.RedisCache"/>
序列化支持缓存对象必须实现 Serializable否则抛出异常
第三方集成支持与 Redis、Memcached 等集成需引入对应客户端依赖

💡 典型场景:分布式系统中使用 Redis 作为二级缓存,提升并发性能。

第7章:插件与拦截器

7.1 MyBatis 插件机制原理

核心概念说明
责任链模式MyBatis 将核心组件(Executor, StatementHandler, ParameterHandler, ResultSetHandler)包装在代理中,形成拦截链。
代理机制基于 JDK 动态代理或 CGLIB 实现,在目标方法执行前后插入自定义逻辑。
签名(@Signature)定义插件要拦截的类、方法和参数类型,是插件注册的关键元数据。
插件注册通过 <plugins> 配置或 SqlSessionFactoryBuilder 注册,MyBatis 自动织入拦截逻辑。

拦截点生命周期

Executor → StatementHandler → ParameterHandler → ResultSetHandler

7.2 实现 Interceptor 接口

方法用途参数说明
Object intercept(Invocation invocation)核心逻辑:在此实现拦截处理invocation: 包含被调用对象、方法、参数
Object plugin(Object target)返回被代理的对象(通常用 Plugin.wrap(target, this))target: 被拦截的接口实例
void setProperties(Properties properties)接收插件配置属性可从 XML 中传入参数,如分页大小

📌 代码模板

@Intercepts({@Signature(
    type = Executor.class,
    method = "query",
    args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)})
public class ExampleInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 执行前逻辑
        Object result = invocation.proceed(); // 放行执行
        // 执行后逻辑
        return result;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 处理配置属性
    }
}

7.3 拦截 Executor、StatementHandler 等

组件拦截用途典型应用场景
Executor控制 SQL 执行流程分页、缓存控制、性能监控、事务管理增强
StatementHandler操作 SQL 语句与参数SQL 改写(如加租户字段)、慢 SQL 记录
ParameterHandler参数设置过程敏感字段加密、默认值填充
ResultSetHandler结果集处理字段脱敏、自动补全、结果转换

⚠️ 注意事项

  • 拦截 Executor 影响范围最大,慎用。
  • 修改 SQL 时注意保留原有逻辑(如 #{} ${} 解析)。
  • 避免在拦截器中进行耗时操作,影响性能。

7.4 实现分页插件示例

步骤说明示例代码/配置
1. 定义注解或参数标识需要分页的查询使用 RowBounds 或自定义 Page<T> 对象
2. 拦截 Executor.query()在查询前修改 SQL 添加 LIMIT / ROWNUM判断是否存在 RowBounds 非空
3. 获取总记录数执行 SELECT COUNT(*) 查询基于原 SQL 构造统计语句
4. 改写原始 SQL添加分页子句(MySQL: LIMIT ?,?;Oracle: ROWNUM)使用 BoundSql 获取原始 SQL
5. 封装分页结果将数据 + 总数封装为 PageInfo返回 PageResult<T> 对象

💡 简化版逻辑

public Object intercept(Invocation invocation) {
    Object[] args = invocation.getArgs();
    MappedStatement ms = (MappedStatement) args[0];
    RowBounds rowBounds = (RowBounds) args[2];

    if (rowBounds != RowBounds.DEFAULT) {
        // 1. 获取总数
        int total = countTotal(ms, args[1]);
        // 2. 改写 SQL 加入 LIMIT
        String sql = rewriteSqlWithLimit(ms, rowBounds);
        // 3. 设置新 BoundSql
        ...
        // 4. 返回包含总数的结果
        return new PageResult<>(resultList, total);
    }
    return invocation.proceed();
}

推荐方案:使用成熟框架如 PageHelper。

第8章:注解开发模式

8.1 使用注解替代 XML(@Select, @Insert 等)

注解用途示例
@Select("SELECT * FROM user WHERE id = #{id}")查询替代 <select>
@Insert("INSERT INTO user(name) VALUES(#{name})")插入替代 <insert>
@Update("UPDATE user SET name=#{name} WHERE id=#{id}")更新替代 <update>
@Delete("DELETE FROM user WHERE id=#{id}")删除替代 <delete>
@Options(useGeneratedKeys=true, keyProperty="id")控制主键生成类似 <insert> 的属性
@Results + @Result映射复杂结果替代 <resultMap>

优点:简洁、无需维护 XML 文件 ❌ 缺点:复杂 SQL 可读性差,难以调试

8.2 注解中的动态 SQL(@SelectProvider 等)

注解用途示例
@SelectProvider(type = SqlProvider.class, method = "buildSelectById")动态生成 SELECT 语句提供类 + 方法名
@InsertProvider动态 INSERT构建条件化插入语句
@UpdateProvider动态 UPDATE如根据字段是否为空更新
@DeleteProvider动态 DELETE条件删除

📌 SqlProvider 示例

public class UserSqlProvider {
    public String buildSelectById(Long id) {
        return new SQL() {{
            SELECT("*");
            FROM("user");
            if (id != null) {
                WHERE("id = #{id}");
            }
        }}.toString();
    }
}

优势:结合 Java 逻辑生成 SQL,灵活性高 🔧 工具类支持:org.apache.ibatis.jdbc.SQL 辅助构建

8.3 注解与 XML 混合使用

场景配置方式注意事项
同一个 Mapper 中混合可同时存在注解方法和 XML 定义MyBatis 会合并解析
优先级XML 定义覆盖注解若同名方法存在于 XML,则忽略注解
引用 resultMap注解中可用 @ResultMap("xxx") 引用 XML 中定义的 <resultMap>实现复用
事务与缓存缓存需在 XML 中配置 <cache/>注解方式无法直接启用二级缓存
最佳实践简单 CRUD 用注解;复杂查询、关联映射用 XML提升可维护性

配置示例

@Mapper
public interface UserMapper {
    @Select("SELECT * FROM user WHERE id = #{id}")
    @ResultMap("userResultMap") // 引用 XML 中定义的 resultMap
    User findById(Long id);

    List<User> findAll(); // 对应 XML 中的 <select id="findAll">...
}

💡 建议:团队统一风格,避免混乱;大型项目推荐以 XML 为主。

第9章:Spring 集成

9.1 MyBatis 与 Spring 整合原理

核心机制说明
依赖注入Spring 容器管理 MyBatis 的核心对象(SqlSessionFactory、SqlSessionTemplate、Mapper 接口)。
声明式事务基于 DataSourceTransactionManager 实现,通过 @Transactional 注解控制事务边界。
代理模式Spring 使用 JDK 动态代理为 Mapper 接口生成实现类,内部委托给 SqlSessionTemplate 执行 SQL。
生命周期管理SqlSession 由 Spring 自动创建、使用、关闭,避免资源泄漏。
整合包使用 mybatis-spring 桥接 MyBatis 与 Spring,提供 SqlSessionFactoryBean 和 MapperScannerConfigurer 等组件。

优势:简化配置、统一事务管理、无缝集成 Spring 生态。

9.2 配置 SqlSessionFactoryBean

属性用途示例值
dataSource指定数据源(必须)ref=“dataSource”
configLocationMyBatis 全局配置文件路径(可选)classpath:mybatis-config.xml
mapperLocations指定 Mapper XML 文件位置classpath:mapper/*.xml
typeAliasesPackage扫描别名包,自动注册类型别名com.example.entity
plugins注册拦截器(如分页插件)<array><ref bean="pageInterceptor"/></array>

📌 XML 配置示例

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    <property name="typeAliasesPackage" value="com.example.entity"/>
    <property name="plugins">
        <array>
            <ref bean="pageHelper"/>
        </array>
    </property>
</bean>

Java Config 示例

@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource);
    factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
        .getResources("classpath:mapper/*.xml"));
    factoryBean.setTypeAliasesPackage("com.example.entity");
    return factoryBean.getObject();
}

9.3 配置 Mapper 扫描(MapperScannerConfigurer)

属性用途示例值
basePackage指定 Mapper 接口所在的包com.example.mapper
sqlSessionFactoryBeanName关联 SqlSessionFactorysqlSessionFactory
annotationClass只扫描带有指定注解的接口(可选)org.apache.ibatis.annotations.Mapper
markerInterface只扫描继承指定接口的 Mapper(可选)BaseMapper.class

📌 XML 配置示例

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.example.mapper"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

效果:Spring 自动扫描包下所有 Mapper 接口,并注册为 Bean,无需手动实现。

9.4 使用 @Mapper 注解

特性说明
作用标记接口为 MyBatis Mapper,配合 @MapperScan 使用
位置直接加在 Mapper 接口上
配合注解@MapperScan("com.example.mapper") 在配置类上启用扫描
与 MapperScannerConfigurer 的关系二者功能等价,@MapperScan 是注解方式的替代

📌 Java Config 示例

@Configuration
@MapperScan("com.example.mapper")
public class MyBatisConfig {
    // 配置 SqlSessionFactory...
}

@Mapper
public interface UserMapper {
    User findById(Long id);
}

建议:推荐使用 @MapperScan 批量扫描,避免每个接口都加 @Mapper。

第10章:Spring Boot 集成

10.1 引入 mybatis-spring-boot-starter

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>
特性说明
自动配置Spring Boot 自动配置 SqlSessionFactory、SqlSessionTemplate、DataSource
简化配置无需手动配置 SqlSessionFactoryBean 和 MapperScannerConfigurer
版本兼容注意与 Spring Boot 版本匹配(如 3.0.x 支持 Spring Boot 3)

启动类无需额外注解:只要 Mapper 在组件扫描路径下即可。

10.2 配置文件(application.yml)设置

配置项说明示例
mybatis.mapper-locations指定 XML 映射文件路径classpath:mapper/*.xml
mybatis.type-aliases-package类型别名包com.example.entity
mybatis.configuration.map-underscore-to-camel-case开启下划线转驼峰true
mybatis.configuration.lazy-loading-enabled启用延迟加载true
mybatis.configuration.default-statement-timeout默认超时时间(秒)30

📌 application.yml 示例

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.entity
  configuration:
    map-underscore-to-camel-case: true
    lazy-loading-enabled: true
    aggressive-lazy-loading: false
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 控制台打印 SQL

10.3 注解方式开发

步骤说明
1. 创建 Mapper 接口使用 @Select, @Insert 等注解编写 SQL
2. 添加 @Mapper或使用 @MapperScan 扫描
3. 注入使用在 Service 中 @Autowired 注入 Mapper

📌 示例

@Mapper
public interface UserMapper {
    @Select("SELECT * FROM user WHERE id = #{id}")
    User findById(@Param("id") Long id);

    @Insert("INSERT INTO user(name, email) VALUES(#{name}, #{email})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);
}

适合场景:简单 CRUD、SQL 固定、快速开发。

10.4 XML 方式开发

步骤说明
1. 配置 mapper-locations确保 XML 文件路径正确
2. 编写 Mapper 接口只定义方法,不加注解
3. 编写 XML 文件namespace 对应接口全限定名,id 对应方法名
4. 使用 @MapperScan启用 Mapper 扫描

📌 XML 示例(UserMapper.xml)

<?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">
    <select id="findById" resultType="User">
        SELECT * FROM user WHERE id = #{id}
    </select>
</mapper>

优势:支持复杂 SQL、动态 SQL、结果映射,易于维护和调试。

10.5 集成分页插件(如 PageHelper)

1. 添加依赖

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.7</version>
</dependency>

2. 配置(可选):application.yml 中配置数据库类型、合理化分页等

3. 使用方式:在查询前调用 PageHelper.startPage(pageNum, pageSize)

📌 yml 配置示例

pagehelper:
  helper-dialect: mysql
  reasonable: true
  support-methods-arguments: true
  params: count=countSql

📌 代码使用示例

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public PageInfo<User> getPage(int pageNum, int pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        List<User> users = userMapper.findAll(); // 紧跟查询
        return new PageInfo<>(users);
    }
}

特点:自动改写 SQL 实现分页,支持多种数据库,返回 PageInfo 包含总数、页码等信息。

第11章:最佳实践与性能优化

11.1 SQL 语句优化建议

优化策略说明实践建议
避免 SELECT *减少网络传输和内存消耗明确列出所需字段,提升可读性与性能
使用索引字段查询加速数据检索WHERE、JOIN、ORDER BY中的字段应建立合适索引
避免 N+1 查询问题防止因关联查询导致大量数据库访问使用JOIN + 嵌套结果映射,或启用延迟加载
合理使用分页控制返回数据量使用LIMIT/ROWNUM分页,避免全表加载
减少子查询嵌套子查询可能影响执行计划尽量用JOIN替代深层嵌套子查询
避免在 WHERE 中对字段进行函数操作导致索引失效WHERE YEAR(create_time) = 2023 → 改为范围查询 create_time BETWEEN '2023-01-01' AND '2023-12-31'
使用预编译参数(#{}防止 SQL 注入,提升执行效率禁用 ${} 拼接用户输入,仅用于动态表名等可信场景

工具辅助:使用 EXPLAIN 分析执行计划,识别慢查询。

11.2 使用 resultMap 减少冗余

实践方式说明示例
定义基础 resultMap抽取公共字段(如 id, create_time)<resultMap id="baseResultMap" type="User"><id property="id" column="id"/><result property="createTime" column="create_time"/></resultMap>
继承复用(extends)避免重复定义<resultMap id="userWithDept" type="User" extends="baseResultMap"><association property="dept" resultMap="deptResultMap"/></resultMap>
引用已有映射多个语句共用同一映射规则<select id="findUserById" resultMap="userWithDept">...</select><select id="findUserList" resultMap="userWithDept">...</select>
自动映射 + 手动补充简单字段自动映射,复杂字段手动指定<resultMap id="autoWithAssoc" type="User" autoMapping="true"><association property="dept" column="dept_id" select="findDept"/></resultMap>

优势:提高可维护性,降低出错率,减少代码重复。

11.3 合理使用缓存

缓存类型使用建议注意事项
一级缓存(SqlSession 级)默认开启,适用于单次请求内的重复查询跨 SqlSession 不共享;DML 操作后自动清空
二级缓存(Mapper 级)适合读多写少、数据变化不频繁的场景(如字典表)必须实现 Serializable;集群环境下需配合 Redis 等分布式缓存
禁用缓存场景实时性要求高的数据、频繁更新的表、敏感信息(如密码)设置 useCache="false"
刷新策略配置 flushInterval 或通过 DML 自动刷新避免脏读
缓存粒度按业务模块启用,避免全量缓存使用 <cache> 配置 size、eviction 控制内存占用

最佳实践

  • 开启二级缓存前评估数据一致性要求。
  • 使用 @CacheNamespace(readOnly = false) 支持序列化副本,避免并发修改问题。

11.4 批量操作(BatchExecutor)

场景配置方式性能对比
批量插入/更新/删除使用 SqlSessionTemplate 或 SqlSession 的 ExecutorType.BATCH相比默认 SIMPLE 执行器,性能提升显著
Spring 集成配置@Autowired private SqlSessionFactory sqlSessionFactory; 配合 openSession(ExecutorType.BATCH)1000 条插入:BATCH 比 SIMPLE 快 5~10 倍(取决于数据库)
MyBatis-Plus 等框架支持提供 saveBatch() 方法封装批量逻辑简化开发
JDBC 层优化启用 rewriteBatchedStatements=true(MySQL)进一步提升性能
public void batchInsert(List<User> users) {
    try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
        UserMapper mapper = session.getMapper(UserMapper.class);
        users.forEach(mapper::insert);
        session.commit();
    }
}

⚠️ 注意事项

  • 批量操作不触发一级缓存。
  • 异常处理需谨慎,部分成功时考虑事务回滚策略。
  • 大数据量建议分页提交(如每 500 条 commit 一次),防止 OOM。

11.5 日志配置与调试

日志类型配置方式用途
MyBatis SQL 日志mybatis.configuration.log-impl 中设置查看实际执行的 SQL 和参数
支持的日志实现StdOutImpl:控制台输出;Log4jImpl / Log4j2Impl;SLF4JImpl推荐使用 SLF4J + Logback 统一日志框架
结合 logback-spring.xml<logger name="com.example.mapper" level="DEBUG"/><logger name="org.apache.ibatis" level="INFO"/>按 Mapper 包控制日志级别
显示参数值使用 #{} 参数绑定自动打印${} 不安全且不记录参数
格式化输出配合 p6spy 或 mybatis-plus 的性能分析插件格式化 SQL、统计执行时间

📌 application.yml 示例

mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

调试技巧

  • 生产环境关闭 SQL 日志,防止敏感信息泄露。
  • 使用 @Flush 注解强制刷新批量操作。
  • 结合 APM 工具(如 SkyWalking)监控慢查询。

💡 总结:性能优化核心原则

  • SQL 优先:优化查询语句和索引是根本。
  • 减少交互次数:避免 N+1,合理使用 JOIN 和批量操作。
  • 缓存适度:根据业务特性启用缓存,警惕数据一致性。
  • 日志可控:开发期充分调试,生产期合理降级。