欢迎光临
我们一直在努力

MyBatis-Spring整合详解

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


目录

  • 整合要解决的问题
  • 核心组件
  • 会话与事务联动
  • Spring Boot Starter 整合
  • 多数据源整合
  • 常见整合问题排查
  • 总结
  • 常见高频面试题

  • 一、整合要解决的问题

    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. 非事务:立即关闭
    }
    }
    }

    三个关键性质:

  • 线程安全:SqlSessionTemplate 本身无可变状态,会话是按调用临时获取的,因此可以单例共享;真正非线程安全的是它内部临时取出的 SqlSession。
  • 异常翻译:PersistenceException(含 BindingException、DataAccessException 相关)统一转为 Spring 的 DataAccessException 体系,持久层异常与 Spring 生态一致。
  • 事务感知:是否 commit/close 取决于会话是否处于 Spring 事务同步中(见第三节)。
  • 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 回滚整个事务
    }

    联动要点:

  • 同事务共享会话:多次 Mapper 调用复用同一 SqlSession 与连接,一级缓存在事务内生效(04 篇 2.5 节)。
  • 写操作清缓存:事务内任意 C/U/D 清空该会话一级缓存,防止读到事务内过期数据。
  • 连接桥接:SpringManagedTransaction 实现 MyBatis 的 Transaction 接口,从 DataSourceUtils 获取连接——正是这个类把 MyBatis 的连接获取接入了 Spring 事务体系。
  • 编程式事务(TransactionTemplate)同样生效,原理相同。
  • 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>

    Spring Bootstarter原因
    3.x(Jakarta EE 命名空间) 3.x javax → jakarta 类迁移
    2.x 2.x javax 命名空间

    版本错配的典型报错:ClassNotFoundException: javax.servlet… 或自动装配条件不满足——Boot 3 项目误引 starter 2.x 是最常见的版本事故。

    4.2 自动装配

    MybatisAutoConfiguration 的三件事:

  • 容器中存在 DataSource Bean 时,自动创建 SqlSessionFactory(收集 mapperLocations、configuration 等配置项);
  • 自动创建单例 SqlSessionTemplate;
  • AutoConfiguredMapperScannerRegistrar 自动扫描标注 @Mapper 的接口注册为 Bean。
  • 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 隔离原则

  • 包隔离:不同数据源的 Mapper 放不同包,@MapperScan 各扫各的。
  • 工厂引用:sqlSessionFactoryRef 显式指定归属,防止 Mapper 绑错工厂。
  • 事务指定:@Transactional(transactionManager = "orderTransactionManager")。
  • 跨库强一致:本地事务无法覆盖跨库原子性,需 Seata 等分布式事务方案(属系统架构知识库分布式主题范畴)。

  • 六、常见整合问题排查

    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,不要试图排除依赖硬凑。


    七、总结

  • 整合价值:SqlSessionTemplate 托管会话生命周期(线程安全单例),Spring 事务统一提交边界,@MapperScan 让 Mapper 成为可注入的 Bean——业务代码只剩接口注入与 @Transactional。
  • SqlSessionTemplate 原理:内部 SqlSessionInterceptor 每次调用经 SqlSessionUtils 获取会话(事务内复用、无事务新建),非事务场景调用后即 commit + close,并把 MyBatis 异常翻译为 Spring DataAccessException。
  • 事务联动:SpringManagedTransaction 桥接连接获取;同事务多次 Mapper 调用共享 SqlSession 与连接,一级缓存事务内生效、写操作后清空。
  • Starter 整合:自动装配 SqlSessionFactory + Template + @Mapper 扫描;配置集中在 mybatis.* 前缀;Boot 3 必须用 starter 3.x。
  • 多数据源:DataSource/Factory/TransactionManager 三件套成组配置,Mapper 按包隔离并用 sqlSessionFactoryRef 绑定归属;跨库强一致交给分布式事务。
  • 排查三板斧:Invalid bound statement 查 XML 路径/namespace/id;注入失败查扫描范围;事务失效查 public/自调用/异常处理。

  • 八、常见高频面试题

    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 生态一致,上层可统一捕获处理。

    在这里插入图片描述

    赞(0)
    未经允许不得转载:171主机测评 » MyBatis-Spring整合详解
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址