MyBatis3源码深度解析(二十七)MyBatis整合Spring框架

前言

通常情况下,我们并不会单独使用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动态代理对象。

源码1org.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对象。

源码2org.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。

源码3org.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对象。

源码4org.springframework.context.annotation.ClassPathBeanDefinitionScanner

public int scan(String... basePackages) {
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
    // 执行扫描动作
    doScan(basePackages);
    // ......
}

由 源码4 可知,ClassPathBeanDefinitionScanner对象的scan()方法会转调子类的doScan()方法。

源码5org.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对象。

源码6org.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对象、提交或回滚事务。 其源码如下:

源码7org.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()方法源码如下:

源码8org.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来完成的。

源码9org.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值。

源码10org.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。其源码如下:

源码11org.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对象。

源码12org.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源码深度解析

本专栏完。后续按需增加相关文章。

  • 29
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

灰色孤星A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值