Mybatis在spring中的工作原理

在日常的开发工作中,常接触的持久层框架主要是Hibernate、Mybatis和spring-jdbc,其中spring-jdbc的封装程度相比之下没有另外两个框架高,所以在平时的开发中,使用Hibernate、Mybatis的较多,Hibernate更偏向与对象与关系的映射,是面向对象的一个很好的体现,但是在有些场景中,则不存在那么多的关系,使用Hibernate必要性就没那么高,况且相比Mybatis,Hibernate的学习成本要稍高一些,上手难度就更大一点,所以在我们的日常开发中,选用Mybatis的情况更多一些,当然,Hibernate框架的强大是不容否定的,感兴趣的朋友可以花点时间去做一下深入的研究,在这里我就主要分享一下我在阅读了部分Mybatis源码后的一些总结。

通过源码来看Mybatis的工作流程

我们这里也拿一个传统的spring web项目来帮助分析。虽然现在大多的项目都基于spring-boot在开发,但是其根本和传统的spring是一样的,只是在某些方面作了一些便利的操作,让开发者更简单的编码。如果要在spring项目中使用Mybatis,首先在我们是spring配置的xml文件中都会配置以下这些相关配置项:

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

配置主要是数据源、xml扫描规则,dao接口的扫描包路径
我们来看一下SqlSessionFactoryBean这个类,它实现了FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent>这三个接口,FactoryBean代表可以getObject()获取对象,InitializingBean可以在初始化的时候作扩展,ApplicationListener代表它本身可以进行事件监听,对应配置文件,我们可以看到它设置了dataSource、mapperLocations、typeAliasesPackage和plugins这几个属性,这些都对应到了SqlSessionFactoryBean中的同名的属性,我们暂且看到这个地方,后面用到这些属性时再作详细分析。
然后我们在来看下MapperScannerConfigurer这个类,跟上面的SqlSessionFactoryBean一样,也是设置一些属性,具体的功能在后面做详细分析。

SqlSessionFactory的创建

在spring容器中,一切皆bean的思想是不会变的,从配置的标签上看,我们上面配置的两个类,同样也是两个bean,那么是bean,就会被spring容器所加载管理。我在spring容器启动流程一文中介绍了spring bean的加载创建过程,有兴趣的可以去读一读。接下来我们回到这两个bean上来,首先看看SqlSessionFactoryBean,在之前的文章中,我们提到过一种特殊的bean叫FactoryBean,恰好SqlSessionFactoryBean就是这样的一种bean,根据实现的接口可以得知,这是一个SqlSessionFactory类型的FactoryBean,在之前介绍的bean创建的时候,有一个方法叫做doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName),该方法中最终创建bean的方法则是调用object = factory.getObject(),即使用传入的FactoryBean的getObject()方法来获取bean的实例,那么我们回到SqlSessionFactoryBean中来找找看getObejct()方法的实现:

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

    return this.sqlSessionFactory;
  }

这里返回的就是一个sqlSessionFactory,进入afterPropertiesSet()方法查看,最终会看到是调用同类中的一个buildSqlSessionFactory(),由于这个方法代码过多,我这里就只取出我们重点关注的那部分代码:

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

这里因为是创建实例,所有代码块的configuration是为null的,所以在以上代码前是有一个new Configuration()的操作,这个configuration则是整个sqlsessionfactory的核心内容,后面的所有操作都是基于这个configuration。代码片段中的this.mapperLocations就是我们在文章开始处xml配置的mapperLocations属性,这里我的理解就是加载mapperLocations下的所有xml文件,然后存放到configuration中。

xmlMapperBuilder.parse();

进入这个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标签的属性及子标签的各种属性,如mapper标签的属性namespace,子标签如:cache-ref | cache | resultMap* | parameterMap* | sql* | insert* | update* | delete* | select* 等的id、type、parameter、property、javaType、jdbcType等属性,
详细操作可以看一下代码片段:

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

总之一句话,就是解析标签的属性
configuration.addLoadedResource(this.resource):标记该资源已被加载
bindMapperForNamespace():这个就是绑定namespace对应的接口类和xml的关联,这里就为后面接口调用xml方法做好了准备。具体实现:

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

可以看到,当在resultMapResolver.resolve()抛出异常时,会调用configuration.addIncompleteResultMap(resultMapResolver)方法来存储这些处理不了的或者说叫做处理异常的resultMap,parsePendingResultMaps()实际上就是对这些异常的或者说是无法处理的resultMap做一次补救,重新在进行一次解析。
parsePendingCacheRefs():同理,即是针对configurationElement方法中的cacheRefElement(context.evalNode("cache-ref"))的补救措施
parsePendingStatements():和上面两个方法一样,针对configurationElement方法中的buildStatementFromContext(context.evalNodes("select|insert|update|delete"))的补救措施
以上的所有处理,都是在组装configuration,然后通过new DefaultSqlSessionFactory(Configuration configuration),返回一个SqlSessionFactory对象,至此,SqlSessionFactory创建完成。

MapperScannerConfigurer

在spring容器启动的时候,我们会调用AbstractApplicationContext中的refresh()方法,在refresh()方法中有调用了invokeBeanFactoryPostProcessors(beanFactory),该方法的执行在 obtainFreshBeanFactory()后,说明在执行invokeBeanFactoryPostProcessors的时候我们的xml已经完成了解析,并且对应beanDefiniton已经生成,在这个基础上我们进入到invokeBeanFactoryPostProcessors方法的实现中来:

PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

继续进入到invokeBeanFactoryPostProcessors方法中,如图:


调用链路.png

进入到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));
  }

重点关注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返回,然后把对应beanDefinition注册到容器中,为后面生成对应的bean做准备。
再来仔细看一下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.设置beanClassName
2.设置beanClass,把原来的BeanClass的类型替换成了MapperFactoryBean类型,这个是Mapper接口加载定义阶段最重要的一步。是生成代理类的关键。MapperFactoryBean实现了FactoryBean接口,实现了FactoryBean接口的类型在调用getBean(beanName)既通过名称获取对象时,返回的对象不是本身类型的对象,而是通过实现接口中的getObject()方法返回的对象,MapperFactoryBean实现了FactoryBean接口InitializingBean接口,在对象初始化的时候会调用它的afterPropertiesSet方法,该方法中首先调用了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)方法重点关注,最终实现是在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对应xml的解析成MappedStatement,并添加到了configuration对象中,这里的configuration也就是我们上面提到的new Configuration()创建的那个对象(非常重要)。

mapper接口的定义在bean加载阶段会被替换成MapperFactoryBean类型,在spring容器初始化的时候会给我们生成MapperFactoryBean类型的对象,在该对象生成的过程中调用afterPropertiesSet()方法,为我们生成了一个MapperProxyFactory类型的对象存放于Configuration里的MapperRegistry对象中,同时解析了mapper接口对应的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);
  }

豁然开朗,这里就是创建代理类的地方,被代理对象为MapperProxy,正是这个MapperProxy,为后面的调用链路,提供了核心的功能。

3.添加属性addToConfig
4.判断配置的是sqlSessionFactoryBeanName还是sqlSessionFactory,最终都转换为sqlSessionFactory实例(就是我们上面创建的sqlSessionFactory的引用)
5.转换设置的sqlSessionTemplate
6.设置自动装配方式,默认是根据类型装配

以上就是mybatis对应两个配置项的加载过程梳理,这两个bean在容器启动后就加入到了spring的容器中去了,接下来我们继续来看我们在使用的时候,调用的链路是如何的。

Mybatis调用链路解析

service.select():service为代理接口类,select()为执行方法,首先就会进入到MapperProxy中执行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)是具体执行操作的方法:

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

这里会判断是具体的操作类型是增、删、改还是查,这里以查询列表为例进行分析
executeForMany:最终进入到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方法查询数据库,这里我们看看这个executor是个什么东西,是如何生成的。
回到MapperProxy中,在调用execute方法时传入了sqlSession参数,最后我们在对数据库做操作时,也是使用的这个sqlSession做的查询,这个sqlSession根据我们前面的分析应该是一个DefaultSqlSession,恰好executor正好是这个类中的一个属性,追溯到代理类创建的地方,也就是是MapperFactoryBean中,里面有一个getSqlSession()的操作,实际上是调用的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;
  }

其本质是一个SqlSessionTemplate类型的,我们再到SqlSessionTemplate去看看它的构造方法:

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

其中包含了不少的内容,特别留意一下sqlSessionProxy ,这里的sqlSessionProxy实际上是一个代理类,代理对象是位于本类中的一个内部类SqlSessionInterceptor。好了,我们回到上面的查询方法selectList中来,这个selectList实际上调用的就是SqlSessionTemplate中的selectList:

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

既然sqlSessionProxy是代理类,selectList就会调用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);
        }
      }
    }

进入到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();
    }
  }

会发现,原来excutor是在这里创建的,并且调用的configuration里的方法:

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

进入到configuration中看它的实现:

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

根据指定不同的执行器类型来创建执行器,常用的就是SimpleExecutor,在这里面重点关注一下:

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

cacheEnabled是configuration的属性,意思就是说我们可以配置这个开关,网上查一下mybatis配置项我们可以得知,这个是开启Mybatis二级缓存的开关,也就是说,二级缓存使用executor就是CachingExecutor。转过头来,我们来看executor.query方法,如果是使用SimpleExecutor,则进入BaseExecutor执行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);然后返回,如果不存在,则调用queryFromDatabase即从数据库中查询,这个localCache就是Mybatis的一级缓存:

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

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

其本质就是一个HashMap,查询条件组装为key,查询结果为value,在执行查询获得结果后,如果localcache设置的作用域为STATEMENT,则会对localcache进行clear(),我们常用的即是STATEMENT,所以每次查询数据都是从数据库中获取的最新数据,没有延迟。
如果我们开启了二级缓存,则是调用CachingExecutor中的query方法,实现如下:

  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,这里就是当我们开启二级缓存时,也可以让他每次获取数据库最新的数据,则是配置刷新cache,这个配置就是mapperstatement中的flushCacheRequired.
接下来的逻辑就简单了,从缓存中取,取到值则直接返回,否则就从数据库去查询,然后放入缓存中。
这就是mybatis一级缓存和二级缓存的实现方式。
返回了查询结果,则按照原调用链路逐级return,一直到返回给调用的应用,这就是一个完整的mybatis接口调用链路。

总结

Mybatis的实现方式其核心就是使用了动态代理来实现各种操作,查询获取数据库连接、mybatis事务的管理,都是通过动态代理来实现了。在实际开发中,我们接触的mybatis通用mapper,mybaits-plus,一级分页插件PageInterceptor的实现都是在这个基础上进行的扩展,为开发者提供了更简洁的开发方式。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容