Mybatis在spring中的工作原理

在日常的開發(fā)工作中恶守,常接觸的持久層框架主要是Hibernate进泼、Mybatis和spring-jdbc,其中spring-jdbc的封裝程度相比之下沒有另外兩個框架高监憎,所以在平時的開發(fā)中渔期,使用Hibernate镊叁、Mybatis的較多柠辞,Hibernate更偏向與對象與關(guān)系的映射团秽,是面向?qū)ο蟮囊粋€很好的體現(xiàn),但是在有些場景中叭首,則不存在那么多的關(guān)系习勤,使用Hibernate必要性就沒那么高,況且相比Mybatis焙格,Hibernate的學(xué)習(xí)成本要稍高一些图毕,上手難度就更大一點(diǎn),所以在我們的日常開發(fā)中眷唉,選用Mybatis的情況更多一些予颤,當(dāng)然,Hibernate框架的強(qiáng)大是不容否定的冬阳,感興趣的朋友可以花點(diǎn)時間去做一下深入的研究蛤虐,在這里我就主要分享一下我在閱讀了部分Mybatis源碼后的一些總結(jié)。

通過源碼來看Mybatis的工作流程

我們這里也拿一個傳統(tǒng)的spring web項目來幫助分析肝陪。雖然現(xiàn)在大多的項目都基于spring-boot在開發(fā)驳庭,但是其根本和傳統(tǒng)的spring是一樣的,只是在某些方面作了一些便利的操作见坑,讓開發(fā)者更簡單的編碼嚷掠。如果要在spring項目中使用Mybatis,首先在我們是spring配置的xml文件中都會配置以下這些相關(guān)配置項:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"></property>
    <property name="mapperLocations" value="classpath:mapper/*.xml"></property>
    <property name="typeAliasesPackage" value="com.hq.entity"></property>
    <property name="plugins">
        <array>
            <bean class="com.github.pagehelper.PageInterceptor">
                <property name="properties">
                    <value>
                        helperDialect=oracle
                    </value>
                </property>
            </bean>
            <!-- <bean class="com.hq.interceptor.MapperInterceptor">
            </bean> -->
        </array>
    </property>
</bean>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.bonzer.dao" />
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg index="0" ref="sqlSessionFactory" />
</bean>

配置主要是數(shù)據(jù)源捏检、xml掃描規(guī)則荞驴,dao接口的掃描包路徑
我們來看一下SqlSessionFactoryBean這個類,它實現(xiàn)了FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent>這三個接口贯城,F(xiàn)actoryBean代表可以getObject()獲取對象熊楼,InitializingBean可以在初始化的時候作擴(kuò)展,ApplicationListener代表它本身可以進(jìn)行事件監(jiān)聽,對應(yīng)配置文件鲫骗,我們可以看到它設(shè)置了dataSource犬耻、mapperLocations、typeAliasesPackage和plugins這幾個屬性执泰,這些都對應(yīng)到了SqlSessionFactoryBean中的同名的屬性枕磁,我們暫且看到這個地方,后面用到這些屬性時再作詳細(xì)分析术吝。
然后我們在來看下MapperScannerConfigurer這個類计济,跟上面的SqlSessionFactoryBean一樣,也是設(shè)置一些屬性排苍,具體的功能在后面做詳細(xì)分析沦寂。

SqlSessionFactory的創(chuàng)建

在spring容器中,一切皆bean的思想是不會變的淘衙,從配置的標(biāo)簽上看传藏,我們上面配置的兩個類,同樣也是兩個bean彤守,那么是bean毯侦,就會被spring容器所加載管理。我在spring容器啟動流程一文中介紹了spring bean的加載創(chuàng)建過程具垫,有興趣的可以去讀一讀叫惊。接下來我們回到這兩個bean上來,首先看看SqlSessionFactoryBean做修,在之前的文章中霍狰,我們提到過一種特殊的bean叫FactoryBean,恰好SqlSessionFactoryBean就是這樣的一種bean饰及,根據(jù)實現(xiàn)的接口可以得知蔗坯,這是一個SqlSessionFactory類型的FactoryBean,在之前介紹的bean創(chuàng)建的時候燎含,有一個方法叫做doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName)宾濒,該方法中最終創(chuàng)建bean的方法則是調(diào)用object = factory.getObject(),即使用傳入的FactoryBean的getObject()方法來獲取bean的實例屏箍,那么我們回到SqlSessionFactoryBean中來找找看getObejct()方法的實現(xiàn):

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

    return this.sqlSessionFactory;
  }

