Spring 整合 MyBatis

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 本文首先对 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'


2. 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());
  }
}

3. 主配置文件


@Configuration
@Import(MyBatisConfig.class)
@MapperScan(value = "cn.edu.xxx.mapper", annotationClass = Mapper.class)
public class AppConfig {
}


4.@MapperScan 注解的定义


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
    //...
}


5.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));
}


6.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 类。


  1. MapperScannerRegistrar 类实现了 ImportBeanDefinitionRegistrar 接口,所以 Spring 在启动时会调用MapperScannerRegistrar 类中的 registerBeanDefinitions 方法。


  1. registerBeanDefinitions 中生成了一个 MapperScannerConfigurer 类型的 BeanDefinition 。


  1. 而 MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor 接口,所以 Spring 在初始化的时候,会去调用 MapperScannerConfigurer 的 postProcessBeanDefinitionRegistry() 方法。


  1. 在 postProcessBeanDefinitionRegistry() 中生成一个 ClassPathMapperScanner 对象,然后进行扫描


  1. 通过利用 Spring 的扫描后,会把接口扫描出来并且得到对应的 BeanDefinition。


  1. 接下来把扫描得到的 BeanDefinition 进行修改,把 BeanClass 修改为 MapperFactoryBean,把 AutowireMode 修改为 byType。


  1. 扫描完成后,Spring 就会基于 BeanDefinition 去创建 Bean 了,相当于每个 Mapper 对应一个 FactoryBean(单例的)


  1. 在 MapperFactoryBean 中的 getObject 方法中,调用了 getSqlSession() 去得到一个 sqlSession 对象,然后根据对应的 Mapper 接口生成一个代理对象


  1. sqlSession 对象是 Mybatis 中的,一个 sqlSession 对象需要 SqlSessionFactory 来产生


  1. MapperFactoryBean 的 AutowireMode 为byType,所以 Spring 会自动调用 set 方法,有两个 set 方法,一个setSqlSessionFactory,一个 setSqlSessionTemplate,而这两个方法执行的前提是根据方法参数类型能找到对应的bean,所以 Spring 容器中要存在 SqlSessionFactory 类型的 bean 或者 SqlSessionTemplate 类型的 bean。


  1. 如果你定义的是一个 SqlSessionFactory 类型的 bean,那么最终也会被包装为一个 SqlSessionTemplate 对象,并且赋值给 sqlSession 属性


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


mybatis-spring-1.3.4


  1. 通过利用 Spring 的扫描后,会把接口扫描出来并且得到对应的 BeanDefinition。


  1. 接下来把扫描得到的 BeanDefinition 进行修改,把 BeanClass 修改为 MapperFactoryBean,把 AutowireMode 修改为 byType。


  1. 扫描完成后,Spring 就会基于 BeanDefinition 去创建 Bean了,相当于每个 Mapper 对应一个 FactoryBean(单例的)


  1. 在 MapperFactoryBean 中的 getObject 方法中,调用了 getSqlSession() 去得到一个 sqlSession 对象,然后根据对应的 Mapper 接口生成一个代理对象。


  1. 同时因为ClassPathMapperScanner中重写了isCandidateComponent方法,导致isCandidateComponent只会认为接口是备选者Component


  1. 后续流程和 2.0.5 一致。


Spring 源码解析









相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
1月前
ssm(Spring+Spring mvc+mybatis)——updateDept.jsp
ssm(Spring+Spring mvc+mybatis)——updateDept.jsp
10 0
|
1月前
ssm(Spring+Spring mvc+mybatis)——showDept.jsp
ssm(Spring+Spring mvc+mybatis)——showDept.jsp
9 0
|
1月前
|
SQL Java 数据库连接
挺详细的spring+springmvc+mybatis配置整合|含源代码
挺详细的spring+springmvc+mybatis配置整合|含源代码
41 1
|
1月前
|
druid Java 数据库连接
Spring Boot3整合MyBatis Plus
Spring Boot3整合MyBatis Plus
45 1
|
27天前
|
敏捷开发 监控 前端开发
Spring+SpringMVC+Mybatis的分布式敏捷开发系统架构
Spring+SpringMVC+Mybatis的分布式敏捷开发系统架构
61 0
|
1月前
|
Java Windows Perl
mybatis+spring报错PropertyAccessException 1: org.springframework.beans.MethodInvocationException
mybatis+spring报错PropertyAccessException 1: org.springframework.beans.MethodInvocationException
12 0
|
1月前
ssm(Spring+Spring mvc+mybatis)Dao层实现类——DeptDaoImpl
ssm(Spring+Spring mvc+mybatis)Dao层实现类——DeptDaoImpl
12 0
|
1月前
ssm(Spring+Spring mvc+mybatis)Dao接口——IDeptDao
ssm(Spring+Spring mvc+mybatis)Dao接口——IDeptDao
8 0
|
1月前
ssm(Spring+Spring mvc+mybatis)实体类——Dept
ssm(Spring+Spring mvc+mybatis)实体类——Dept
11 0
|
1月前
|
Java 关系型数据库 MySQL
ssm(Spring+Spring mvc+mybatis)
ssm(Spring+Spring mvc+mybatis)
13 0