5.5 与 Spring 集成 本节摘要:集成之后,代码里再也不会出现 openSession——SqlSessionFactoryBean 负责建厂,@MapperScan 负责把 Mapper 接口注册成 Bean,SqlSessionTemplate 用代理把"每次操作借一个新会话"做成默认行为,事务边界则交给 @Transactional 声明。本节走完集成四步,讲清线程安全的来龙去脉与事务易失效的场景。 监护设备清单的最后一件:把整张手术台搬进 Spring 病房,交给容器统一调度。从这一节起,1.4 里"try-with-resources 开会话、finally 关会话"的纪律成为历史——容器替你管。
本节摘要:集成之后,代码里再也不会出现 openSession——SqlSessionFactoryBean 负责建厂,@MapperScan 负责把 Mapper 接口注册成 Bean,SqlSessionTemplate 用代理把"每次操作借一个新会话"做成默认行为,事务边界则交给 @Transactional 声明。本节走完集成四步,讲清线程安全的来龙去脉与事务易失效的场景。
监护设备清单的最后一件:把整张手术台搬进 Spring 病房,交给容器统一调度。从这一节起,1.4 里"try-with-resources 开会话、finally 关会话"的纪律成为历史——容器替你管。
引入 mybatis-spring 依赖后,配置就四件事:
@Configuration @MapperScan("com.example.mapper") // 3. 扫描 Mapper 接口注册成 Bean @EnableTransactionManagement // 4. 开启声明式事务 public class AppConfig { @Bean public DataSource dataSource() { // 1. 数据源:连接池交给它管 HikariDataSource ds = new HikariDataSource(); ds.setJdbcUrl("jdbc:mysql://localhost:3306/shop"); ds.setUsername("dev"); ds.setPassword("secret"); return ds; } @Bean public SqlSessionFactory sqlSessionFactory(DataSource ds) throws Exception { SqlSessionFactoryBean fb = new SqlSessionFactoryBean(); fb.setDataSource(ds); // 2. 建厂:数据源 + XML 位置 fb.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath:mapper/*.xml")); return fb.getObject(); } }
之后 Service 层直接注入 Mapper 接口用,仿佛它天生就是个 Bean:
@Service public class UserService { @Autowired private UserMapper userMapper; // 不是接口的实例,是代理 public User getUser(Long id) { return userMapper.selectByPrimaryKey(id); // 没有 openSession } }

1.4 讲过:SqlSession 不是线程安全的,一个会话只该被一个线程用。那共享的单例 Template 怎么敢被并发调用?答案是它根本不持有会话——每个方法调用进来,Template 的代理都会从 SqlSessionFactory 借一个新会话执行,用完即还。你拿到的"同一个" Template,内部每次都在用不同的会话。
两个推论直接影响日常写法:
容器环境下提交回滚的时机不由你的代码决定,而由事务边界决定——@Transactional 标在哪个方法,那个方法就是一次"要么都做要么都不做":
@Service public class OrderService { @Autowired private OrderMapper orderMapper; @Autowired private AccountMapper accountMapper; @Transactional // 两步更新同生共死,细节留给 6.1 public void placeOrder(Order order) { accountMapper.deduct(order.getAccountId(), order.getAmount()); orderMapper.insert(order); } // 正常返回自动 commit;抛 RuntimeException 自动 rollback }
配套组件是 DataSourceTransactionManager:它拦截方法入口,把后续借到的连接绑定到当前线程,方法结束时统一 commit 或 rollback。6.1 会把"为什么 autoCommit 不行、事务为什么会失效"讲透。
⚠️ 常见坑:@Transactional 默认只回滚 RuntimeException 与 Error。捕获了异常却没重新抛出、或抛的是受检异常,事务照常提交——"明明报错了数据却写进去了"多半是这个原因。需要时用 @Transactional(rollbackFor = Exception.class) 放宽。
💡 关键直觉:集成 Spring 后责任重新分工——连接归连接池、会话归 Template、事务归声明式注解,你只写"做什么",不写"怎么开怎么关"。凡是手写 openSession 的新代码出现在 Spring 项目里,都值得先怀疑一下是不是走错了层。
至此监护设备全部就位,第 5 章收工。下一章进复盘室:从事故止损讲起——事务管理从手动提交到声明式的完整纪律。