zoukankan      html  css  js  c++  java
  • MyBatis 源码篇-MyBatis-Spring 剖析

    本章通过分析 mybatis-spring-x.x.x.jar Jar 包中的源码,了解 MyBatis 是如何与 Spring 进行集成的。

    Spring 配置文件

    MyBatis 与 Spring 集成,在 Spring 配置文件中配置了数据源、SqlSessionFactory、自动扫描 MyBatis 中的 Mapper 接口、事务管理等,这部分内容都交由 Spring 管理。部分配置内容如下所示:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans" >
      
      <!-- 配置数据源 -->
      <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
        lazy-init="true" init-method="init" destroy-method="close">
        <property name="url" value="${dataSource.url}" />
        <property name="username" value="${dataSource.username}" />
        <property name="password" value="${dataSource.password}" />
      </bean>
        
      <!-- 配置SqlSessionFactory -->
      <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:mybatis-config.xml" />
        <property name="mapperLocations" value="classpath*:rabbit/template/mapper/*.xml" />
      </bean>
    
      <!-- 配置自动扫描所有Mapper接口 -->
      <bean id="mapperScan" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="rabbit.template.mapper" />
      </bean>
        
      <!--配置事务管理 -->
      <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
      </bean>
    
      <!-- 激活CGLIB代理功能 -->
      <aop:aspectj-autoproxy proxy-target-class="true" />
      <tx:annotation-driven transaction-manager="transactionManager" />
        
        ......
    </beans>

    从配置文件中可以看出,MyBatis 与 Spring 集成后,MyBatis 中的 SqlSessionFactory 对象是由 SqlSessionFactoryBean 创建的。

    SqlSessionFactoryBean

    SqlSessionFactoryBean 类作为我们分析源码的入口,该类实现了 InitializingBean 接口,在 Bean 初始化的时候,会执行 afterPropertiesSet() 方法,最后会调用 buildSqlSessionFactory() 方法创建 SqlSessionFactory。

    SqlSessionFactoryBean.buildSqlSessionFactory() 方法的具体实现如下:

    protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    
        Configuration configuration;
        XMLConfigBuilder xmlConfigBuilder = null;
        // 创建Configuration对象
        if (this.configuration != null) {
          configuration = this.configuration;
          // 省略配置相关代码
        } else if (this.configLocation != null) {
          xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
          configuration = xmlConfigBuilder.getConfiguration();
        } else {
          configuration = new Configuration();
          // 省略配置相关代码
        }
        // 配置objectFactory
        // 根据Spring配置文件,设置Configuration.objectWrapperFactory
        // 根据Spring配置文件,设置Configuration.objectFactory
        // 扫描typeAliasesPackage指定的包,并为其中的类注册别名
        // 为typeAliases集合中指定的类注册别名
        // 注册plugins集合中指定的插件
        // 扫描typeHandlersPackage指定的包,并注册其中的TypeHandler
        // 注册指定的typeHandlers
        // 配置databaseIdProvider
        // 配置缓存
      
        // 调parse()方法解析配置文件
        if (xmlConfigBuilder != null) {
          xmlConfigBuilder.parse();
        }
        // 如果未配置transactionFactory,则默认使用SpringManagedTransactionFactory
        if (this.transactionFactory == null) {
          this.transactionFactory = new SpringManagedTransactionFactory();
        }
        // 设置environment
        configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
        // 根据mapperLocations配置,处理映射配置文件以及相应的Mapper接口
        if (!isEmpty(this.mapperLocations)) {
          for (Resource mapperLocation : this.mapperLocations) {
            if (mapperLocation == null) {
              continue;
            }
            try {
              XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                  configuration, mapperLocation.toString(), configuration.getSqlFragments());
              xmlMapperBuilder.parse();
            } catch (Exception e) {
              throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
            } finally {
              ErrorContext.instance().reset();
            }
    
            if (LOGGER.isDebugEnabled()) {
              LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
            }
          }
        } else {
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
          }
        }
        // 调用SqlSessionFactoryBuilder.build()方法,创建SqlSessionFactory对象并返回
        return this.sqlSessionFactoryBuilder.build(configuration);
      }

    SqlSessionFactoryBuilder.build() 方法返回的对象类型是 DefaultSqlSessionFactory。

    SqlSessionFactoryBean 同时实现了 FactoryBean 接口,重写了 getObject() 方法,该方法返回 DefaultSqlSessionFactory 对象。

    当配置文件中 <bean> 的 class 属性配置的实现类是 FactoryBean 时,通过 getBean() 方法返回的不是 FactoryBean 本身,而是 FactoryBean#getObject() 方法所返回的对象,相当于FactoryBean#getObject() 代理了 getBean() 方法。

    SqlSessionFactoryBean#getObject() 方法的具体实现如下:

    public SqlSessionFactory getObject() throws Exception {
      if (this.sqlSessionFactory == null) {
        afterPropertiesSet();
      }
    
      return this.sqlSessionFactory;
    }

    在《MyBatis 技术内幕》这本书的4.2.4章详细描述了 mybatis-spring Jar 包中的 SpringManagedTransaction、SqlSessionTemplate、SqlSessionDaoSupport、MapperFactoryBean、MapperScannerConfigurer 类的作用,有不明白的可以参考。

    接下来继续介绍 Spring 配置文件中的 MapperScannerConfigurer Bean 对象。

    MapperScannerConfigurer

    MapperScannerConfigurer 类实现了 BeanDefinitionRegistryPostProcessor 接口,该接口中的 postProcessBeanDefinitionRegistry() 方法会在系统初始化的过程中被调用,该方法扫描了配置文件中配置的basePackage 下的所有 Mapper 类,最终生成 Spring 的 Bean 对象,注册到容器中。

    postProcessBeanDefinitionRegistry() 方法具体实现如下:

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
      if (this.processPropertyPlaceHolders) {
        processPropertyPlaceHolders();
      }
      // 创建ClassPathMapperScanner对象
      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);
      // 根据上面的配置,生成相应的过滤器。这些过滤器在扫描过程中会过滤掉不符合添加的内容,例如,
      // annotationClass字段不为null时,则会添加AnnotationTypeFilter过滤器,通过该过滤器
      // 实现只扫描annotationClass注解标识的接口的功能
      scanner.registerFilters();
      scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
    }

    ClassPathMapperScanner.scan() 方法会调用父类的 scan() 方法,核心逻辑在 doScan() 方法中实现,具体实现如下:

    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
      // 调用父类的doScan()方法,遍历basePackages中指定的所有包,扫描每个包下的Java文件并进行解析。
      // 使用之前注册的过滤器进行过滤,得到符合条件的BeanDefinitionHolder对象
      Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    
      if (beanDefinitions.isEmpty()) {
        logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
      } else {
        // 处理扫描得到的BeanDefinitionHolder集合
        processBeanDefinitions(beanDefinitions);
      }
    
      return beanDefinitions;
    }

    ClassPathMapperScanner.processBeanDefinitions() 方法会对 doScan() 方法中扫描到的 BeanDefinition 集合进行修改,主要是将其中记录的接口类型改造为 MapperFactoryBean 类型,并填充 MapperFactoryBean 所需的相关信息。

    ClassPathMapperScanner.processBeanDefinitions() 方法具体实现如下:

    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
      GenericBeanDefinition definition;
      for (BeanDefinitionHolder holder : beanDefinitions) {
        definition = (GenericBeanDefinition) holder.getBeanDefinition();
    
        if (logger.isDebugEnabled()) {
          logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
            + "' and '" + definition.getBeanClassName() + "' mapperInterface");
        }
    
        // 将扫描到的接口类型作为构造方法的参数
        definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
        // 将BeanDefinition中记录的Bean类型修改为MapperFactoryBean
        definition.setBeanClass(this.mapperFactoryBean.getClass());
        // 构造MapperFactoryBean的属性,将sqlSessionFactory、sqlSessionTemplate
        // 等信息填充到BeanDefinition中
        // 修改自动注入方式
      }
    }

    ClassPathMapperScanner 在处理 Mapper 接口的时候用到了 MapperFactoryBean 类,它是 MyBatis-Spring 提供的一个动态代理的实现,可以直接将 Mapper 接口注入到 Service 层的 Bean 中,这样就不需要编写任何 DAO 实现的代码。

    MapperFactoryBean 的继承关系如图所示:

    image.png

    MapperFactoryBean 类的动态代理功能是通过实现了 Spring 提供的 FactoryBean 接口实现的,该接口是一个用于创建 Bean 对象的工厂接口,通过 geObject() 方法获取真实的对象。

    MapperFactoryBean.geObject() 方法的实现如下:

    public T getObject() throws Exception {
      return getSqlSession().getMapper(this.mapperInterface);
    }

    通过 SqlSession 对象获取 Mapper 文件,《MyBatis 源码篇-SQL 执行的流程》介绍过 Mapper 接口的代理对象的获取过程。

    getSqlSession() 是 SqlSessionDaoSupport 中的方法。该类的 sqlSession 属性是通过 Spring 容器自动注入 sqlSessionFactory 属性实现的。源码实现如下:

    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
      if (!this.externalSqlSession) {
        this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
      }
    }

    这样就不难理解,在 Spring 容器中,Mapper 接口对应的实现类还是 MyBatis 提供的 MapperProxy 代理对象。MapperFactoryBean 类只不过是包装了一下,让真正的对象能够注入到 Spring 容器中。所以 Mapper 接口对应的实现类是作为单例一直存在 Spring 容器中的。

    因为引入了 Spring 框架,增加了这块的源码的理解难度,下面通过一个简单的时序图总结 Mapper 实现类的生成过程。

    MyBatis-集成Spring-Mapper.png

    SqlSessionTemplate

    SqlSessionTemplate 实现了 SqlSession 接口,在 MyBatis 与 Spring 集成开发时,用来代替 MyBatis 中的 DefaultSqlSession 的功能。

    SqlSessionTemplate 是线程安全的,可以在 DAO 之间共享使用,比如上面生成的 Mapper 对象会持有一个 SqlSessionTemplate 对象,每次请求都会共用该对象。在 MyBatis 中 SqlSession 的 Scope 是会话级别,请求结束后需要关闭本次会话,怎么集成了 Spring 后,可以共用了?

    首先,在集成 Spring 后,Mapper 对象是单例,由 Spring 容器管理,供 Service 层使用,SqlSessionTemplate 在设计的时候,功能分成了如下两部分:

    1. 获取 MapperProxy 代理对象;
    2. 执行 SQL 操作,该部分功能通过代理对象 SqlSessionInterceptor 实现;

    两类方法的具体实现如下:

    @Override
    public <T> T getMapper(Class<T> type) {
      return getConfiguration().getMapper(type, this);
    }
    
    @Override
    public <T> T selectOne(String statement) {
      return this.sqlSessionProxy.<T> selectOne(statement);
    }

    getMapper 方法是可以共享的,SQL 操作的相关方法是会话级别的不能共享,所以在 SqlSessionTemplate 类中通过动态代理的方式来区分这两部分功能的实现。

    接下来看看 SqlSessionTemplate 类中的动态代理部分的实现:

    // SqlSession 的代理类
    private final SqlSession sqlSessionProxy;
    
    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
        PersistenceExceptionTranslator exceptionTranslator) {
    
      notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
      notNull(executorType, "Property 'executorType' is required");
    
      this.sqlSessionFactory = sqlSessionFactory;
      this.executorType = executorType;
      this.exceptionTranslator = exceptionTranslator;
      // 创建SqlSession的代理类,给sqlSessionProxy属性赋值
      this.sqlSessionProxy = (SqlSession) newProxyInstance(
          SqlSessionFactory.class.getClassLoader(),
          new Class[] { SqlSession.class },
          new SqlSessionInterceptor());
    }
    // 代理类
    private class SqlSessionInterceptor implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          // 通过SqlSessionUtils.getSqlSession()获取SqlSession对象,同一个事务共享SqlSession
          SqlSession sqlSession = getSqlSession(
              SqlSessionTemplate.this.sqlSessionFactory,
              SqlSessionTemplate.this.executorType,
              SqlSessionTemplate.this.exceptionTranslator);
          // 调用SqlSession对象的相应方法
          Object result = method.invoke(sqlSession, args);
          // 检测事务是否由Spring进行管理,并据此决定是否提交事务
          if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
            // force commit even on non-dirty sessions because some databases require
            // a commit/rollback before calling close()
            sqlSession.commit(true);
          }
          return result;
        }
      }

    MyBatis 源码篇

  • 相关阅读:
    易语言破解与安装
    用 AS3.0 的 fscommand 命令调用 .exe 文件。
    swf批量导出
    pureMVC java版搭建流程
    PureMVC 框架总结收录
    一些算法
    练习3.34
    关于数组的注意事项
    练习3.30、3.33
    练习3.27、3.28、3.29
  • 原文地址:https://www.cnblogs.com/yinjw/p/11757637.html
Copyright © 2011-2022 走看看