這里返回的就是一個sqlSessionFactory,進(jìn)入afterPropertiesSet()方法查看绘梦,最終會看到是調(diào)用同類中的一個buildSqlSessionFactory(),由于這個方法代碼過多赴魁,我這里就只取出我們重點(diǎn)關(guān)注的那部分代碼:

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 + "'");
        }
      }

這里因為是創(chuàng)建實例卸奉,所有代碼塊的configuration是為null的,所以在以上代碼前是有一個new Configuration()的操作颖御,這個configuration則是整個sqlsessionfactory的核心內(nèi)容榄棵,后面的所有操作都是基于這個configuration。代碼片段中的this.mapperLocations就是我們在文章開始處xml配置的mapperLocations屬性,這里我的理解就是加載mapperLocations下的所有xml文件,然后存放到configuration中疹鳄。

xmlMapperBuilder.parse();

進(jìn)入這個parse()方法

if (!this.configuration.isResourceLoaded(this.resource)) {
         this.configurationElement(this.parser.evalNode("/mapper"));
         this.configuration.addLoadedResource(this.resource);
         this.bindMapperForNamespace();
  }

  this.parsePendingResultMaps();
  this.parsePendingCacheRefs();
  this.parsePendingStatements();

configurationElement(this.parser.evalNode("/mapper")):解析mapper標(biāo)簽的屬性及子標(biāo)簽的各種屬性拧略,如mapper標(biāo)簽的屬性namespace,子標(biāo)簽如:cache-ref | cache | resultMap* | parameterMap* | sql* | insert* | update* | delete* | select* 等的id瘪弓、type垫蛆、parameter、property腺怯、javaType月褥、jdbcType等屬性,
詳細(xì)操作可以看一下代碼片段:

String namespace = context.getStringAttribute("namespace");
if (namespace != null && !namespace.equals("")) {
      this.builderAssistant.setCurrentNamespace(namespace);
      this.cacheRefElement(context.evalNode("cache-ref"));
      this.cacheElement(context.evalNode("cache"));
      this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      this.resultMapElements(context.evalNodes("/mapper/resultMap"));
      this.sqlElement(context.evalNodes("/mapper/sql"));
      this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } else {
       throw new BuilderException("Mapper's namespace cannot be empty");
    }

總之一句話瓢喉,就是解析標(biāo)簽的屬性
configuration.addLoadedResource(this.resource):標(biāo)記該資源已被加載
bindMapperForNamespace():這個就是綁定namespace對應(yīng)的接口類和xml的關(guān)聯(lián)宁赤,這里就為后面接口調(diào)用xml方法做好了準(zhǔn)備。具體實現(xiàn):

        String namespace = this.builderAssistant.getCurrentNamespace();
        if (namespace != null) {
            Class boundType = null;

            try {
                boundType = Resources.classForName(namespace);
            } catch (ClassNotFoundException var4) {
            }

            if (boundType != null && !this.configuration.hasMapper(boundType)) {
                this.configuration.addLoadedResource("namespace:" + namespace);
                this.configuration.addMapper(boundType);
            }
        }

this.parsePendingResultMaps():這個方法從名稱上來理解是解析一些等待處理的resultMap栓票,這里可以回過頭來查看一下configurationElement(this.parser.evalNode("/mapper"))中的resultMapElements(context.evalNodes("/mapper/resultMap"))方法决左,在這個方法中有這么一段代碼:

ResultMapResolver resultMapResolver = new ResultMapResolver(this.builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
   return resultMapResolver.resolve();
} catch (IncompleteElementException var14) {
  this.configuration.addIncompleteResultMap(resultMapResolver);
  throw var14;
}

可以看到,當(dāng)在resultMapResolver.resolve()拋出異常時走贪,會調(diào)用configuration.addIncompleteResultMap(resultMapResolver)方法來存儲這些處理不了的或者說叫做處理異常的resultMap佛猛,parsePendingResultMaps()實際上就是對這些異常的或者說是無法處理的resultMap做一次補(bǔ)救,重新在進(jìn)行一次解析坠狡。
parsePendingCacheRefs():同理继找,即是針對configurationElement方法中的cacheRefElement(context.evalNode("cache-ref"))的補(bǔ)救措施
parsePendingStatements():和上面兩個方法一樣,針對configurationElement方法中的buildStatementFromContext(context.evalNodes("select|insert|update|delete"))的補(bǔ)救措施
以上的所有處理逃沿,都是在組裝configuration婴渡,然后通過new DefaultSqlSessionFactory(Configuration configuration),返回一個SqlSessionFactory對象凯亮,至此边臼,SqlSessionFactory創(chuàng)建完成。

