zoukankan      html  css  js  c++  java
  • 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,而是并不是批量提交的,而是单条提交的。

      

  • 相关阅读:
    感动女友的话
    情侣一起玩的游戏
    让妹子被我征服
    调戏妹子 微信聊天
    sqlserver2008 解决 ldf文件过大的方法
    sqlserver 动态表名 动态字段名 执行 动态sql
    sqlserver convert 日期时间 转换格式化
    sqlserver 2008 卸载时提示 “重新启动计算机”失败
    sqlserver mdf ldf文件导入
    myeclipse trial expired[转]
  • 原文地址:https://www.cnblogs.com/juniorMa/p/13933192.html
Copyright © 2011-2022 走看看