Mybatis和Spring的整合原理

  上一篇提到了和Spring整合后,Mybatis的BatchExecutor无法真正生效,本篇就好好分析分析这里面的原因

  一 配置文件

<!-- 配置sqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 实例化sqlSessionFactory时需要使用上述配置好的数据源以及SQL映射文件 -->
        <property name="dataSource" ref="dataSource" />
        <!-- 自动扫描me/gacl/mapping/目录下的所有SQL映射的xml文件, 省掉Configuration.xml里的手工配置
        value="classpath:me/gacl/mapping/*.xml"指的是classpath(类路径)下me.gacl.mapping包中的所有xml文件
        UserMapper.xml位于me.gacl.mapping包下,这样UserMapper.xml就可以被自动扫描
         -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:me/gacl/mapping/*.xml" />
    </bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 扫描me.gacl.dao这个包以及它的子包下的所有映射接口类 -->
        <property name="basePackage" value="me.gacl.dao" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>

  接下来我们就好好分析这两个类  SqlSessionFactoryBean ,MapperScannerConfigurer

  二 SqlSessionFactoryBean 

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

  它是一个FactoryBean,那我们只需要关注getObject方法就好了

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

    return this.sqlSessionFactory;
  }

  afterPropertiesSet有一处还是值得研究的

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
  ...
  

  if (this.transactionFactory == null) {
  this.transactionFactory = new SpringManagedTransactionFactory();
  }

  mybatis中很重要的调用链上,一个sqlSession包含一个executor,一个executor包含一个transaction,这个transanction是真正提供jdbc的connection的,这里负责创建transaction的是spirng提供的

  SpringManagedTransactionFactory,就表示提供connection的任务由spring完成。

  这样,spring容器内就有一个java bean 类型是 SqlSessionFactory,name是我们配的 sqlSessionFactory 

  三  MapperScannerConfigurer

  它实现了接口  BeanDefinitionRegistryPostProcessor 就说明它具有向beanFactory注册BeanDefinition的能力

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware

  

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.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

  因为我们只配了  <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> 所以这里   this.sqlSessionFactory = null

  basePackage = me.gacl.dao

  直接跳到 ClassPathMapperScanner.doScan

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    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 {
      for (BeanDefinitionHolder holder : beanDefinitions) {
        GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();

        if (logger.isDebugEnabled()) {
          logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
              + "' and '" + definition.getBeanClassName() + "' mapperInterface");
        }

        // the mapper interface is the original class of the bean
        // but, the actual class of the bean is MapperFactoryBean
        definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());//先把原始的类型取出来塞到BD的属性里 me.gacl.dao.UserMapper
        definition.setBeanClass(MapperFactoryBean.class);//然后重新给BD赋予class,这样这个bean的类型就是 MapperFactoryBean 

        definition.getPropertyValues().add("addToConfig", this.addToConfig);

        boolean explicitFactoryUsed = false;
        if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
          definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
          explicitFactoryUsed = true;
        } else if (this.sqlSessionFactory != null) {
          definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
          explicitFactoryUsed = true;
        }

  一个BeanDefinition就这样被完成了,并注册到beanFactory里。它有几个重要的属性

  1 该Bean的class是 MapperFactoryBean

  2 它有属性  mapperInterface 这里是 me.gacl.dao.UserMapper

  3 它有属性  sqlSessionFactory 就是在上一小节得到的 sqlSessionFactory (Mybatis的原生类)

  好了,到这里后我们要分析的代码就是 MapperFactoryBean 

  四 MapperFactoryBean 

  这个类不得了,可以说不能执行Batch的原因就出在他身上

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  可以看出来 MapperFactoryBean 本身就一个属性  mapperInterface 表示的是 me.gacl.dao.UserMapper

  主要的功能和属性都在  SqlSessionDaoSupport 

  而且它本身就是一个  FactoryBean 还是来看 getObject,getMapper就是生成Dao中接口的代理类

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

  getSqlSession()的实现就至关重要了,接下来就是重点分析 SqlSessionTemplate。这里请记住两个至关重要的类SqlSessionDaoSupport ,SqlSessionTemplate

public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSession sqlSession;

  private boolean externalSqlSession;

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

  这个类看上去有点奇怪,他本身实现了SqlSession接口,但是成员变量还有一个sqlSessionProxy,这个就是很常见的组合模式,干活的肯定是sqlSessionProxy

public class SqlSessionTemplate implements SqlSession {

  private final SqlSessionFactory sqlSessionFactory;

  private final ExecutorType executorType;

  private final SqlSession sqlSessionProxy;

  private final PersistenceExceptionTranslator exceptionTranslator;

  熟悉的动态代理代码,那么重点当然是分析实现InvocationHandler的逻辑了

this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());

  

private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);//这个sqlSession就是
      try {
        Object result = method.invoke(sqlSession, args);
        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;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);//spring会在每次执行后都关闭,但是这个关闭并不是sqlSession的关闭,仅仅是计数-1
        }//也就是说提交完全交给spring的事务去弄
      }
    }

  

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, "No SqlSessionFactory specified");
    notNull(executorType, "No ExecutorType specified");

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);//这里每次都是null

    if (holder != null && holder.isSynchronizedWithTransaction()) {
      if (holder.getExecutorType() != executorType) {
        throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction");
      }

      holder.requested();

      if (logger.isDebugEnabled()) {
        logger.debug("Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
      }

      return holder.getSqlSession();
    }

    if (logger.isDebugEnabled()) {
      logger.debug("Creating a new SqlSession");
    }

    SqlSession session = sessionFactory.openSession(executorType);//所以这里每次都会执行,也就是每次都会new出来Executor

  正因为每次都需要搞出来一个新的SqlSession,每个SqlSession里都会new一个Executor,所以批量执行是没法完成的。

  这篇文章讲解了spring和mybatis的整合原理,进而分析出了为啥batch没有效果,这里说的没有效果不是说里面的Executor不是BatchExecuto,而是并不是批量提交的,而是单条提交的。

  

原文地址:https://www.cnblogs.com/juniorMa/p/13933192.html