MapperScannerConfigurer

在spring容器啟動的時候假消,我們會調(diào)用AbstractApplicationContext中的refresh()方法柠并,在refresh()方法中有調(diào)用了invokeBeanFactoryPostProcessors(beanFactory),該方法的執(zhí)行在 obtainFreshBeanFactory()后富拗,說明在執(zhí)行invokeBeanFactoryPostProcessors的時候我們的xml已經(jīng)完成了解析臼予,并且對應(yīng)beanDefiniton已經(jīng)生成,在這個基礎(chǔ)上我們進(jìn)入到invokeBeanFactoryPostProcessors方法的實現(xiàn)中來:

PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

繼續(xù)進(jìn)入到invokeBeanFactoryPostProcessors方法中啃沪,如圖:


調(diào)用鏈路.png

進(jìn)入到MapperScannerConfigurer中的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.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

重點(diǎn)關(guān)注scan方法:

scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

然后一直來到:

  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 {
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

doScan方法:

    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
        for (String basePackage : basePackages) {
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            for (BeanDefinition candidate : candidates) {
                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                candidate.setScope(scopeMetadata.getScopeName());
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                if (candidate instanceof AbstractBeanDefinition) {
                    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                }
                if (candidate instanceof AnnotatedBeanDefinition) {
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                }
                if (checkCandidate(beanName, candidate)) {
                    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    definitionHolder =
                            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    beanDefinitions.add(definitionHolder);
                    registerBeanDefinition(definitionHolder, this.registry);
                }
            }
        }
        return beanDefinitions;
    }

這里主要是解析包路徑下的接口類粘拾,然后解析成為beanDefinition返回,然后把對應(yīng)beanDefinition注冊到容器中谅阿,為后面生成對應(yīng)的bean做準(zhǔn)備半哟。
再來仔細(xì)看一下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");
      }

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      definition.setBeanClass(this.mapperFactoryBean.getClass());

      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;
      }

      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

      if (!explicitFactoryUsed) {
        if (logger.isDebugEnabled()) {
          logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        }
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }

主要有這些操作:
1.設(shè)置beanClassName
2.設(shè)置beanClass签餐,把原來的BeanClass的類型替換成了MapperFactoryBean類型寓涨,這個是Mapper接口加載定義階段最重要的一步。是生成代理類的關(guān)鍵氯檐。MapperFactoryBean實現(xiàn)了FactoryBean接口戒良,實現(xiàn)了FactoryBean接口的類型在調(diào)用getBean(beanName)既通過名稱獲取對象時,返回的對象不是本身類型的對象冠摄,而是通過實現(xiàn)接口中的getObject()方法返回的對象糯崎,MapperFactoryBean實現(xiàn)了FactoryBean接口InitializingBean接口,在對象初始化的時候會調(diào)用它的afterPropertiesSet方法河泳,該方法中首先調(diào)用了checkDaoConfig()方法沃呢,MapperFactoryBean重載的checkDaoConfig()如下:

  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

configuration.addMapper(this.mapperInterface)方法重點(diǎn)關(guān)注,最終實現(xiàn)是在MapperRegistry中:

  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

parser.parse()完成了對mapper對應(yīng)xml的解析成MappedStatement拆挥,并添加到了configuration對象中,這里的configuration也就是我們上面提到的new Configuration()創(chuàng)建的那個對象(非常重要)薄霜。

mapper接口的定義在bean加載階段會被替換成MapperFactoryBean類型,在spring容器初始化的時候會給我們生成MapperFactoryBean類型的對象纸兔,在該對象生成的過程中調(diào)用afterPropertiesSet()方法惰瓜,為我們生成了一個MapperProxyFactory類型的對象存放于Configuration里的MapperRegistry對象中,同時解析了mapper接口對應(yīng)的xml文件汉矿,把每一個方法解析成一個MappedStatement對象崎坊,存放于Configuration里mappedStatements這個Map集合中。

緊跟著來看一下MapperFactoryBean的getObject()方法:

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

跟蹤getMapper方法一直到MapperProxyFactory的newInstance(SqlSession sqlSession):

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

