本文将从以下几个方面分析 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 接口调用。使用方式如下:
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
// 1. xml 调用
User user = sqlSession.selectOne("getUser", 1);
// 2. Mapper 接口调用
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.getUser();
} finally {
sqlSession.close();
}
说明:这两种方式都需要通过 SqlSessionFactory 工厂类获取 SqlSession。所以,Spring 整合 MyBatis 的第一件事就是将配置文件解析成 org.apache.ibatis.session.Configuration,并组装 SqlSessionFactory,这就是 SqlSessionFactoryBean 的功能。下面我们先看一下,Spring 整合 MyBatis 的代码,再分析其使用其原理。
@Configuration
@MapperScan(basePackages = "com.binarylei.spring.mybatis.mapper",
annotationClass = Mapper.class)
@ComponentScan(basePackages = "com.binarylei.spring.mybatis.service")
@EnableTransactionManagement
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean() {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
return sqlSessionFactoryBean;
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean
public PlatformTransactionManager platformTransactionManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean
public DataSource dataSource() {
String url = "jdbc:mysql://118.190.54.16:3306/sys?useUnicode=true&characterEncoding=UTF-8";
String username = "root";
String password = "123456";
return new DriverManagerDataSource(url, username, password);
}
}
2. Spring 整合 MyBatis
2.1 SqlSession 整合
这个比较简单,关键是 SqlSessionFactoryBean 和 SqlSessionTemplate。
2.2 @Mapper 整合
@Mapper 的处理其实也很简单,无非是在容器启动时,根据 @Mapper 注解注入 MapperFactoryBean。这个 FactoryBean#getObject 实际上也是调用 SqlSession#getMapper(mapperInterface) 获取代理对象。
说明:MapperScannerConfigurer 是最核心的处理类。它会扫描 @Mapper 注解,并将接口替换成 MapperFactoryBean 注册到容器中,最终还是调用 SqlSession#getMapper 获取代理对象。
MapperScannerConfigurer:实现了 BeanDefinitionRegistryPostProcessor 后置处理器接口。在容器启动时,回调 postProcessBeanDefinitionRegistry 方法时处理 @Mapper 注解。
- ClassPathMapperScanner:继承自 ClassPathBeanDefinitionScanner 类,扫描 @Mapper 注解。最终调用 processBeanDefinitions 处理扫描后的 @Mapper 注解。
- MapperFactoryBean:将标注有 @Mapper 注解的接口替换成 MapperFactoryBean 注册到容器中。最终调用 getObject 时调用 getSqlSession().getMapper(this.mapperInterface) 获取代理对象。
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
2.3 事务整合
关于事务可以先看我之前《Spring 整合 JdbcTemplate 事务》。问题的关键在于,Spring 事务使用的连接保存在 TransactionSynchronizationManager.resources 变量中。
说明:其中 resources 保存的类型为private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
,通过 DataSourceUtils#getConnection(DataSource) 方法会查找这个 ThreadLocal 中的连接。那问题就变成:我们需要SqlSession 获取连接时调用 DataSourceUtils#getConnection 方法。这样,MyBatis 就可以将事务委托给 Spring 管理了。
MyBatis 获取连接时,会调用其 Transaction#getConnection 方法。所以,我们只需要自己实现一个 SpringManagedTransaction 重写这个方法即可。再将这个 Transaction 配置到 MyBatis 的配置中即可。
SqlSessionFactoryBean 整合时默认的事务工厂类就是 SpringManagedTransactionFactory。换句话说,SqlSession 会调用 DataSourceUtils#getConnection 获取连接,将连接交给 Spring 事务管理。// SpringManagedTransaction重写获取连接的方法
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
}
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
...
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() :
this.transactionFactory, this.dataSource));
}