本文将从以下几个方面分析 Spring 是如何整合 MyBatis(mybatis-spring-2.0.2)。

  • MyBatis API:通过 SqlSessionFactory 获取 SqlSession。
  • SqlSession 整合:注入 SqlSessionFactoryBean 和 SqlSessionTemplate。
  • @Mapper 整合:@MapperScan 会注册 MapperScannerConfigurer 后置处理器。当容器启动时,处理 @Mapper 注解,使用 MapperFactoryBean 生成代理类。
  • 事务整合:SpringManagedTransaction 直接从 DataSourceUtils.getConnection(this.dataSource) 中获取连接。

    1. 背景介绍

    MyBatis 提供了二种使用方式:xml 调用和 mapper 接口调用。使用方式如下:

    1. Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
    2. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    3. SqlSession sqlSession = sqlSessionFactory.openSession();
    4. try {
    5. // 1. xml 调用
    6. User user = sqlSession.selectOne("getUser", 1);
    7. // 2. Mapper 接口调用
    8. UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    9. userMapper.getUser();
    10. } finally {
    11. sqlSession.close();
    12. }

    说明:这两种方式都需要通过 SqlSessionFactory 工厂类获取 SqlSession。所以,Spring 整合 MyBatis 的第一件事就是将配置文件解析成 org.apache.ibatis.session.Configuration,并组装 SqlSessionFactory,这就是 SqlSessionFactoryBean 的功能。下面我们先看一下,Spring 整合 MyBatis 的代码,再分析其使用其原理。

    1. @Configuration
    2. @MapperScan(basePackages = "com.binarylei.spring.mybatis.mapper",
    3. annotationClass = Mapper.class)
    4. @ComponentScan(basePackages = "com.binarylei.spring.mybatis.service")
    5. @EnableTransactionManagement
    6. public class MyBatisConfig {
    7. @Bean
    8. public SqlSessionFactoryBean sqlSessionFactoryBean() {
    9. SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
    10. sqlSessionFactoryBean.setDataSource(dataSource());
    11. return sqlSessionFactoryBean;
    12. }
    13. @Bean
    14. public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    15. return new SqlSessionTemplate(sqlSessionFactory);
    16. }
    17. @Bean
    18. public PlatformTransactionManager platformTransactionManager() {
    19. return new DataSourceTransactionManager(dataSource());
    20. }
    21. @Bean
    22. public DataSource dataSource() {
    23. String url = "jdbc:mysql://118.190.54.16:3306/sys?useUnicode=true&characterEncoding=UTF-8";
    24. String username = "root";
    25. String password = "123456";
    26. return new DriverManagerDataSource(url, username, password);
    27. }
    28. }

    2. Spring 整合 MyBatis

    2.1 SqlSession 整合

    这个比较简单,关键是 SqlSessionFactoryBean 和 SqlSessionTemplate。

    2.2 @Mapper 整合

    @Mapper 的处理其实也很简单,无非是在容器启动时,根据 @Mapper 注解注入 MapperFactoryBean。这个 FactoryBean#getObject 实际上也是调用 SqlSession#getMapper(mapperInterface) 获取代理对象。 spring-mybatis - 图1说明:MapperScannerConfigurer 是最核心的处理类。它会扫描 @Mapper 注解,并将接口替换成 MapperFactoryBean 注册到容器中,最终还是调用 SqlSession#getMapper 获取代理对象。

  • MapperScannerConfigurer:实现了 BeanDefinitionRegistryPostProcessor 后置处理器接口。在容器启动时,回调 postProcessBeanDefinitionRegistry 方法时处理 @Mapper 注解。

  • ClassPathMapperScanner:继承自 ClassPathBeanDefinitionScanner 类,扫描 @Mapper 注解。最终调用 processBeanDefinitions 处理扫描后的 @Mapper 注解。
  • MapperFactoryBean:将标注有 @Mapper 注解的接口替换成 MapperFactoryBean 注册到容器中。最终调用 getObject 时调用 getSqlSession().getMapper(this.mapperInterface) 获取代理对象。
    1. @Override
    2. public T getObject() throws Exception {
    3. return getSqlSession().getMapper(this.mapperInterface);
    4. }

    2.3 事务整合

    关于事务可以先看我之前《Spring 整合 JdbcTemplate 事务》。问题的关键在于,Spring 事务使用的连接保存在 TransactionSynchronizationManager.resources 变量中。
    1. private static final ThreadLocal<Map<Object, Object>> resources =
    2. new NamedThreadLocal<>("Transactional resources");
    说明:其中 resources 保存的类型为 ,通过 DataSourceUtils#getConnection(DataSource) 方法会查找这个 ThreadLocal 中的连接。那问题就变成:我们需要SqlSession 获取连接时调用 DataSourceUtils#getConnection 方法。这样,MyBatis 就可以将事务委托给 Spring 管理了。
    MyBatis 获取连接时,会调用其 Transaction#getConnection 方法。所以,我们只需要自己实现一个 SpringManagedTransaction 重写这个方法即可。再将这个 Transaction 配置到 MyBatis 的配置中即可。
    1. // SpringManagedTransaction重写获取连接的方法
    2. @Override
    3. public Connection getConnection() throws SQLException {
    4. if (this.connection == null) {
    5. openConnection();
    6. }
    7. return this.connection;
    8. }
    9. private void openConnection() throws SQLException {
    10. this.connection = DataSourceUtils.getConnection(this.dataSource);
    11. this.autoCommit = this.connection.getAutoCommit();
    12. this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
    13. }
    SqlSessionFactoryBean 整合时默认的事务工厂类就是 SpringManagedTransactionFactory。换句话说,SqlSession 会调用 DataSourceUtils#getConnection 获取连接,将连接交给 Spring 事务管理。
    1. protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    2. final Configuration targetConfiguration;
    3. ...
    4. targetConfiguration.setEnvironment(new Environment(this.environment,
    5. this.transactionFactory == null ? new SpringManagedTransactionFactory() :
    6. this.transactionFactory, this.dataSource));
    7. }