sqlsessiontemplate是mybatis-spring的核心代理类,线程安全、可多dao共享,通过threadlocal动态绑定当前线程/事务专属sqlsession,自动管理生命周期与异常翻译。

MyBatis 中 SqlSession 本身**不是线程安全的**,不能在多线程间共享使用;但 Spring 整合后通过 SqlSessionTemplate 实现了线程安全的 SQL 执行。关键不在于“让 SqlSession 变线程安全”,而在于“每次执行都获取与当前线程/事务绑定的独有 SqlSession”。
SqlSession 的原始执行方式(非 Spring 环境)
手动创建时,需严格控制生命周期:
- 调用
sqlSessionFactory.openSession()获取新实例 —— 每次请求都应新建一个 - 用完必须显式调用
sqlSession.close(),否则连接泄漏、一级缓存混乱 - 执行 SQL 有两种写法:
• 直接传入 statement ID:sqlSession.selectOne("com.example.UserMapper.findById", 1)
• 或先获取 Mapper 接口代理:UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.findById(1) - 所有操作(查询、更新、事务)都在该会话内完成,缓存和事务状态仅对本次会话有效
Spring 环境下真正线程安全的执行机制
Spring 不直接暴露或复用 SqlSession,而是靠 SqlSessionTemplate 封装调度:
批量分析录音转写,输出多维度拓客报告。触发词:录音分析、总结、音频总结、拜访记录总结。当用户提及「分析录音」「看看录音数据」「最近的录音」「通话记录」且意图为批量统计/分析时触发。仅出现「录音」或「拜访」时需结合上下文,若仅查看单条详情则不触发。
- SqlSessionTemplate 是单例 Bean,但它内部不持有 SqlSession 实例
- 每次调用它的方法(如
selectOne或getMapper().findById()),都会通过SqlSessionUtils动态查找或创建 SqlSession - 查找逻辑基于 ThreadLocal:
• 若当前线程已绑定 SqlSession(比如在 @Transactional 方法中),就复用它
• 若无绑定,且处于事务中,则创建新 SqlSession 并绑定到当前线程
• 若不在事务中,也会创建临时会话,但执行完立即关闭(此时一级缓存无效) - 事务提交/回滚/异常时,由 Spring 自动触发 session 清理、连接归还、ThreadLocal 解绑
为什么 SqlSessionTemplate 能被多个 DAO 共享而不冲突
因为它本质是代理对象,不是会话容器:
- 它实现了 SqlSession 接口,但所有方法都委托给“按需获取的 SqlSession”执行
- 不同线程调用同一个 SqlSessionTemplate 实例,底层拿到的是各自线程隔离的 SqlSession
- Mapper 接口注入时(如
@Autowired UserMapper mapper),实际得到的是 SqlSessionTemplate 包装的动态代理,每次调用都走上述线程感知流程 - 无需开发者管理 close、commit 或线程绑定,全部由 Spring 事务上下文接管
不安全的典型误用场景
这些做法会破坏一致性或引发并发问题:
- 将 DefaultSqlSession 注入为 Spring Bean(prototype 也不推荐,易遗漏 close)
- 在 service 层缓存 SqlSession 或 Mapper 实例跨方法复用
- 在异步线程(如 @Async)中直接使用主线程的 Mapper,导致找不到事务绑定的 SqlSession
- 手动开启事务但忘记配置 @Transactional,使每次 Mapper 调用都新建并立刻关闭 SqlSession
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










