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,而是並不是批量提交的,而是單條提交的。

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM