SqlSessionTemplate 通过动态代理+ThreadLocal绑定事务内SqlSession实现线程安全与事务一致,依赖TransactionSynchronizationManager管理生命周期,避免手动获取原生session导致事务不同步或连接泄漏。

SqlSessionTemplate 怎么保证线程安全又不破坏事务
它不是直接包装 SqlSessionFactory 创建新 SqlSession,而是用动态代理把所有方法调用转给一个“当前线程绑定的” SqlSession。这个 session 由 Spring 的 TransactionSynchronizationManager 管理——事务开始时绑定,提交或回滚后清理。
关键点在于:它自己不持有真实 SqlSession 实例,每次调用都从 Spring 的事务上下文中取;没事务时才临时创建非托管 session(但不推荐裸用)。
- 如果在 service 层没加
@Transactional,SqlSessionTemplate会 fallback 到非事务模式,执行完立刻 close,无法复用连接 - 多个 DAO 共享同一个
SqlSessionTemplate实例完全安全,因为底层 session 是按事务线程隔离的 - 别手动调用
sqlSessionTemplate.getSqlSession()拿原生 session——这会绕过代理逻辑,导致事务不同步、连接泄漏
为什么不能用 SqlSessionUtils.openSession() 替代 SqlSessionTemplate
SqlSessionUtils.openSession() 返回的是原始 SqlSession,它不会自动注册到 Spring 事务同步器里。哪怕你在 @Transactional 方法里这么写,Spring 也感知不到这个 session,事务提交时不会 flush 或 commit 它。
常见错误现象:insert 执行成功但数据库没数据,或者抛出 SqlSession is not synchronized with Transaction 类似提示。
- 只有
SqlSessionTemplate和SqlSessionDaoSupport子类能真正接入 Spring 事务生命周期 -
openSession()适合测试或脱离 Spring 环境的场景,生产代码中混用极易导致脏读或连接未释放 - MyBatis-Spring 3.x 后已标记
SqlSessionUtils.openSession()为 @Deprecated,明确不鼓励使用
Mapper 接口背后怎么和 SqlSessionTemplate 绑定的
Spring 在扫描 @Mapper 接口时,实际注入的是一个 JDK 动态代理对象。这个代理不持 SqlSession,而是每次调用方法时,通过 MapperMethod 解析 SQL,再委托给容器里的 SqlSessionTemplate 执行。
所以你看到的 userMapper.insert(user),本质是 sqlSessionTemplate.insert("com.example.UserMapper.insert", user)。
- Mapper 接口必须由 Spring 的
MapperScannerConfigurer或@MapperScan注入,手写new UserMapperImpl()会丢失事务上下文 - 同一个 Mapper 接口多次注入(比如用
@Primary+ 备份 bean),只要底层共用同一个SqlSessionTemplate,事务就仍然有效 - 若配置了多个
SqlSessionFactory,必须确保每个MapperScannerConfigurer明确指定对应的sqlSessionFactoryBeanName,否则可能绑定错 session 工厂
事务传播行为下 SqlSessionTemplate 会怎样表现
它本身不处理传播逻辑,全靠 Spring 的 TransactionSynchronizationManager。比如外层 @Transactional(propagation = Propagation.REQUIRED) 开启事务后,内层方法即使也加了 @Transactional,只要传播类型兼容,依然复用同一个线程绑定的 SqlSession。
但如果内层用了 REQUIRES_NEW,Spring 会 suspend 当前 session 并新建一个,此时 SqlSessionTemplate 会自动切换到新的绑定实例。
-
NESTED行为依赖数据库支持 savepoint,SqlSessionTemplate会配合SqlSession的savepoint方法做回滚,但 MyBatis 本身不管理 savepoint 名称,全交给 JDBC 驱动 - 如果在
REQUIRES_NEW方法里手动调用sqlSessionTemplate.clearCache(),只清空当前新 session 的缓存,不影响外层 - 跨线程(如
@Async)必然丢失事务绑定,此时SqlSessionTemplate只能走非事务分支——这是最容易被忽略的隐性断点










