前言
通常情况下,我们并不会单独使用MyBatis框架,而是与Spring、SpringBoot等框架进行整合。
MyBatis框架与Spring框架整合需要使用mybatis-spring模块。整合后,MyBatis中的Mapper动态代理对象会作为Spring框架的Bean注册到Spring容器中。
第11章 MyBatis整合Spring框架
11.1 Mapper动态代理对象注册过程
首先引入mybatis-spring模块:
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.3</version>
</dependency>
该模块提供了一个@MapperScan注解,用于扫描指定包中的Mapper接口并创建Mapper动态代理对象。
源码1:org.mybatis.spring.annotation.MapperScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
// 扫描包路径
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
// 扫描Mapper接口之后得到的Class对象
Class<?>[] basePackageClasses() default {};
// ......
}
由 源码1 可知,@MapperScan注解通过@Import注解导入了一个MapperScannerRegistrar对象。
源码2:org.mybatis.spring.annotation.MapperScannerRegistrar
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
// 调用重载的registerBeanDefinitions()方法注册BeanDefinition对象
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
// 创建BeanDefinitionBuilder对象(BeanDefinition构建器)
// 参数是MapperScannerConfigurer的Class对象,意思是要构建MapperScannerConfigurer对象的对应的BeanDefinition
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
// 处理annotationClass属性
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
builder.addPropertyValue("annotationClass", annotationClass);
}
// 处理markerInterface、nameGenerator、factoryBean、sqlSessionTemplateRef、sqlSessionFactoryRef属性 ......
// 处理value、basePackages、basePackageClasses属性,合并三者
List<String> basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
if (basePackages.isEmpty()) {
basePackages.add(getDefaultBasePackage(annoMeta));
}
// 处理lazyInitialization属性 ......
// 注册BeanDefinition
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
}
由 源码2 可知,MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口。而ImportBeanDefinitionRegistrar用于在Spring解析Bean的配置阶段向BeanDefinitionRegistrar容器注册额外的BeanDefinition对象。
MapperScannerRegistrar类重写了ImportBeanDefinitionRegistrar类的registerBeanDefinitions()
方法,最终调用重载的registerBeanDefinitions()
方法。
重载的registerBeanDefinitions()
方法创建了一个BeanDefinitionBuilder对象,并以MapperScannerConfigurer的Class对象为参数,意思是要构建MapperScannerConfigurer对象的对应的BeanDefinition。
接下来依次读取@MapperScan注解的annotationClass、markerInterface等各个属性。值得注意的是,value、basePackages、basePackageClasses三个属性的值最终会被合并到一起。
最后,调用BeanDefinitionBuilder对象的registerBeanDefinition()
方法注册MapperScannerConfigurer对象的对应的BeanDefinition。
源码3:org.mybatis.spring.mapper.MapperScannerConfigurer
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// ...
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
// ......
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
}
由 源码3 可知,在MapperScannerConfigurer类中,有一个后置处理方法postProcessBeanDefinitionRegistry()
,它会在MapperScannerConfigurer对象对应的BeanDefinition注册完成后被调用。
在该后置方法中,创建了一个ClassPathMapperScanner对象,该对象是ClassPathBeanDefinitionScanner的子类,可以通过调用ClassPathBeanDefinitionScanner的scan()
方法扫描特定包下的Mapper接口,将Mapper接口信息转换为对应的BeanDefinition对象。
源码4:org.springframework.context.annotation.ClassPathBeanDefinitionScanner
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 执行扫描动作
doScan(basePackages);
// ......
}
由 源码4 可知,ClassPathBeanDefinitionScanner对象的scan()
方法会转调子类的doScan()
方法。
源码5:org.mybatis.spring.mapper.ClassPathMapperScanner
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 调用父类的doScan()方法获取指定目录下的Mapper接口对应的BeanDefinitionHolder对象
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
if (printWarnLogIfNotFoundMappers) {
// LOGGER ...
}
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
AbstractBeanDefinition definition;
BeanDefinitionRegistry registry = getRegistry();
// 遍历BeanDefinition信息
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (AbstractBeanDefinition) holder.getBeanDefinition();
// ......
// 将BeanDefinition对象的beanClass属性设置为MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBeanClass);
// ......
// 将SqlSessionFactory注入MapperFactoryBean
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
// ......
definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
// ......
}
}
由 源码5 可知,在ClassPathMapperScanner类的doScan()
方法中,首先会调用父类的doScan()
方法获取指定目录下的Mapper接口对应的BeanDefinitionHolder对象,而该对象持有一个BeanDefinition对象及Bean的名称和所有别名。
接着调用processBeanDefinitions()
方法,对所有BeanDefinitionHolder对象进行遍历处理。在遍历逻辑中,先获取BeanDefinitionHolder对象所持有的BeanDefinition对象,再将BeanDefinition对象的baenClass属性设置为MapperFactoryBean,并增加几个PropertyValue对象。
将BeanDefinition对象的baenClass属性设置为MapperFactoryBean这一步很重要,当Spring根据BeanDefinition对象实例化Mapper接口对象时,创建的是MapperFactoryBean对象。
源码6:org.mybatis.spring.mapper.MapperFactoryBean
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
}
由 源码6 可知,MapperFactoryBean实现了FactoryBean接口,可以通过其getObject()
方法获得一个实例对象。
在getObject()
方法中,调用SqlSession对象的getMapper()
方法返回一个Mapper对象。
那这个Mapper对象是动态代理对象吗?
答案是肯定的。在【MyBatis3源码深度解析(十五)SqlSession的创建与执行(二)Mapper接口和XML配置文件的注册与获取 5.7 Mapper接口的动态代理对象的获取】中就已学习过,调用SqlSession对象的getMapper()
方法获得的是一个Mapper接口的动态代理对象。
至此,Mapper动态代理对象注册到Spring完毕。
11.2 MyBatis整合Spring事务管理
Spring框架提供了比较完善的事务管理机制,MyBatis与Spring框架整合后,则可以使用Spring的事务管理机制进行事务管理。Spring框架的事务管理机制又分为声明式事务管理和编程式事务管理。
关于声明式事务管理的内容,可以参考我之前写的两篇文章:【SpringBoot源码解读与原理分析(三十三)SpringBoot整合JDBC(二)声明式事务的生效原理和控制流程】、【SpringBoot源码解读与原理分析(三十四)SpringBoot整合JDBC(三)声明式事务的传播行为控制】。
这里主要介绍一下编程式事务管理的使用。
MyBatis本身提供了一个Transaction接口,定义了事务管理器的基本行为,例如获取Connection对象、提交或回滚事务。 其源码如下:
源码7:org.apache.ibatis.transaction.Transaction
public interface Transaction {
// 获取Connection对象
Connection getConnection() throws SQLException;
// 提交事务
void commit() throws SQLException;
// 回滚事务
void rollback() throws SQLException;
// 关闭事务
void close() throws SQLException;
// 获取超时时间
Integer getTimeout() throws SQLException;
}
Executor组件就是通过Transaction对象的getConnection()
方法获取Connection对象的,进而完成与数据库的交互。例如,BaseExecutor的getConnection()
方法源码如下:
源码8:org.apache.ibatis.executor.BaseExecutor
protected Transaction transaction;
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
}
return connection;
}
MyBatis的Transaction接口有两个实现类,分别是JdbcTransaction和ManagedTransaction。 JdbcTransaction实现类提供了通过JDBC方式进行简单的事务提交和回滚操作,需要自行处理程序中的异常。而ManagedTransaction表示MyBatis不进行事务管理,事务由其他框架来管理。
MyBatis整合Spring事务管理器,比较关键的部分就是保证Connection对象的一致性。
在Spring事务管理器中,Connection对象的获取和释放都是通过spring-jdbc模块中的一个工具类DataSourceUtils来完成的。
源码9:org.springframework.jdbc.datasource.DataSourceUtils
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
try {
return doGetConnection(dataSource);
}
// catch ...
}
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
// 获取ConnectionHolder对象
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
// ConnectionHolder对象中持有Connection对象
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
// logger ...
}
// 直接返回ConnectionHolder对象中持有Connection对象
return conHolder.getConnection();
}
// ConnectionHolder对象中没有持有Connection对象
// 则从数据源对象的连接池中申请一个新的Connection对象
Connection con = fetchConnection(dataSource);
if (TransactionSynchronizationManager.isSynchronizationActive()) {
try {
// 将Connection对象包装成ConnectionHolder对象
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
} else {
holderToUse.setConnection(con);
}
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
// 将ConnectionHolder对象与TransactionSynchronizationManager进行关联
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
} // catch ...
}
return con;
}
由 源码9 可知,在DataSourceUtils工具类的doGetConnection()
方法中,首先调用TransactionSynchronizationManager类的getResource()
方法获取一个ConnectionHolder对象,如果能获取到,则直接返回ConnectionHolder对象中持有的Connection对象。
如果没有获取到ConnectionHolder对象,则从数据源对象的连接池中申请一个新的Connection对象,并将Connection对象包装成ConnectionHolder对象,再调用TransactionSynchronizationManager类的bindResource()
方法与之进行关联。
TransactionSynchronizationManager类的bindResource()
方法有两个参数:第一个是DataSource对象,第二个是ConnectionHolder对象。进入bindResource()
方法后,这两个参数分别对应一个Map对象的Key值和Value值。
源码10:org.springframework.transaction.support.TransactionSynchronizationManager
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
public static void bindResource(Object key, Object value) throws IllegalStateException {
// 将DataSource对象转换为实际的Key
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Assert.notNull(value, "Value must not be null");
// 获取ThreadLocal中的Map对象
Map<Object, Object> map = resources.get();
// 如果获取不到,则进行初始化
if (map == null) {
map = new HashMap<>();
resources.set(map);
}
// 替换当前Key对应的ConnectionHolder对象
Object oldValue = map.put(actualKey, value);
// Transparently suppress a ResourceHolder that was marked as void...
if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
oldValue = null;
}
if (oldValue != null) {
// throw ...
}
}
public static Object getResource(Object key) {
// 将DataSource对象转换为实际的Key
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
return doGetResource(actualKey);
}
private static Object doGetResource(Object actualKey) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
// 根据DataSource对象对应的Key值获取ConnectionHolder对象
Object value = map.get(actualKey);
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
由 源码10 可知,TransactionSynchronizationManager类的bindResource()
方法首先获取ThreadLocal对象中的Map对象,如果获取不到,则进行初始化。再以DataSource对象为Key值,ConnectionHolder对象为Value值存入Map对象中。
在TransactionSynchronizationManager类的getResource()
方法中,仍然将DataSource对象转换为实际的Key,再在doGetResource()
方法中根据DataSource对象对应的Key值获取ConnectionHolder对象。
只要是同一个DataSource对象,则它所对应的Key值就是一样的,最终获取到的ConnectionHolder对象也是一样的,也就是Connection对象是一样的。
mybatis-spring模块对MyBatis的Transaction接口定义了一个新的实现:SpringManagedTransaction。其源码如下:
源码11:org.mybatis.spring.transaction.SpringManagedTransaction
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
private void openConnection() throws SQLException {
// 通过DataSourceUtils工具类获取Connection对象
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
// LOGGER...
}
由 源码11 可知,SpringManagedTransaction重写了getConnection()
方法,转调openConnection()
方法。而在openConnection()
方法中,就是通过DataSourceUtils工具类获取Connection对象,这样就确保了Spring框架获取到的Connection对象是同一个。
另外,mybatis-spring模块还提供了SpringManagedTransactionFactory工厂类用户创建SpringManagedTransaction对象。
源码12:org.mybatis.spring.SqlSessionFactoryBean
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
// ......
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
// ......
}
由 源码12 可知,在SqlSessionFactoryBean的buildSqlSessionFactory()
方法中构建SqlSessionFactory对象时,指定Environment中的transactionFactory属性为SpringManagedTransactionFactory对象。
这样就保证了MyBatis的Executor组件通过Transaction对象的getConnection()
方法获取到的Connection对象,和Spring事务管理器中获取到的Connection对象是同一个对象。
11.3 小结
第十一章到此就梳理完毕了,本章的主题是:MyBatis整合Spring。回顾一下本章的梳理的内容:
(二十七)Mapper动态代理对象注册过程、MyBatis整合Spring事务管理
更多内容请查阅分类专栏:MyBatis3源码深度解析
本专栏完。后续按需增加相关文章。