豁然開朗洲拇,這里就是創(chuàng)建代理類的地方奈揍,被代理對象為MapperProxy,正是這個MapperProxy,為后面的調(diào)用鏈路赋续,提供了核心的功能打月。

3.添加屬性addToConfig
4.判斷配置的是sqlSessionFactoryBeanName還是sqlSessionFactory,最終都轉(zhuǎn)換為sqlSessionFactory實例(就是我們上面創(chuàng)建的sqlSessionFactory的引用)
5.轉(zhuǎn)換設(shè)置的sqlSessionTemplate
6.設(shè)置自動裝配方式,默認(rèn)是根據(jù)類型裝配

以上就是mybatis對應(yīng)兩個配置項的加載過程梳理蚕捉,這兩個bean在容器啟動后就加入到了spring的容器中去了奏篙,接下來我們繼續(xù)來看我們在使用的時候,調(diào)用的鏈路是如何的迫淹。

Mybatis調(diào)用鏈路解析

service.select():service為代理接口類秘通,select()為執(zhí)行方法,首先就會進(jìn)入到MapperProxy中執(zhí)行invoke方法:

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

mapperMethod.execute(sqlSession, args)是具體執(zhí)行操作的方法:

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

這里會判斷是具體的操作類型是增敛熬、刪肺稀、改還是查,這里以查詢列表為例進(jìn)行分析
executeForMany:最終進(jìn)入到selectList方法:

  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

先從configuration中找到MappedStatement应民,再使用executor的query方法查詢數(shù)據(jù)庫话原,這里我們看看這個executor是個什么東西夕吻,是如何生成的。
回到MapperProxy中繁仁,在調(diào)用execute方法時傳入了sqlSession參數(shù)涉馅,最后我們在對數(shù)據(jù)庫做操作時,也是使用的這個sqlSession做的查詢黄虱,這個sqlSession根據(jù)我們前面的分析應(yīng)該是一個DefaultSqlSession稚矿,恰好executor正好是這個類中的一個屬性,追溯到代理類創(chuàng)建的地方捻浦,也就是是MapperFactoryBean中晤揣,里面有一個getSqlSession()的操作,實際上是調(diào)用的SqlSessionDaoSupport的getSqlSession(),返回的就是一個sqlSession,在此類中朱灿,查看sqlSession值的來源昧识,總共有兩處:

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

  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSession = sqlSessionTemplate;
    this.externalSqlSession = true;
  }

其本質(zhì)是一個SqlSessionTemplate類型的,我們再到SqlSessionTemplate去看看它的構(gòu)造方法:

  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;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }

其中包含了不少的內(nèi)容盗扒,特別留意一下sqlSessionProxy ,這里的sqlSessionProxy實際上是一個代理類滞诺,代理對象是位于本類中的一個內(nèi)部類SqlSessionInterceptor。好了环疼,我們回到上面的查詢方法selectList中來习霹,這個selectList實際上調(diào)用的就是SqlSessionTemplate中的selectList:

  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    return this.sqlSessionProxy.<E> selectList(statement, parameter, rowBounds);
  }

既然sqlSessionProxy是代理類,selectList就會調(diào)用SqlSessionInterceptor中的invoke方法:

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      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);
        }
      }
    }

進(jìn)入到getSqlSession中炫隶,一直跟蹤到DefaultSqlSessionFactory:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

會發(fā)現(xiàn)淋叶,原來excutor是在這里創(chuàng)建的,并且調(diào)用的configuration里的方法:

final Executor executor = configuration.newExecutor(tx, execType);

進(jìn)入到configuration中看它的實現(xiàn):

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

根據(jù)指定不同的執(zhí)行器類型來創(chuàng)建執(zhí)行器,常用的就是SimpleExecutor伪阶,在這里面重點(diǎn)關(guān)注一下:

if (cacheEnabled) {
    executor = new CachingExecutor(executor);
}

cacheEnabled是configuration的屬性煞檩,意思就是說我們可以配置這個開關(guān),網(wǎng)上查一下mybatis配置項我們可以得知栅贴,這個是開啟Mybatis二級緩存的開關(guān)斟湃,也就是說,二級緩存使用executor就是CachingExecutor檐薯。轉(zhuǎn)過頭來凝赛,我們來看executor.query方法,如果是使用SimpleExecutor坛缕,則進(jìn)入BaseExecutor執(zhí)行query,這里查詢的邏輯如下:

List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }

先是從localCache中去取墓猎,有值則handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);然后返回,如果不存在赚楚,則調(diào)用queryFromDatabase即從數(shù)據(jù)庫中查詢毙沾,這個localCache就是Mybatis的一級緩存:

 private Map<Object, Object> cache = new HashMap<Object, Object>();

  ...
  
  public Object getObject(Object key) {
    return cache.get(key);
  }

其本質(zhì)就是一個HashMap,查詢條件組裝為key,查詢結(jié)果為value宠页,在執(zhí)行查詢獲得結(jié)果后左胞,如果localcache設(shè)置的作用域為STATEMENT寇仓,則會對localcache進(jìn)行clear(),我們常用的即是STATEMENT,所以每次查詢數(shù)據(jù)都是從數(shù)據(jù)庫中獲取的最新數(shù)據(jù)烤宙,沒有延遲遍烦。
如果我們開啟了二級緩存,則是調(diào)用CachingExecutor中的query方法门烂,實現(xiàn)如下:

  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

首先是從mapperstatement中去獲取一個cache,這個cache位于mapperstatement中乳愉,就說明二級緩存是基于mapperstatement的兄淫,如果cache不為空屯远,則flushCacheIfRequired,這里就是當(dāng)我們開啟二級緩存時,也可以讓他每次獲取數(shù)據(jù)庫最新的數(shù)據(jù)捕虽,則是配置刷新cache,這個配置就是mapperstatement中的flushCacheRequired.
接下來的邏輯就簡單了慨丐,從緩存中取,取到值則直接返回泄私,否則就從數(shù)據(jù)庫去查詢房揭,然后放入緩存中。
這就是mybatis一級緩存和二級緩存的實現(xiàn)方式晌端。
返回了查詢結(jié)果捅暴,則按照原調(diào)用鏈路逐級return,一直到返回給調(diào)用的應(yīng)用咧纠,這就是一個完整的mybatis接口調(diào)用鏈路蓬痒。

總結(jié)

Mybatis的實現(xiàn)方式其核心就是使用了動態(tài)代理來實現(xiàn)各種操作,查詢獲取數(shù)據(jù)庫連接漆羔、mybatis事務(wù)的管理梧奢,都是通過動態(tài)代理來實現(xiàn)了。在實際開發(fā)中演痒,我們接觸的mybatis通用mapper亲轨,mybaits-plus,一級分頁插件PageInterceptor的實現(xiàn)都是在這個基礎(chǔ)上進(jìn)行的擴(kuò)展鸟顺,為開發(fā)者提供了更簡潔的開發(fā)方式惦蚊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市讯嫂,隨后出現(xiàn)的幾起案子养筒,更是在濱河造成了極大的恐慌,老刑警劉巖端姚,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晕粪,死亡現(xiàn)場離奇詭異胳岂,居然都是意外死亡迫皱,警方通過查閱死者的電腦和手機(jī)命浴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門厕倍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人尚氛,你說我怎么就攤上這事诀诊。” “怎么了阅嘶?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵属瓣,是天一觀的道長。 經(jīng)常有香客問我讯柔,道長抡蛙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任魂迄,我火速辦了婚禮粗截,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘捣炬。我一直安慰自己熊昌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布湿酸。 她就那樣靜靜地躺著婿屹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪推溃。 梳的紋絲不亂的頭發(fā)上昂利,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天,我揣著相機(jī)與錄音美莫,去河邊找鬼页眯。 笑死,一個胖子當(dāng)著我的面吹牛厢呵,可吹牛的內(nèi)容都是我干的窝撵。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼襟铭,長吁一口氣:“原來是場噩夢啊……” “哼碌奉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起寒砖,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤赐劣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后哩都,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體魁兼,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年漠嵌,在試婚紗的時候發(fā)現(xiàn)自己被綠了咐汞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盖呼。...
    茶點(diǎn)故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖化撕,靈堂內(nèi)的尸體忽然破棺而出几晤,到底是詐尸還是另有隱情,我是刑警寧澤植阴,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布蟹瘾,位于F島的核電站,受9級特大地震影響掠手,放射性物質(zhì)發(fā)生泄漏憾朴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一惨撇、第九天 我趴在偏房一處隱蔽的房頂上張望伊脓。 院中可真熱鬧府寒,春花似錦魁衙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至纤房,卻和暖如春纵隔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背炮姨。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工捌刮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人舒岸。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓绅作,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蛾派。 傳聞我的和親對象是個殘疾皇子俄认,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容