Spring 整合 MyBatis

808 阅读5分钟

概述

本文首先对 FactoryBean 接口做简单得介绍,详细描述如何通过 FactoryBean 来自定义 Spring Bean, 然后在对 Spring 和 MyBatis 进行一个整合。 最后再说明 mybatis-spring 中 2.0 和 1.3 的实现。

FactoryBean

FactoryBean 主要是解决自定义创建 Bean 的问题,通常 Spring IOC 是通过反射来创建对象的。但是对于特别复杂的对象我们可以通过自己实现 FactoryBean#getObject 来自定义创建实例,比如:mybatis-spring 中的 SqlSessionFactoryBean 这个接口主要是就是用来拓展三方框架,或者是说他是 Spring 整合其他框架的基础。

  1. FactoryBean 在创建 bean 过程中如何调用的在 DefaultListableBeanFactory#preInstantiateSingletons 方法我们先来看看。
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
    // 1. 判断是否是 FactoryBean, 判断规则: 是否是实现 FactoryBean 接口
    if (isFactoryBean(beanName)) {
        //如果是一个 FactoryBean 那么就去获取 xxxFactoryBeans 实例, 它的 beanName = "&" + beanName
        Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
        if (bean instanceof FactoryBean) {
            final FactoryBean<?> factory = (FactoryBean<?>) bean;
            boolean isEagerInit;
            // eager: 急切的意思, isEagerInit 是否需要立马初始化
            if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
                                ((SmartFactoryBean<?>) factory)::isEagerInit,
                        getAccessControlContext());
            } else {
                isEagerInit = (factory instanceof SmartFactoryBean &&
                        ((SmartFactoryBean<?>) factory).isEagerInit());
            }
            if (isEagerInit) {
                //提前初始化 xxxFactoryBean
                getBean(beanName);
            }
        }
    } else {
        // 2. 不是工厂Bean, 就通过 getBean 来创建 Bean 实例
        getBean(beanName);
    }
}

这里其实有两个逻辑如果是 FactoryBean 首先会去创建一个 FactoryBean Bean 他的 beanName 是 "&" + beanName, 创建完成之后再去创建 Bean。

  1. 我们再来看下 FactoryBean#getObject 调用的时机他是在 AbstractBeanFactory#doGetBean 的时候会调用 getObjectForBeanInstance 再去调 getObjectFromFactoryBean 然后调用 doGetObjectFromFactoryBean 最后调用 factory.getObject() 下面我贴一下核心代码方便阅读。
// doGetBean
Object sharedInstance = getSingleton(beanName); //Map<beanName, Object>
if (sharedInstance != null && args == null) { // Bean 存在
    if (logger.isTraceEnabled()) {
        if (isSingletonCurrentlyInCreation(beanName)) {
            logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
                    "' that is not fully initialized yet - a consequence of a circular reference");
        }
        else {
            logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
        }
    }
    // 判断 sharedInstance 是不是 FactoryBean
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}

// getObjectForBeanInstance
// 如果不是 FactoryBean 直接返回
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
    return beanInstance;
}

Object object = null;
if (mbd == null) {
    // 缓存中获取
    object = getCachedObjectForFactoryBean(beanName);
}
if (object == null) {
    // Return bean instance from factory.
    FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
    // Caches object obtained from FactoryBean if it is a singleton.
    if (mbd == null && containsBeanDefinition(beanName)) {
        mbd = getMergedLocalBeanDefinition(beanName);
    }
    boolean synthetic = (mbd != null && mbd.isSynthetic());
    // 执行创建
    object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}

// getObjectFromFactoryBean
object = doGetObjectFromFactoryBean(factory, beanName);

// doGetObjectFromFactoryBean
if (System.getSecurityManager() != null) {
    AccessControlContext acc = getAccessControlContext();
    try {
        object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc);
    }
    catch (PrivilegedActionException pae) {
        throw pae.getException();
    }
}
else {
    // 调用 FactoryBean的 getObject 方法
    object = factory.getObject();
}

Spring 和 MyBatis 的整合(2.0.5)

  1. 配置依赖信息
compile(project(":spring-jdbc"))
// Spring 整合 Mybatis
// compile 'org.mybatis:mybatis-spring:1.3.2'
compile 'org.mybatis:mybatis-spring:2.0.5'
compile 'org.mybatis:mybatis:3.5.3'
compile "commons-dbcp:commons-dbcp:1.4"
compile 'mysql:mysql-connector-java:8.0.16'
  1. Spring Bean 配置信息
@Configuration
public class MyBatisConfig {

	/**
	 * Session 工厂配置
	 *
	 * @param dataSource 数据源
	 * @return Session 工厂
	 * @throws Exception 异常信息
	 */
	@Bean
	public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
		SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
		factoryBean.setDataSource(dataSource);
		return factoryBean.getObject();
	}

	/**
	 * 数据源配置
	 *
	 * @return 数据源
	 */
	@Bean
	public DataSource dataSource() {
		BasicDataSource dataSource = new BasicDataSource();
		dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
		dataSource.setUrl("jdbc:mysql://ip:3306/db_name?useSSL=false&characterEncoding=UTF-8&useUnicode=true&serverTimezone=Asia/Shanghai");
		dataSource.setUsername("root");
		dataSource.setPassword("---");
		dataSource.setInitialSize(5);
		dataSource.setMaxActive(10);
		return dataSource;
	}

	/**
	 * 事务管理器配置
	 *
	 * @return 事务管理器
	 */
	@Bean
	public DataSourceTransactionManager transactionManager() {
		return new DataSourceTransactionManager(dataSource());
	}
}
  1. 主配置文件
