戴老师的博客 - mybatis
type: channel
link: https://www.tushu.info/tag/mybatis/
items: ibatis源码分析
items: Mybatis源代码分析之类型转换
items: SpringBoot+Mybatis一级缓存和二级缓存详解
items: 为什么你用的 MyBatis 慢？一行配置让它性能翻倍！
items: 深入分析 iBATIS 框架之系统架构与映射原理
items: 深度剖析MyBatis：核心类的奥秘与强大功能
items: MyBatis缓存机制深度剖析：原理、源码与最佳实践
items: MyBatis 缓存机制深度解剖

ibatis源码分析
type: item
date: 2026-07-09T04:38:30+00:00
description: 背景本文基于调试模式下的单步运行记录，通过分析一个查询订单协议的操作，侧面剖析 iBATIS 框架的执行动作与原理。一、核心组件简介1. DAO 层与 SqlMapClientDaoSupportDAL 层的 DAO 接口实现类通常会继承 SqlMapClientDaoSupport。Spring 容器在初始化一个 DAO Bean 实例时，通常会注入两块信息：DataSource（数据源）和 SqlMapClient（主要是 SQL 语句）。这两块信息会被封装到 SqlMapClientTemplate 中。2. 数据源配置 (DataSource.html)其中数据源的实例通常采用 Apache 的开源项目 DBCP。代码配置如下：<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="xxxx"/> <property name="username"><value>xxxx</value></property> <property name="password"><value>xxxxx</value></property> <property name="maxActive"><value>20</value></property> <property name="initialSize"><value>1</value></property> <property name="maxWait"><value>60000</value></property> <property name="maxIdle"><value>20</value></property> <property name="minIdle"><value>3</value></property> <property name="removeAbandoned"><value>true</value></property> <property name="removeAbandonedTimeout"><value>180</value></property> <property name="connectionProperties"><value>clientEncoding=GBK</value></property> </bean>各配置参数的具体含义可参照：dbcp 基本配置和重连配置3. SqlMapClient 配置与初始化<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="configLocation"> <value>classpath:sqlmap.xml</value> </property> </bean> <bean id="sqlMapClientTddl" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="dataSource" ref="tGroupDataSource"/> <property name="configLocation" value="classpath:sqlmap.xml"/> </bean>SqlMapClient 主要是借助于实现 FactoryBean 和 InitializingBean 两个接口，加载 sql.xml 文件资源信息，得到 SqlMapClient 组件。注：上面的 sqlMapClient 默认不配置数据源，后面的 SqlMapClientTemplate 优先从全局变量中取，如果没有再从 sqlMapClient 中查找。public DataSource getDataSource() { DataSource ds = super.getDataSource(); return (ds != null ? ds : this.sqlMapClient.getDataSource(.html)); }构造 SqlMapClient 组件的核心代码块：public void afterPropertiesSet() throws Exception { if (this.lobHandler != …

Mybatis源代码分析之类型转换
type: item
date: 2026-07-09T04:36:51+00:00
description: 编者注：本文为历史博文归档；涉及 JDK、框架与工具链版本请以当前官方文档为准。引用外链图片可能失效，阅读时请注意时效性。ORM 框架的核心功能在于建立面向对象模型与关系型数据库表之间的映射关系。在这一关联过程中，必然涉及对象中的数据类型与数据库表字段类型之间的转换。MyBatis 中的 org.apache.ibatis.type 包主要便是为了实现这一功能。1. org.apache.ibatis.type 基础类在 MyBatis 官网（http://mybatis.github.io/mybatis-3/configuration.html#typeHandlers）关于类型转换有如下的描述：Whenever MyBatis sets a parameter on a PreparedStatement or retrieves a value from a ResultSet, a TypeHandler is used to retrieve the value in a means appropriate to the Java type.当 MyBatis 为 PreparedStatement 设置参数时或者从 ResultSet 中获取数据时，会根据 Java 类型使用 TypeHandler 去获取相应的值。官网中也列出了每一个 TypeHandler 用来处理对应的 JDBC 类型和 Java 类型。1.1 TypeHandler 接口这个接口主要有三个方法：一个 set 方法：用来给 PreparedStatement 对象对应的列设置参数。两个 get 方法：从 ResultSet 和 CallableStatement 获取对应列的值。不同之处在于一个是取第几个位置的值，一个是取具体列名所对应的值。set 用来将 Java 对象中的数据类型转换为 JDBC 中对应的数据类型，get 用来将 JDBC 中对应的数据类型转换为 Java 对象中的数据类型。1.2 BaseTypeHandler 抽象类在进行软件设计时提倡面向接口的设计，但接口只是一个规范，并不做任何实质性的操作，还需有一系列的实现才可以真正的达到目标。BaseTypeHandler 类便是对 TypeHandler 接口的初步实现。在实现 TypeHandler 接口的三个函数外，又引入了 3 个抽象函数用于 null 值的处理。1.3 DateTypeHandler 示例承接上文，BaseTypeHandler 类也是一个抽象类，按照 Java 的规定抽象类并不能初始化，也不能直接使用，因而还需要有具体的实现类。在 type 包中有十多个具体的类来具体处理类型转换，每一个类处理一个数据类型，像 long、int、double 等等。我们以一个稍微复杂些的 DateTypeHandler 类为例，了解下对日期是如何进行处理的。1.3.1 setNonNullParameterpublic void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType.html) throws SQLException { ps.setTimestamp(i, new java.sql.Timestamp(((Date.html) parameter).getTime())); }首先将参数 parameter 这个 Object 转换为 Date 类型，而后通过 Date 对象的 getTime() 将日期转为毫秒数，而后再将毫秒数转换为 java.sql.Timestamp 对象。即将 java.util.Date 对象转换为 java.sql.Timestamp 对象。1.3.2 getNullableResultpublic Object getNullableResult(ResultSet rs, String columnName.html) throws SQLException { java.sql.Timestamp sqlTimestamp = rs.getTimestamp(columnName.html); if (sqlTimestamp != null.html) { return new java.util.Date(sqlTimestamp.getTime(.html)); } return null; } public Object getNullableResult(CallableStatement cs, int columnIndex.html) throws SQLException { ja…

SpringBoot+Mybatis一级缓存和二级缓存详解
type: item
date: 2026-07-09T04:35:55+00:00
description: Spring Boot + MyBatis 一级缓存和二级缓存详解本文主要介绍在 Spring Boot 项目中如何使用 MyBatis 的一级缓存和二级缓存。为了演示方便，本文的数据库采用 H2 内存数据库，数据库连接池默认使用 Spring Boot 2.x 自带的 HikariCP。正确使用 MyBatis 缓存可以有效减少多余的数据库查询操作，节约 I/O 资源。接下来我们从实践出发，看一看 MyBatis 的一级缓存、二级缓存如何使用。相关代码请查阅：https://github.com/zhengxl5566/springboot-demo.git1. 概念介绍什么是一级缓存在日常开发过程中，经常会有相同的 SQL 执行多次查询的情况。MyBatis 提供了一级缓存来优化这些查询，避免多次请求数据库。一级缓存在 MyBatis 中默认是开启的，并且是 Session 级别，它的作用域为一次 SqlSession 会话。什么是二级缓存相对于一级缓存，二级缓存的作用域更广泛。它不止局限于一个 SqlSession，可以在多个 SqlSession 之间共享。事实上，它的作用域是 Namespace。MyBatis 的二级缓存全局开关默认是开启的，但由于它的作用域是 Namespace，所以还需要在 mapper.xml 中配置 <cache> 标签才能生效。缓存的优先级通过 MyBatis 发起的查询，查找顺序为：二级缓存 -> 一级缓存 -> 数据库。其中任何一个环节查到不为空的数据，都将直接返回结果。缓存失效当在一个缓存作用域中发生了 update、insert、delete 动作后，将会触发缓存失效。下一次查询将命中数据库，从而保证不会查到脏数据。2. 代码演示2.1 一级缓存默认情况下，MyBatis 开启并使用了一级缓存。单元测试用例： /** * 开启事务，测试一级缓存效果 * 缓存命中顺序：二级缓存 ---> 一级缓存 ---> 数据库 **/ @Test @Transactional(rollbackFor = Throwable.class) public void testFistCache(){ // 第一次查询，缓存到一级缓存 userMapper.selectById(1.html); // 第二次查询，直接读取一级缓存 userMapper.selectById(1.html); }执行结果：可以看到，虽然进行了两次查询，但最终只请求了一次数据库。第二次查询命中了一级缓存，直接返回了数据。这里有两点需要说明：为什么开启事务？由于使用了数据库连接池（HikariCP），默认每次查询完之后自动 commit，这就导致两次查询使用的不是同一个 SqlSession。根据一级缓存的原理，它将永远不会生效。当我们开启了事务，两次查询都在同一个 SqlSession 中，从而让第二次查询命中了一级缓存。读者可以自行关闭事务验证此结论。两种一级缓存模式一级缓存的作用域有两种：SESSION（默认）和 STATEMENT。可通过设置 local-cache-scope 的值来切换，默认为 SESSION。二者的区别在于 SESSION 会将缓存作用于同一个 SqlSession，而 STATEMENT 仅针对一次查询。所以，local-cache-scope: STATEMENT 可以理解为关闭一级缓存。2.2 二级缓存默认情况下，MyBatis 打开了二级缓存的全局开关，但它并未立即生效。因为二级缓存的作用域是 Namespace，所以还需要在 Mapper.xml 文件中配置一下才能使二级缓存生效。单表二级缓存下面对 userMapper.xml 配置一下，让其二级缓存生效，只需加入 <cache> 标签即可。<cache></cache>单元测试用例： /** * 测试二级缓存效果 * 需要 *Mapper.xml 开启二级缓存 **/ @Test public void testSecondCache(){ userMapper.selectById(1.html); userMapper.selectById(1.html); }执行结果：这里可以看到，第二次查询直接命中了缓存，日志还打印了该缓存的命中率。读者可以自行关闭二级缓存查看效果，通过注掉对应 mapper.xml 的 <cache> 标签，或者设置 cache-enabled: false 均可。多表联查二级缓存接下来演示多表联查的二级缓存，user 表 left join user_order 表 on user.id = user_order.user_id。我们考虑这样一种情况：该联查执行两次，第二次联查前更新 user_orde…

为什么你用的 MyBatis 慢？一行配置让它性能翻倍！
type: item
date: 2026-07-09T04:35:20+00:00
description: 在 Java 后端开发领域，MyBatis 凭借其灵活的 SQL 编写能力与便捷的数据库对接特性，成为了众多开发者的首选 ORM 框架。然而，在实际生产环境中，不少团队面临着 MyBatis 响应缓慢、拖慢业务整体性能的困境。面对高并发场景下的页面加载延迟或接口超时告警，往往并非代码逻辑复杂所致，而是默认配置未能适配实际业务需求。本文将深入剖析 MyBatis 性能瓶颈，并提供关键的配置优化方案。一、MyBatis 性能瓶颈现状剖析在日常开发中，基于 MyBatis 编写的业务代码在本地测试环境下往往表现正常，但一旦上线面对真实的大数据量与高并发场景，性能问题便接踵而至。究其根源，主要是 MyBatis 的默认配置在特定场景下存在“水土不服”的现象。例如，MyBatis 的一级缓存（Local Cache）机制初衷是为了减少数据库查询次数，但在某些会话管理不当的场景下，缓存可能频繁失效或重建，带来额外的开销。此外，SQL 执行过程中 Statement 对象的创建与资源管理若未优化，反复的资源申请与释放也会消耗系统资源，影响执行效率。二、核心性能瓶颈分析（一）参数设置不合理：fetchSize 默认值过小MyBatis 的 fetchSize 参数决定了每次从数据库驱动层拉取数据的行数。该参数的默认值通常较小（依赖 JDBC 驱动默认值，往往为 10 或更小）。这意味着在查询大量数据时，数据库需要多次往返传输数据，导致网络开销剧增。类比：如同去仓库搬货，若每次只拿一件，大部分时间都浪费在往返路上；若每次搬运一箱，效率将显著提升。（二）缓存策略不当一级缓存：默认开启且作用域为 SqlSession。若会话管理粒度不合理，容易导致脏读或数据不一致，且在高并发下若会话复用策略不当，可能引发性能波动。二级缓存：虽能跨会话共享数据，但配置相对繁琐。许多开发者因难以把握缓存过期与清理策略，要么直接弃用，要么开启后因配置失误导致系统响应变慢。（三）SQL 执行细节缺失MyBatis 生成的 SQL 语句在执行计划上可能并非最优。例如，关联查询时未充分利用数据库索引，导致数据库引擎进行全表扫描。在海量数据面前，这种低效查询的时间复杂度会显著增加，成为系统瓶颈。三、关键配置优化：调整 defaultFetchSize针对上述瓶颈，尤其是大数据量查询场景，可以通过调整 MyBatis 的全局配置来显著改善性能。在 MyBatis 配置文件（mybatis-config.xml）中加入以下配置：<settings> <setting name="defaultFetchSize" value="1000"/> </settings>优化原理：将 defaultFetchSize 设置为 1000，意味着 MyBatis 每次从数据库抓取数据时，会一次性拉取 1000 行记录。这大幅减少了数据库连接交互次数与网络传输频次，从而提升整体查询效率。实战效果：在某电商系统商品列表页查询场景中，优化前加载 5000 件商品信息耗时近 10 秒；添加该配置后，同等数据量下耗时锐减至 3 秒以内，性能提升显著。四、配套优化方案仅靠单一配置无法解决所有性能问题，建议结合以下手段全方位提升 MyBatis 性能：（一）缓存精细化管理一级缓存：根据业务场景合理控制 SqlSession 生命周期。二级缓存：针对只读或低频变更的业务（如商品分类信息）开启二级缓存，并设置合理的过期时间（如 30 分钟），定期清理无效缓存，平衡数据准确性与读取效率。（二）SQL 优化“组合拳”索引优化：结合数据库特性，为频繁查询字段及关联查询字段添加索引。执行计划分析：利用 EXPLAIN 语句分析 SQL 执行计划，及时调整查询语句，摒弃低效写法。连接池配置：采用高效的数据库连接池（如 HikariCP），复用连接资源，降低连接创建成本。（三）监控与调优接入性能监控工具（如 Arthas、Pinpoint 等），实时监测 MyBatis 执行 SQL 的耗时与资源占用情况。依据监控数据动态调整配置参数，实现持续迭代优化。五、实战验证与常见问题答疑实战案例在某社交平台用户动态查询模块中，初期因 MyBatis 查询效率低，用户刷新动态延迟明显。通过调整 fetchSize 配置、优化关联查询 SQL 并完善缓存策略后，页面实现秒开，用户活跃度随之提升。常见问题解答Q1：改大 defaultFetchSize 会不会导致内存溢出（OOM）？A： 合理取值是关键。需依据服务器内存大小与业务单次查询数据量权衡，一般设置在 1000 - 5000 之间较安全。同时务必做好分页处理，防止单次加载过量数据。Q2：二级缓存配置复杂，有没有简化思路？A： 优先选用整合框架自带的缓存方案。例如在 Sprin…

深入分析 iBATIS 框架之系统架构与映射原理
type: item
date: 2026-07-09T04:31:58+00:00
description: iBATIS 框架主要的类层次结构总体来说，iBATIS 的系统结构相对简洁，它主要完成两件事情：根据 JDBC 规范建立与数据库的连接；通过反射打通 Java 对象与数据库参数交互之间的相互转化关系。iBATIS 的框架结构正是按照这种思想来组织类层次结构的，其实它是一种典型的交互式框架。先期准备好交互的必要条件，然后构建一个交互的环境，交互环境中还划分成会话，每次的会话也有一个环境。当这些环境都准备好以后，剩下的就是交换数据了。其实涉及到网络通信，一般都会是类似的处理方式。图 1 是 iBATIS 框架的主要类层次结构图：图 1. iBATIS 框架的主要类层次结构图上面的类图中，左边 SqlMapClient 接口主要定义了客户端的操作行为，包括 select、insert、update、delete。而右边主要是定义了当前客户端在当前线程的执行环境。SqlMapSession 可以共享使用，也可以自己创建；如果是自己创建，在结束时必须要调用关闭接口关闭。当使用者持有了 SqlMapClientImpl 对象就可以使用 iBATIS 来工作了。这里还要提到另外一个类 SqlMapExecutorDelegate，这个类从名字就可以看出它是执行代理类。这个类非常重要，因为它耦合了用户端的执行操作行为和执行的环境，它持有执行操作所需要的数据，同时提供管理着执行操作依赖的环境。所以它是一个强耦合的类，也可以看做是个工具类。iBATIS 框架的设计策略iBATIS 主要的设计目的还是为了让我们执行 SQL 时对输入输出的数据管理更加方便，所以如何方便地让我们写出 SQL 和方便地获取 SQL 的执行结果才是 iBATIS 的核心竞争力。那么 iBATIS 是怎么实现它的核心竞争力的呢？iBATIS 框架的一个重要组成部分就是其 SqlMap 配置文件，SqlMap 配置文件的核心是 Statement 语句，包括 CRUD（增删改查）。iBATIS 通过解析 SqlMap 配置文件得到所有的 Statement 执行语句，同时会形成 ParameterMap、ResultMap 两个对象用于处理参数和经过解析后交给数据库处理的 Sql 对象。这样除去数据库的连接，一条 SQL 的执行条件已经具备了。图 2 描述了 Statement 有关的类结构图：图 2. Statement 有关的类结构图图 2 给出了围绕 SQL 执行的基本结构关系，但是还有一个关键的部分就是，如何定义 SQL 语句中的参数与 Java 对象之间的关系，这其中还涉及到 Java 类型到数据库类型的转换等一系列问题。数据的映射大体的过程是这样的：根据 Statement 中定义的 SQL 语句，解析出其中的参数，按照其出现的顺序保存在 Map 集合中，并按照 Statement 中定义的 ParameterMap 对象类型解析出参数的 Java 数据类型。并根据其数据类型构建 TypeHandler 对象，参数值的复制是通过 DataExchange 对象完成的。图 3 是参数映射相关的类结构图：图 3. 参数映射相关的类结构图图 3 是输入参数的映射结构情况，返回结果 ResultMap 的映射情况也是类似的。主要就是要解决 SQL 语句中的参数与返回结果的列名与 Statement 中定义的 parameterClass 和 resultClass 中属性的对应关系。iBATIS 框架的运行原理前面大体分析了 iBATIS 框架的主要类的结构，这里主要看一下这些类是如何串联起来、如何工作的。图 4 描述了整个过程的主要执行步骤。图 4. iBATIS 运行的主要执行步骤上图中描述的 SqlMapSession 对象的创建和释放根据不同情况会有不同，因为 SqlMapSession 负责创建数据库的连接，包括对事务的管理。iBATIS 对管理事务既可以自己管理也可以由外部管理：iBATIS 自己管理是通过共享 SqlMapSession 对象实现的，多个 Statement 的执行时共享一个 SqlMapSession 实例，而且都是线程安全的；如果是外部程序管理就要自己控制 SqlMapSession 对象的生命周期。图 5 是通过 Spring 调用 iBATIS 执行一个 Statement 的一个详细的时序图：图 5. Spring 调用 iBATIS 执行一个 Statement 的时序图（查看图 5 的 清晰版本。）iBATIS 的主要工作是连接、交互，所以必须根据不同的事务场景设计不同的交易环境。示例下面我们将根据一个具体的实例解析一个 Statement 如何完成映射的，我们用一个典型的查询语句看看 Java 对象中的数据是如何赋给 SQL…

深度剖析MyBatis：核心类的奥秘与强大功能
type: item
date: 2026-07-09T04:30:27+00:00
description: 在 Java 持久层框架的世界里，MyBatis 以其灵活、高效的特性备受开发者青睐。今天，让我们一同深入探究 MyBatis 中几个至关重要的类，揭开它们的神秘面纱，领略 MyBatis 的强大魅力。一、MappedStatement：SQL 语句的映射使者（一）功能概述MappedStatement 在 MyBatis 框架中扮演着关键角色，它犹如一座桥梁，将 XML 文件中的 SQL 语句节点（如 、<update>、<insert> 标签）与 Java 代码紧密相连。在 MyBatis 框架初始化阶段，会对 XML 配置文件进行深度扫描和解析，将其中的 SQL 语句节点逐一转化为一个个 MappedStatement 对象，从而构建起 SQL 语句与代码逻辑之间的映射关系。（二）实例解析以一个简单的 XML Mapper 文件为例：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="mybatis.UserDao"> <cache type="org.mybatis.caches.ehcache.LoggingEhcache"/> <resultMap id="userResultMap" type="UserBean"> <id property="userId" column="user_id"/> <result property="userName" column="user_name"/> <result property="userPassword" column="user_password"/> <result property="createDate" column="create_date"/> </resultMap> select * from user <where> <if test="userName!=null and userName!=''"> and user_name = #{userName} </if> <if test="userPassword!=null and userPassword!=''"> and user_password = #{userPassword} </if> <if test="createDate!=null"> and create_date = #{createDate} </if> </where> select * from user <where> <if test="userName!=null and userName!=''"> and user_name = #{userName} </if> <if test="userPassword!=null and userPassword!=''"> and user_password = #{userPassword} </if> <if test="createDate!=null"> and create_date = #{createDate} </if> </where> </mapper>MyBatis 解析该文件后，会注册两个 MappedStatement 对象，分别对应 id 为 find 和 find2 的 节点。在 MyBatis 框架中，为了确保每个 MappedStatement 对象的唯一性，其标识采用 Mapper 文件的 namespace 加上节点本身的 id 值。例如，上述两个 MappedStatement 对象在 MyBatis 框架中的唯一标识分别是 mybatis.UserDao.find 和 mybatis.UserDao.find2。（三）源码探秘查看 MappedStatement 对象的源码，我们可以看到其中包含了众多属性，这些属性与 XML 元素的属性存在着紧密的对应关系。其中，比较关键的属性包括：ParameterMap 对象：用于表示查询参数，它明确了输入参数的类型和映射关系，确保 SQL 语句在执行时能够正确获取参数值。ResultMap 列表（resultMaps）：负责定义 SQL 查询结果与 Java 对象之间的映射规则，使得 MyBatis 能够将从数据库中获取的数据准确无误地封装成 JavaBean 对象，方便在 Java 代码中进行处理。SqlSource 对象：这是最为重要的属性之一，它承担着执行动态 SQL 计算和获取的重任。通过这个对象，MappedStatement 能够根据用户提供的查询参数对象，动态生成要执行的 SQL 语句，充分展现了 MyBatis 的灵活性。（四）工作流程MappedStatement 对象的工作流程清晰而高效。当…

MyBatis缓存机制深度剖析：原理、源码与最佳实践
type: item
date: 2026-07-09T04:29:40+00:00
description: 在当今的软件开发领域，数据访问效率一直是备受关注的焦点。随着应用程序规模的不断扩大，数据库操作的性能成为了影响系统整体性能的关键因素之一。MyBatis 作为一款流行的 Java 持久层框架，其缓存机制在提升数据访问效率方面发挥着重要作用。本文将深入剖析 MyBatis 的缓存机制，包括一级缓存和二级缓存的原理、源码实现以及最佳实践，旨在帮助读者全面理解并掌握这一特性，从而在实际应用中优化数据库操作性能。缓存机制概述MyBatis 提供了两级缓存机制来减少数据库交互次数，提升查询性能：一级缓存（Local Cache）：基于 SqlSession 级别，默认开启。二级缓存（Global Cache）：基于 Mapper（Namespace）级别，需手动配置开启。一级缓存MyBatis 的一级缓存基于 PerpetualCache 的 HashMap 实现，存储作用域为 SqlSession。当一个 SqlSession 被创建时，MyBatis 会为其创建一个对应的一级缓存对象。在该 SqlSession 进行数据库查询操作时，查询结果会被缓存在这个 HashMap 中。当再次执行相同的查询语句时，MyBatis 会先从一级缓存中查找，如果找到匹配的结果，则直接返回缓存中的数据，避免了再次与数据库交互，从而提高了查询效率。以下是一级缓存的简单示例代码：SqlSession sqlSession = sqlSessionFactory.openSession(); try { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 第一次查询 User user1 = userMapper.getUserById(1.html); // 第二次查询，会从一级缓存中获取数据 User user2 = userMapper.getUserById(1.html); // user1 和 user2 是同一个对象 System.out.println(user1 == user2.html); } finally { sqlSession.close(); }二级缓存二级缓存与一级缓存机制相似，默认也使用 PerpetualCache 和 HashMap 存储。但其存储作用域为 Mapper（Namespace），这意味着不同的 Mapper 可以拥有独立的二级缓存。二级缓存可以通过配置文件进行自定义，支持多种存储源，如 Ehcache、Redis 等，以满足不同场景的需求。当在多个 SqlSession 之间共享数据时，二级缓存发挥着重要作用。在配置文件中开启二级缓存的示例如下：<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>缓存数据更新机制当在某一个作用域（一级缓存 SqlSession / 二级缓存 Namespace）内进行了插入（Insert）、更新（Update）或删除（Delete）操作后，默认情况下该作用域下所有的 SELECT 查询缓存将被清除。这确保了缓存数据与数据库数据的一致性，避免了因数据更新而导致的缓存数据过时问题。核心设计：Delegate 与装饰器模式MyBatis 的缓存采用了 Delegate（委托）机制及装饰器模式（Decorator Pattern）设计。当进行 put、get、remove 等操作时，会经过多层 Delegate Cache 处理。Cache 类别主要包括：BaseCache：基础缓存，是缓存数据最终存储的处理类，默认为 PerpetualCache，基于 Map 存储。EvictionCache：排除算法缓存，在缓存数量达到一定大小后，通过算法（如默认的 LRU 算法）对缓存数据进行清除。DecoratorCache：装饰器缓存，用于在缓存 put/get 处理前后进行装饰。例如：LoggingCache：输出缓存命中日志信息。SerializedCache：对 Cache 的数据 put 或 get 进行序列化及反序列化处理。源码深度剖析执行过程接口调用与方法拦截：在 Service 层调用 Mapper Interface 中的方法时，实际上调用的是 MapperProxy 中的方法，MapperProxy 会拦截所有方法调用。方法执行与数据库操作：MapperProxy 将方法调用委托给 MapperMethod 处理，MapperMethod 根据操作类型（如 INSERT、UPDATE、DELETE、SELECT）调用 DefaultSqlSession 中的相应执行方法。缓存执行器的选择：在 D…

MyBatis 缓存机制深度解剖
type: item
date: 2026-07-09T04:29:28+00:00
description: 编者注：本文为历史博文归档；涉及 JDK、框架与工具链版本请以当前官方文档为准。引用外链图片可能失效，阅读时请注意时效性。缓存概述正如大多数持久层框架一样，MyBatis 同样提供了一级缓存和二级缓存的支持：一级缓存：基于 PerpetualCache 的 HashMap 本地缓存。其存储作用域为 Session，当 Session flush 或 close 之后，该 Session 中的所有 Cache 将被清空。二级缓存：与一级缓存机制相同，默认也是采用 PerpetualCache（HashMap 存储）。不同在于其存储作用域为 Mapper (Namespace.html)，并且可自定义存储源，如 Ehcache、Hazelcast 等。缓存更新机制：当某一个作用域（一级缓存 Session / 二级缓存 Namespaces）进行了 C/U/D 操作后，默认该作用域下所有 select 中的缓存将被 clear。设计模式：MyBatis 的缓存采用了 Delegate 机制及装饰器模式设计。当 put、get、remove 时，其中会经过多层 Delegate Cache 处理。其 Cache 类别主要有：BaseCache（基础缓存）：为缓存数据最终存储的处理类，默认为 PerpetualCache，基于 Map 存储；可自定义存储处理，如基于 EhCache、Memcached 等。EvictionCache（排除算法缓存）：当缓存数量达到一定大小后，将通过算法对缓存数据进行清除。默认采用 LRU 算法 (LruCache.html)，提供有 FIFO 算法 (FifoCache.html) 等。DecoratorCache（装饰器缓存）：缓存 put/get 处理前后的装饰器。例如使用 LoggingCache 输出缓存命中日志信息、使用 SerializedCache 对 Cache 的数据 put 或 get 进行序列化及反序列化处理、当设置 flushInterval (默认 1 小时.html) 后，则使用 ScheduledCache 对缓存数据进行定时刷新等。Key 生成规则：一般缓存框架的数据结构基本上都是 Key-Value 方式存储，MyBatis 对于其 Key 的生成采取规则为：[hashcode : checksum : mappedStatementId : offset : limit : executeSql : queryParams]。并发同步：对于并发 Read/Write 时缓存数据的同步问题，MyBatis 默认基于 JDK concurrent 中的 ReadWriteLock，使用 ReentrantReadWriteLock 的实现，从而通过 Lock 机制防止在并发 Write Cache 过程中出现线程安全问题。源码剖解接下来将结合 MyBatis 序列图进行源码分析。在分析其 Cache 前，先看看其整个处理过程。执行过程通常情况下，我们需要在 Service 层调用 Mapper Interface 中的方法实现对数据库的操作，上述根据产品 ID 获取 Product 对象。当调用 ProductMapper 中的方法时，其实这里所调用的是 MapperProxy 中的方法，并且 MapperProxy 已经将所有的方法拦截。其具体原理及分析，参考 MyBatis+Spring 基于接口编程的原理分析，其 invoke 方法代码为：// 当调用 Mapper 所有的方法时，将都交由 Proxy 中的 invoke 处理 public Object invoke(Object proxy, Method method, Object[] args.html) throws Throwable { try { if (!OBJECT_METHODS.contains(method.getName(.html))) { final Class declaringInterface = findDeclaringInterface(proxy, method.html); // 最终交由 MapperMethod 类处理数据库操作，初始化 MapperMethod 对象 final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession.html); // 执行 mapper method，返回执行结果 final Object result = mapperMethod.execute(args.html); .... return result; }…