当前位置: 首页 > news >正文

彻底吃透MyBatis核心原理:SqlSession、两级缓存、Spring集成机制一次说清吃透

本文作者:CodeStats
一个专注分享Java底层原理、自研框架实战干货的技术博主
如果觉得内容实用,欢迎点赞 + 收藏 + 关注


📖 目录

  • 一、写在前面

  • 二、SqlSession:MyBatis的核心入口

  • 三、生命周期:非线程安全,禁止共享

  • 四、两级缓存机制(高频面试+生产重点)

  • 五、核心组件与完整执行流程

  • 六、分页机制:为什么不用RowBounds?

  • 七、Spring集成原理:SqlSessionTemplate的秘密

  • 八、自研框架模拟MyBatis事务

  • 九、核心速查表

  • 十、写在最后


一、写在前面

日常开发中,我们几乎每天都会用到MyBatis。下面这段基础代码,想必所有Java开发者都见过:

java

SqlSession sqlSession = sqlSessionFactory.openSession(); try { UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.selectById(1); sqlSession.commit(); } finally { sqlSession.close(); }

很多人只会机械套用这段代码,但很少有人深究:

  • SqlSession的本质是什么?

  • 它的生命周期到底该如何把控?

  • 事务提交、会话关闭的底层意义是什么?

本文将从底层逻辑出发,逐一拆解MyBatis最核心的SqlSession会话、两级缓存、Spring集成机制,帮你彻底吃透MyBatis核心原理,告别只会用、不懂原理的开发困境。


二、SqlSession:MyBatis的核心入口

用最通俗的话总结:

SqlSession是MyBatis与数据库交互的一次性工作单元,也是MyBatis操作数据库的核心入口。

2.1 SqlSession的四大核心职责

职责说明
1️⃣ 执行SQL语句查询、新增、修改、删除
2️⃣ 获取Mapper代理对象通过动态代理返回接口实现
3️⃣ 管理数据库事务开启、提交、回滚、关闭
4️⃣ 维护一级缓存会话级别的本地缓存

三、生命周期:非线程安全,禁止共享

3.1 核心原则(⭐最重要)

SqlSession不具备线程安全性,多个线程不能共用同一个SqlSession。

违反这一原则会导致:数据错乱 + 事务异常 + 诡异Bug

3.2 标准使用规范

场景使用策略
单次数据库操作单独创建一个SqlSession
Web项目一个HTTP请求对应一个独立SqlSession
多线程场景每个线程持有自己的SqlSession,互不干扰

3.3 完整生命周期流程图

text

应用启动 ↓ SqlSessionFactory(全局单例,只创建一次) ↓ 每次数据库操作 → openSession() → 创建新的SqlSession ↓ 获取Mapper代理对象 → 执行SQL ↓ commit / rollback ↓ close()(必须!)

3.4 ✅ 正确 vs ❌ 错误示例

✅ 正确写法:try-with-resources(推荐)

java

// 自动关闭会话,无需手动finally try (SqlSession session = sqlSessionFactory.openSession()) { UserMapper mapper = session.getMapper(UserMapper.class); return mapper.selectById(id); }
❌ 错误写法:会话未关闭(连接泄漏)

java

// 高危:会话未关闭,长期堆积会导致连接池耗尽 SqlSession session = sqlSessionFactory.openSession(); UserMapper mapper = session.getMapper(UserMapper.class); return mapper.selectById(id); // 无close()操作,资源无法释放

⚠️后果:数据库连接池耗尽 → 应用假死 → 线上事故


四、两级缓存机制(高频面试+生产重点)

MyBatis内置两级缓存,用于减少数据库查询次数、提升接口性能。

4.1 一级缓存(SqlSession级别)

核心特性:

属性说明
默认状态✅ 默认开启,无法关闭
作用范围仅当前同一个SqlSession内有效
底层实现PerpetualCache,本质是HashMap
生命周期会话创建 → 缓存创建;会话关闭 → 缓存销毁
演示案例

java