@Configuration
@Import(MyBatisConfig.class)
@MapperScan(value = "cn.edu.xxx.mapper", annotationClass = Mapper.class)
public class AppConfig {
}
  1. @MapperScan 注解的定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
    //...
}
  1. MapperScannerRegistrar 由于它实现了,BeanDefinitionRegistryPostProcessor 主要是生成一个 ClassPathMapperScanner 对象。我们就来看看 postProcessBeanDefinitionRegistry
// postProcessBeanDefinitionRegistry
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    scanner.registerFilters();
    //执行扫描
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
  1. ClassPathMapperScanner#scan 扫描指定路径得到 Mapper Bean, 也就是说是一个个的 FactoryBean
public int scan(String... basePackages) {
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

    doScan(basePackages);

    // Register annotation config processors, if necessary.
    if (this.includeAnnotationConfig) {
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

    return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
  1. 然后就通过 MapperFactoryBean#getObject 中会通过 getMapper 生成一个代理对象。
public T getObject() throws Exception {
    return this.getSqlSession().getMapper(this.mapperInterface);
}
  1. sqlSession 是通过 SqlSessionFactory 来产生的, 由于 MapperFactoryBean 的字段注入模式为 byType 那么,Spring 会自动调用 set 方法,setSqlSessionTemplate 或者 setSqlSessionFactory, 所以我们要先定义SqlSessionTemplate 或者 SqlSessionFactory 的 Bean。
// setSqlSessionFactory
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
  if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
  }
}

//setSqlSessionTemplate
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
  this.sqlSessionTemplate = sqlSessionTemplate;
}
  1. 如果定义的是一个 SqlSessionFactory 类型的 bean,那么最终也会被包装为一个 SqlSessionTemplate 对象,并且赋值给 sqlSession 属性。

10.而在 SqlSessionTemplate 类中就存在一个 getMapper 方法,这个方法中就会利用 SqlSessionFactory 来生成一个代理对象。

两个版本的实现

最后我们再来完整的整理下 mybatis-spring 1.x 和 2.x 的实现处理过程

mybatis-spring-2.0.5

  1. 通过 @MapperScan 导入了 MapperScannerRegistrar 类。
  2. MapperScannerRegistrar 类实现了 ImportBeanDefinitionRegistrar 接口,所以 Spring 在启动时会调用MapperScannerRegistrar 类中的 registerBeanDefinitions 方法。
  3. registerBeanDefinitions 中生成了一个 MapperScannerConfigurer 类型的 BeanDefinition 。
  4. 而 MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor 接口,所以 Spring 在初始化的时候,会去调用 MapperScannerConfigurer 的 postProcessBeanDefinitionRegistry() 方法。
  5. 在 postProcessBeanDefinitionRegistry() 中生成一个 ClassPathMapperScanner 对象,然后进行扫描
  6. 通过利用 Spring 的扫描后,会把接口扫描出来并且得到对应的 BeanDefinition。
  7. 接下来把扫描得到的 BeanDefinition 进行修改,把 BeanClass 修改为 MapperFactoryBean,把 AutowireMode 修改为 byType。
  8. 扫描完成后,Spring 就会基于 BeanDefinition 去创建 Bean 了,相当于每个 Mapper 对应一个 FactoryBean(单例的)
  9. 在 MapperFactoryBean 中的 getObject 方法中,调用了 getSqlSession() 去得到一个 sqlSession 对象,然后根据对应的 Mapper 接口生成一个代理对象
  10. sqlSession 对象是 Mybatis 中的,一个 sqlSession 对象需要 SqlSessionFactory 来产生
  11. MapperFactoryBean 的 AutowireMode 为byType,所以 Spring 会自动调用 set 方法,有两个 set 方法,一个setSqlSessionFactory,一个 setSqlSessionTemplate,而这两个方法执行的前提是根据方法参数类型能找到对应的bean,所以 Spring 容器中要存在 SqlSessionFactory 类型的 bean 或者 SqlSessionTemplate 类型的 bean。
  12. 如果你定义的是一个 SqlSessionFactory 类型的 bean,那么最终也会被包装为一个 SqlSessionTemplate 对象,并且赋值给 sqlSession 属性
  13. 而在 SqlSessionTemplate 类中就存在一个 getMapper 方法,这个方法中就会利用 SqlSessionFactory 来生成一个代理对象。

mybatis-spring-1.3.4

  1. 通过利用 Spring 的扫描后,会把接口扫描出来并且得到对应的 BeanDefinition。
  2. 接下来把扫描得到的 BeanDefinition 进行修改,把 BeanClass 修改为 MapperFactoryBean,把 AutowireMode 修改为 byType。
  3. 扫描完成后,Spring 就会基于 BeanDefinition 去创建 Bean了,相当于每个 Mapper 对应一个 FactoryBean(单例的)
  4. 在 MapperFactoryBean 中的 getObject 方法中,调用了 getSqlSession() 去得到一个 sqlSession 对象,然后根据对应的 Mapper 接口生成一个代理对象。
  5. 同时因为ClassPathMapperScanner中重写了isCandidateComponent方法,导致isCandidateComponent只会认为接口是备选者Component
  6. 后续流程和 2.0.5 一致。

Spring 源码解析