MyBatis-Spring 整合详解
定位:MyBatis 系列第 6 篇——MyBatis 与 Spring / Spring Boot 的整合机制:会话托管、事务联动、Mapper 注册、多数据源与问题排查 适用版本:MyBatis-Spring 2.x/3.x、mybatis-spring-boot-starter 2.x/3.x、Spring Boot 2.x/3.x
目录
一、整合要解决的问题
1.1 原生模式的三个痛点
回顾 01 篇的原生用法:
try (SqlSession session = factory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.insert(user);
session.commit(); // 手动提交,忘写即丢数据
}
| 会话生命周期 | 手工 open/close,try-with-resources 纪律依赖人 | 泄漏连接、忘记提交 |
| 事务边界 | session 级手动 commit/rollback | 与业务事务边界不一致,部分提交 |
| Mapper 获取 | 手工 getMapper | 无法注入、无依赖管理、重复样板 |
1.2 Spring 整合的答案
| SqlSessionTemplate | 线程安全的 SqlSession 代理,生命周期自动托管 |
| Spring 事务管理器 | 统一事务边界,同事务复用会话与连接 |
| MapperFactoryBean / @MapperScan | Mapper 接口注册为 Spring Bean,直接 @Autowired |
整合后的业务代码形态:
@Service
public class UserService {
@Autowired
private UserMapper userMapper; // 直接注入接口
@Transactional
public void register(User user) {
userMapper.insert(user); // 无 session、无 commit,事务由注解管理
}
}
二、核心组件
2.1 SqlSessionFactoryBean
实现 FactoryBean<SqlSessionFactory> 与 InitializingBean,在容器启动时构建 SqlSessionFactory:
@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource); // 数据源由 Spring 管理(HikariCP/Druid)
bean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/*.xml")); // XML 位置
bean.setTypeAliasesPackage("com.example.entity");
// 与 configLocation 二选一:
org.apache.ibatis.session.Configuration cfg = new org.apache.ibatis.session.Configuration();
cfg.setMapUnderscoreToCamelCase(true);
bean.setConfiguration(cfg);
return bean.getObject();
}
}
关键属性:
| dataSource | 注入 Spring 管理的数据源,mybatis-config.xml 的 environments 不再需要 |
| configLocation | 全局配置文件位置(与 configuration 属性互斥) |
| configuration | 直接传入 Configuration 对象做编程式配置 |
| mapperLocations | XML 映射文件通配符 |
| typeAliasesPackage / typeHandlersPackage | 批量注册别名/TypeHandler |
2.2 SqlSessionTemplate(核心)
定位:SqlSession 接口的线程安全实现,代理模式。作为单例 Bean 可安全注入到任意组件,替代原生 SqlSession 的所有使用场景。
内部机制——SqlSessionInterceptor(简化):
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 获取会话:事务内复用已绑定的,否则新建
SqlSession sqlSession = getSqlSession(sqlSessionFactory, executorType, exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args); // 真实执行
if (!isSqlSessionTransactional(sqlSession, sqlSessionFactory)) {
sqlSession.commit(true); // 2. 非事务:立即提交
}
return result;
} catch (Throwable t) {
// 异常翻译为 Spring 的 DataAccessException
throw translateExceptionIfPossible(t);
} finally {
closeSqlSession(sqlSession, sqlSessionFactory); // 3. 非事务:立即关闭
}
}
}
三个关键性质:
2.3 MapperFactoryBean 与 @MapperScan
MapperFactoryBean<T> 把单个 Mapper 接口注册为 Spring Bean,getObject() 返回 sqlSession.getMapper(mapperInterface) 的代理。
逐个注册不现实,因此用扫描器批量注册 BeanDefinition:
@Configuration
@MapperScan(basePackages = "com.example.mapper")
public class MapperConfig {
}
| @MapperScan | 注解式(mybatis-spring 提供),支持多包、annotationClass/markerInterface 过滤、sqlSessionFactoryRef 指定工厂 |
| MapperScannerConfigurer | BeanDefinition 方式,等价能力,用于 XML 装配时代 |
| @Mapper | 标注在接口上,配合 starter 的自动扫描(逐个标注,易遗漏) |
三、会话与事务联动
3.1 会话获取策略
SqlSessionUtils.getSqlSession 的决策逻辑:
是否存在活跃的 Spring 事务同步(TransactionSynchronizationManager)?
├── 有
│ ├── 该事务已绑定 SqlSession → 直接复用(引用计数)
│ └── 未绑定 → 新建 SqlSession,注册到事务同步
│ (事务提交/回滚时统一 commit/rollback + close)
└── 无
└── 每次新建,方法调用结束后 commit(true) + close
3.2 事务内行为
@Transactional
public void placeOrder(Order order) {
orderMapper.insert(order); // 会话S
orderItemMapper.batchInsert(order.getItems()); // 复用会话S
accountMapper.decrease(order.getUserId(), order.getAmount()); // 复用会话S
// 三次调用共享同一 SqlSession、同一 Connection
// 任一抛异常 → Spring 回滚整个事务
}
联动要点:
3.3 无事务模式的行为
每次 Mapper 调用:新建 SqlSession → 执行 → commit → close。调用之间不共享会话,一级缓存基本不命中(04 篇结论)。只读查询无副作用;连续写操作各自提交,需要原子性时必须加 @Transactional。
四、Spring Boot Starter 整合
4.1 依赖与版本匹配
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
| 3.x(Jakarta EE 命名空间) | 3.x | javax → jakarta 类迁移 |
| 2.x | 2.x | javax 命名空间 |
版本错配的典型报错:ClassNotFoundException: javax.servlet… 或自动装配条件不满足——Boot 3 项目误引 starter 2.x 是最常见的版本事故。
4.2 自动装配
MybatisAutoConfiguration 的三件事:
4.3 配置项
mybatis:
mapper-locations: classpath:mapper/**/*.xml # XML 位置,支持通配符
type-aliases-package: com.example.entity
# config-location 与 configuration.* 互斥,二选一
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
cache-enabled: false # 生产建议关闭二级缓存(04篇)
executor-type: SIMPLE # 全局 Executor 类型
Mapper 注册推荐 @MapperScan(显式可控),也可逐接口 @Mapper。
4.4 最小整合示例结构
src/main/java/com/example
├── Application.java @SpringBootApplication + @MapperScan("com.example.mapper")
├── entity/User.java
├── mapper/UserMapper.java 接口 + 手写方法
└── service/UserService.java @Transactional 业务
src/main/resources
├── application.yml
└── mapper/UserMapper.xml namespace=接口全限定名
五、多数据源整合
5.1 三件套独立配置
每组数据源独立的一套:DataSource + SqlSessionFactory + TransactionManager。
@Configuration
@MapperScan(basePackages = "com.example.mapper.primary",
sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDsConfig {
@Bean
@ConfigurationProperties("spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory primarySqlSessionFactory(
@Qualifier("primaryDataSource") DataSource ds) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(ds);
bean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/primary/*.xml"));
return bean.getObject();
}
@Bean
public DataSourceTransactionManager primaryTransactionManager(
@Qualifier("primaryDataSource") DataSource ds) {
return new DataSourceTransactionManager(ds);
}
}
order 库同构再配一套(com.example.mapper.order + orderSqlSessionFactory)。
5.2 隔离原则
六、常见整合问题排查
6.1 Invalid bound statement (not found)
最高频的整合错误,按顺序排查:
| XML 是否被打进包 | Maven resources 过滤配置;target/classes/mapper/ 下有无文件 |
| mapper-locations | 通配符是否覆盖实际路径(classpath: vs classpath*:,子目录层级) |
| namespace | 是否等于接口全限定名(含包名,大小写敏感) |
| statement id | 是否与方法名完全一致 |
| 扫描范围 | @MapperScan 是否覆盖接口所在包 |
6.2 Mapper Bean 注入失败
- 未加 @MapperScan 且接口未标 @Mapper;
- 接口在扫描包之外;
- 多模块项目中接口所在模块未被扫描配置覆盖。
6.3 事务不生效
| 方法非 public | Spring AOP 代理默认只拦截 public 方法 |
| 同类自调用 | this.xxx() 绕过代理,事务注解失效 |
| 异常被吞 | catch 后未抛出,事务管理器感知不到失败 |
| 检查型异常 | 默认只回滚运行时异常,需 rollbackFor = Exception.class |
6.4 版本不兼容
Boot 3 + starter 2.x:jakarta/javax 命名空间冲突、自动装配条件不满足。对策:升级到对应大版本的 starter,不要试图排除依赖硬凑。
七、总结
八、常见高频面试题
1. SqlSession 是线程安全的吗?SqlSessionTemplate 呢?
要点:原生 SqlSession 非线程安全(持有 Executor、一级缓存等会话状态),只能方法内局部使用。SqlSessionTemplate 是线程安全的:它是 SqlSession 的代理,自身无状态,每次方法调用通过 SqlSessionInterceptor 临时获取会话(事务内复用、无事务新建),执行完非事务场景立即提交关闭,因此可作为单例 Bean 共享。
2. MyBatis 如何与 Spring 事务整合?
要点:SpringManagedTransaction 实现 MyBatis 的 Transaction 接口,通过 DataSourceUtils 获取连接从而接入 Spring 事务同步。@Transactional 开启事务后,SqlSessionUtils 把 SqlSession 绑定到当前事务,同事务内多次 Mapper 调用复用同一会话与连接,提交/回滚由 Spring 事务管理器统一驱动。
3. @MapperScan 的原理?
要点:通过 Import 注册 MapperScannerRegistrar,在 BeanDefinition 阶段扫描 basePackages 下所有接口,为每个接口注册 MapperFactoryBean 定义;容器实例化时 MapperFactoryBean.getObject 返回该接口的 MyBatis 动态代理。还支持 annotationClass/markerInterface 过滤与 sqlSessionFactoryRef 指定工厂。
4. 为什么同一个 @Transactional 方法里多次查询可能命中一级缓存,两次独立的 Service 调用不会?
要点:事务内多次调用被事务同步机制绑定到同一 SqlSession,共享一级缓存;无事务时 SqlSessionTemplate 每次调用新建会话并立即关闭,会话间不共享缓存。
5. Invalid bound statement (not found) 如何排查?
要点:按序检查——XML 是否被打进包(Maven resources);mapper-locations 通配符是否匹配实际路径;namespace 是否等于接口全限定名;id 是否与方法名一致;@MapperScan 是否覆盖接口所在包。
6. Spring Boot 下 MyBatis 的自动装配做了什么?
要点:MybatisAutoConfiguration 在存在 DataSource 时自动创建 SqlSessionFactory(应用 mybatis.* 配置)、单例 SqlSessionTemplate,并自动注册 @Mapper 标注的接口。开发者只需提供数据源与 Mapper 定义。
7. 多数据源场景如何整合 MyBatis?
要点:每个数据源独立配置 DataSource、SqlSessionFactory、TransactionManager;@MapperScan 按包分隔并指定 sqlSessionFactoryRef;事务注解指定对应 transactionManager。跨库原子性需要 Seata 等分布式事务方案。
8. MyBatis 的异常在 Spring 中是什么类型?
要点:SqlSessionTemplate 会把 MyBatis 的 PersistenceException 等翻译为 Spring 的 DataAccessException 体系(如 DuplicateKeyException),使持久层异常与 Spring 生态一致,上层可统一捕获处理。