try (SqlSession session = factory.openSession()) { UserMapper mapper = session.getMapper(UserMapper.class); User user1 = mapper.selectById(1); // 第一次查询,访问数据库 User user2 = mapper.selectById(1); // 第二次查询,命中一级缓存 System.out.println(user1 == user2); // 输出 true(同一内存地址) }
❌ 一级缓存失效场景(重点)
场景说明
执行增删改操作INSERT/UPDATE/DELETE
手动清空缓存调用clearCache()
事务提交/回滚commit()/rollback()
会话关闭close()

4.2 二级缓存(Mapper级别)

核心特性:

属性说明
默认状态❌ 默认关闭,需手动开启
作用范围全局同一个Mapper接口下,跨会话共享
数据持久会话关闭后缓存依然存在(应用级别)
实体类要求必须实现Serializable序列化接口
开启二级缓存(两步)

第一步:全局配置文件开启总开关

xml

<!-- mybatis-config.xml --> <settings> <setting name="cacheEnabled" value="true"/> </settings>

第二步:Mapper.xml开启缓存

xml

<mapper namespace="com.example.UserMapper"> <cache eviction="LRU" <!-- 淘汰策略:最近最少使用 --> flushInterval="60000" <!-- 60秒刷新一次 --> size="512" <!-- 最多缓存512个对象 --> readOnly="false"/> <!-- 非只读,可返回克隆对象 --> </mapper>

4.3 两级缓存核心对比

对比项一级缓存二级缓存
作用范围单个SqlSession会话SqlSession,全局Mapper级别
默认状态✅ 开启(不可关闭)❌ 关闭(需手动开启)
生命周期会话结束即销毁应用运行期间持续存在
共享性线程/会话私有全局共享
实体类要求无要求必须实现Serializable

五、核心组件与完整执行流程

5.1 核心组件职责一览

组件职责
SqlSessionFactory会话工厂,全局单例,唯一职责:创建SqlSession
SqlSession数据库会话,SQL执行 + 事务管理 + 缓存维护
ExecutorSQL执行器,负责缓存校验、SQL执行、事务调度
MappedStatement封装SQL语句、参数规则、结果映射规则
StatementHandler负责JDBCStatement创建、参数赋值、SQL执行
ParameterHandler处理SQL参数的类型转换与赋值
ResultSetHandler处理结果集,封装为Java实体对象
TypeHandler实现Java类型 ↔ 数据库字段类型转换

5.2 完整组件交互流程图

text

开发者调用 Mapper 接口方法 ↓ 获取 Mapper 动态代理对象(JDK代理) ↓ 代理对象调用 SqlSession 查询/更新方法 ↓ ┌─────────────────────────────────────┐ │ Executor 执行器 │ │ ① 校验二级缓存 → ② 校验一级缓存 │ └─────────────────────────────────────┘ ↓(缓存未命中) StatementHandler 执行真实SQL ↓ ParameterHandler 注入参数 ↓ JDBC 执行 SQL ↓ ResultSetHandler 封装结果集 ↓ 结果存入缓存 → 返回给开发者

六、分页机制:为什么不用RowBounds?

6.1 原生RowBounds(❌ 不推荐)

问题本质:内存分页,不会修改原生SQL

java

// 不推荐:会查询全量数据后在内存中截取 RowBounds rowBounds = new RowBounds(offset, limit); List<User> list = session.selectList("selectAll", null, rowBounds);

⚠️严重缺陷:数据量大时 → 全量查库 → 内存溢出 → 性能卡顿

6.2 PageHelper物理分页(✅ 生产推荐)

核心原理:基于MyBatis插件机制,直接改写底层SQL

java

// 推荐:仅查询当前页数据 PageHelper.startPage(pageNum, pageSize); List<User> list = userMapper.selectAll(); PageInfo<User> pageInfo = new PageInfo<>(list);

SQL改写效果对比:

sql

-- 原始SQL SELECT * FROM user -- PageHelper改写后(MySQL) SELECT * FROM user LIMIT 0, 10

PageHelper核心原理(三步):

  1. 通过ThreadLocal获取当前线程的分页参数

  2. 根据数据库类型动态拼接LIMIT/ROWNUM

  3. SQL执行完成后自动清除ThreadLocal,避免参数污染


七、Spring集成原理:SqlSessionTemplate的秘密

7.1 核心类:SqlSessionTemplate

Spring并未直接使用原生SqlSession,而是通过SqlSessionTemplate封装,实现会话自动管理 + 事务同步 + 线程安全适配

SqlSessionTemplate本质是SqlSessionJDK动态代理对象。

7.2 对象依赖链路(Spring容器启动时)

text

SqlSessionFactoryBean(配置加载) ↓ SqlSessionFactory(全局单例) ↓ MapperFactoryBean(为每个Mapper接口生成) ↓ Mapper代理对象(JDK动态代理) ↓ SqlSessionTemplate(代理原生SqlSession) ↓ DefaultSqlSession(原生会话) ↓ Executor执行器

7.3 日常开发:直接@Autowired注入

配置类:

java

@Configuration @MapperScan("com.example.mapper") // 扫描Mapper接口包 public class MyBatisConfig { @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); return factoryBean.getObject(); } }

业务使用:

java

@Service public class UserService { @Autowired private UserMapper userMapper; // Spring自动注入代理对象 public User getUser(Integer id) { return userMapper.selectById(id); // 无需手动创建会话 } }

自动注入核心流程:

  1. @MapperScan扫描指定包下所有Mapper接口

  2. 为每个接口生成MapperFactoryBean工厂Bean

  3. 工厂Bean通过SqlSessionTemplate获取JDK动态代理对象

  4. 代理对象交由Spring容器管理,实现自动注入


八、自研框架模拟MyBatis事务

为了更透彻理解MyBatis事务与代理原理,我们通过自研框架模拟声明式事务的实现逻辑。

8.1 自定义事务注解

java

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Transactional { Propagation propagation() default Propagation.REQUIRED; }

8.2 事务动态代理实现

java

public class TransactionProxy implements InvocationHandler { private final Object target; private final JdbcTemplate jdbcTemplate; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 判断方法是否添加事务注解 if (method.isAnnotationPresent(Transactional.class)) { try { jdbcTemplate.beginTransaction(); // 开启事务 Object result = method.invoke(target, args); jdbcTemplate.commit(); // 提交事务 return result; } catch (Exception e) { jdbcTemplate.rollback(); // 回滚事务 throw e; } } // 无事务注解,直接执行 return method.invoke(target, args); } }

8.3 事务功能使用示例

java

@Service public class UserService { @Autowired private UserMapper userMapper; @Transactional // 添加事务注解,方法内所有操作纳入同一事务 public void transfer(Long fromId, Long toId, BigDecimal amount) { userMapper.decreaseBalance(fromId, amount); userMapper.increaseBalance(toId, amount); } }

8.4 自研框架 vs MyBatis 核心对比

核心特性原生MyBatisCodeStats自研框架
会话管理SqlSessionJdbcTemplate+ 动态代理
事务控制手动commit/rollback + Spring声明式自定义@Transactional注解
Mapper代理JDK动态代理JDK动态代理
连接池适配HikariCP等自研SimpleDataSource
SQL执行StatementHandler处理器直接基于JdbcTemplate

九、核心速查表

知识点核心结论
SqlSession非线程安全,单请求单会话,用完必须close()
一级缓存默认开启、会话级别HashMap,增删改/提交后自动清空
二级缓存手动开启、Mapper全局共享,实体类需实现Serializable
分页机制摒弃RowBounds,生产统一用PageHelper物理分页
Spring集成SqlSessionTemplate代理会话,自动管理生命周期
事务原理基于动态代理 + 注解,实现声明式事务

十、写在最后

MyBatis作为Java生态主流的ORM框架,熟练使用只是基础

掌握底层的会话机制、缓存原理、Spring集成逻辑,才能:

  • ✅ 规避缓存失效、事务异常、连接泄漏等常见问题

  • ✅ 写出更健壮、更高效的数据库访问代码

  • ✅ 在面试中轻松应对底层原理追问

  • ✅ 迈出高级开发工程师的关键一步


💬 互动话题

你在开发中遇到过哪些因为不懂MyBatis底层原理而踩的坑?

  • 一级缓存导致的“数据读不到”?

  • 二级缓存序列化报错?

  • 还是事务不回滚的诡异Bug?

欢迎在评论区分享你的经历,一起交流学习、共同进步!


📌 如果本文对你有帮助,欢迎:

  • 👍点赞支持原创

  • 收藏方便回看

  • 🔁转发给需要的朋友

  • 👀关注CodeStats,陪你深度进阶技术!


CodeStats—— 专注Java底层原理与自研框架实践

http://www.jsqmd.com/news/965361/

相关文章:

  • 从STM32无缝切换到GD32F407:我的RT-Thread BSP移植实战与避坑指南
  • 3个核心技术突破:WebPlotDigitizer图表数据提取完全指南
  • SAP ABAP ALV实战:用DATA_CHANGED函数搞定用户勾选后的实时数据处理(附完整代码)
  • K8s 生产级防御底座:基于 Pod 驱逐策略(Eviction)与资源配额(Quota)防 OOM 故障诊断实战
  • Ansible实战:从零开始用Playbook自动化部署Nginx服务(附完整代码)
  • 终极指南:如何在普通电脑上使用FramePack生成高质量AI视频
  • 揭秘Melodyne的‘黑盒’:它的音频分析算法到底是怎么‘听懂’音乐并修音的?
  • 2026年现阶段南皮地区床板机公司综合实力与选择指南 - 2026年企业资讯
  • 2026年口碑好的防雨毛毡供应商排名,哪家可定制密度? - mypinpai
  • 2026年6月电磁阀线圈生产厂家有哪些,电磁阀线圈/框架式电磁线圈/非包塑电磁阀线圈,电磁阀线圈直销厂家有哪些 - 品牌推荐师
  • 告别漂移!用ArcPy+Python2.7搞定公交GPS轨迹地图匹配(附完整代码)
  • 突破网盘限速壁垒:智能直链下载工具的技术革新与应用实践
  • RadioML 2018.01A数据集详解:24种调制方式与信噪比设置对模型训练的影响
  • Service Mesh 高性能调优:基于 Istio/Envoy Sidecar 内存泄漏定位与 C++ 堆空间排查实战
  • 嵌入式 Linux 驱动底座:中断下半部(Bottom Half)软中断与 Tasklet 异步调度及锁竞争防御
  • 推荐靠谱的便携式红外对射式电子围栏厂家 - mypinpai
  • 2026年Q2西安名酒回收指南:西安上门回收老酒、西安东冬虫夏草回收、西安五粮液回收、西安剑南春回收、西安收老酒选择指南 - 优质品牌商家
  • 为什么分类任务总用交叉熵而不是MSE?从梯度消失和模型收敛速度给你讲明白
  • 2026年运动服饰纱线TOP5盘点:远动袜专用尼龙纱线、锦纶DTY、锦纶染色丝、锦纶色纺丝、锦纶高弹彩色丝、70D140D锦纶高弹丝选择指南 - 优质品牌商家
  • 如何用智能工具3倍提升抖音视频管理效率:douyin-downloader完整指南
  • 用Python爬取A股全量股票代码与名称(附完整代码与数据清洗技巧)
  • 从OD到一线:一个非科班程序员的753天华为生存实录(含可信考试与转正避坑)
  • PHP魔术方法避坑指南:__wakeup、__destruct在CTF与安全审计中的那些“坑”
  • 云原生构建管线加速:Docker 分层构建缓存优化与多构建节点增量提速实战
  • 基于逆变器稳压控制的双向Buck-boost直流微网并网系统仿真研究(Simulink仿真实现)
  • 突破药物研发瓶颈:AutoDock Vina如何让分子对接变得简单高效
  • 当你的AI只认识猫狗:聊聊长尾问题在真实业务里的那些‘坑’与解法
  • 2026年5月西双版纳旅游服务商专业度实测对比:云南旅游/云南旅行社地接/云南旅行社官网/云南旅行社报价/云南本地旅行社/选择指南 - 优质品牌商家
  • 如何通过MAA助手实现明日方舟全自动日常:3步解放双手的智能解决方案
  • 营销场景实战:用CausalML的Uplift Model评估广告投放的增量价值