springboot-mybatis 源码剖析

1. 自动装配入口

  我们知道,Mybatis 的几个重要组件,SqlSessionFactoryBuilder, SqlSessionFactory, SqlSession, Mapper。而在与 Spring 结合后,还需要一个 SqlSessionTemplate,其中 SqlSessionFactoryBuilder 用完就丢,SqlSession 是通过 SqlSessionFactory 创建,而 Mapper 是手写的,因此我们装配的就是 SqlSessionFactory 和 SqlSessionTemplate。

我们的通过这个依赖在 springboot 环境下引入 mybatis

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>

这个包中又引用了

<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
</dependency>

这是一个自动装配的包,所以看 META-INF/spring.factories 文件,

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

由此加载这几个类。

  • EnableAutoConfiguration:启动自动装配
  • MybatisLanguageDriverAutoConfiguration:这个类的配置是对各个语言的支持,比如Thymeleaf, Velocity,LegacyVelociy, FreeMarker等视图组件的支持,不重要,跳过
  • MybatisAutoConfiguration:注册重要组件的

MybatisAutoConfiguration 类定义:

//表示这是一个Spring配置类
@Configuration 
//这个类需要在classpath中存在SqlSessionFactory和SqlSessionFactoryBean时才生效
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) --
//这个类需要有一个DataSource的Canidate注册到Spring容器中 
@ConditionalOnSingleCandidate(DataSource.class)
//使MybatisProperties注解类生效
@EnableConfigurationProperties({MybatisProperties.class})
//需要在DataSourceAutoConfiguration和MybatisLanguageDriverAutoConfiguration自动配置之后执行
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {
   
}

从类定义上看,我们需要的几个元素。

    1. SqlSessionFactory 和 SqlSessionFactoryBean,其中 SqlSessionFactory 是用来创建 SqlSession 的
    1. DataSource 数据源在 mybatis 之前加载好:https://www.cnblogs.com/cnff/p/18179146
    1. MybatisProperties 配置的加载

2. MybatisProperties 配置文件

Springboot 环境下的 mybatis 相关的配置,比如 xml 位置。其中读取配配置是通过 MybatisProperties 类实现。

MybatisProperties 部分源码

//springboot 中读取配置
@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {
  public static final String MYBATIS_PREFIX = "mybatis";

  private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();

  /**
   * Location of MyBatis xml config file.
   * xml 配置文件路径
   */
  private String configLocation;

  /**
   * Locations of MyBatis mapper files.
   * xml 路径
   */
  private String[] mapperLocations;

  /**
   * Packages to search type aliases. (Package delimiters are ",; \t\n")
   */
  private String typeAliasesPackage;

  /**
   * The super class for filtering type alias. If this not specifies, the MyBatis deal as type alias all classes that
   * searched from typeAliasesPackage.
   */
  private Class<?> typeAliasesSuperType;

  /**
   * Packages to search for type handlers. (Package delimiters are ",; \t\n")
   */
  private String typeHandlersPackage;

  /**
   * Indicates whether perform presence check of the MyBatis xml config file.
   */
  private boolean checkConfigLocation = false;

  /**
   * Execution mode for {@link org.mybatis.spring.SqlSessionTemplate}.
   */
  private ExecutorType executorType;

  /**
   * The default scripting language driver class. (Available when use together with mybatis-spring 2.0.2+)
   */
  private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;

  /**
   * Externalized properties for MyBatis configuration.
   */
  private Properties configurationProperties;

  /**
   * A Configuration object for customize default settings. If {@link #configLocation} is specified, this property is
   * not used.
   */
  @NestedConfigurationProperty
  private Configuration configuration;
}

读取 mybatis 为前缀的配置。

## mapper.xml 文件路径
mybatis:
  mapper-locations: classpath*:dao/*.xml

属性配置 时候使用了配置,将读取到的配置放到 SqlSessionFactory 中。

3. SqlSessionFactory 的注册

SqlSessionFactory 是用来创建 SqlSession 的,Springboot 启动的时候就会注册一个 bean 到容器中。 在 SpringBoot 环境下,SqlSessionFactory 是 MybatisAutoConfiguration.sqlSessionFactory() 来定义注册的。

1. SqlSessionFactoryBean 用来创建 SqlSessionFactory

1. MybatisAutoConfiguration.sqlSessionFactory():最终调用 SqlSessionFactoryBean.getObject(), 返回一个 SqlSessionFactory 实例

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {

  //SqlSessionFactoryBean 是用来创建 SqlSessionFactory 的  
  SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
  
  //关于 dataSource
  factory.setDataSource(dataSource);
  factory.setVfs(SpringBootVFS.class);
  if (StringUtils.hasText(this.properties.getConfigLocation())) {
    factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
  }
  
  //1. 关于配置的一些东西
  applyConfiguration(factory);
  。。。。
  
  //2. 这里默认会返回一个DefaultSqlSessionFactory对象
  return factory.getObject();
}

//1. 配置信息
private void applyConfiguration(SqlSessionFactoryBean factory) {
  //3. 从 properties 属性,也就是 MybatisProperties 实例中获取 Configuration
  Configuration configuration = this.properties.getConfiguration();
  if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
    //如果没有从 MybatisProperties 中获取到,就直接 new 一个 Configruation
    configuration = new Configuration();
  }
  if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
    for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
      customizer.customize(configuration);
    }
  }
  //4. 将 Configruation 设置给 SqlSessionFactory
  factory.setConfiguration(configuration);
}
    1. 给 SQL SessionFactory 整了一个 Configuration,这个非常重要,创建 SqlSessionFactory 的Configuration 以及后面很多东西都是使用这个 Configuration 的。

SqlSessionFactoryBean.getObject():一系列调用,最终通过 buildSqlSessionFactory() 返回一个 SqlSessionFactory 实例

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

  return this.sqlSessionFactory;
}

@Override
public void afterPropertiesSet() throws Exception {
  notNull(dataSource, "Property 'dataSource' is required");
  notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
  state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
      "Property 'configuration' and 'configLocation' can not specified with together");

  this.sqlSessionFactory = buildSqlSessionFactory();
}

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
   
    //前面忽略
    。。。
    
    //1. 解析 xml 的
    if (this.mapperLocations != null) {
      if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
      } else {
        //2. 每个 xml 这里都解析为一个资源,对每一个 resource 的解析
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
          try {
            //2. 创建一个 XMLMapperBuilder 对象
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            //3. 调用解析
            xmlMapperBuilder.parse();
          } catch (Exception e) {
            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
          } finally {
            ErrorContext.instance().reset();
          }
          LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }
    
    //4. 建造者模式,最终创建一个 SqlSessionFactory
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
    1. this.mapperLocations 就是根据配置文件中解析的 xml。
    mybatis:
      mapper-locations: classpath*:dao/*.xml
    
    1. 每个 xml 文件,使用一个 xmlMapperBuilder 来解析
    1. 最终创建一个 SqsSessionFactory

    SqlSessionFactoryBuilder.build()

    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }
    

    最终是 new 了一个 DefaultSqlSessionFactory 的 bean;

2. XML 配置文件的解析

1. XMLMapperBuilder.parse():开始解析 xml

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    //1. 从 mapper 标签开始解析
    configurationElement(parser.evalNode("/mapper"));

    //6. mapper 路径,添加到一个 configure 的 loadedResources 属性中
    configuration.addLoadedResource(resource);

    //7. 绑定 Mapper 到 configure
    bindMapperForNamespace();
  }
  
  //8. 转换什么东西的,看不懂
  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

//2. 此时 context 就是一个 <mapper> 以内的 dom
private void configurationElement(XNode context) {
  try {
    
    //3. namespace 命名空间属性,存起来
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.isEmpty()) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    builderAssistant.setCurrentNamespace(namespace);
    
    //4. 解析各种标签
    cacheRefElement(context.evalNode("cache-ref"));
    cacheElement(context.evalNode("cache"));
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    sqlElement(context.evalNodes("/mapper/sql"));
    //5. 解析增删改查标签
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}
    1. 这里开始解析每一个标签。
    1. 这里是对 mapper 中支持的每个标签做解析,大致就是将解析的内容放到 configuration 属性中
    1. 增删改查标签的解析
    1. 绑定 mapper,见过程 6

2. XMLMapperBuilder.buildStatementFromContext():解析增删改查标签

private void buildStatementFromContext(List<XNode> list) {
    //1. 盲猜这里是多数据源设计的
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    //2. 调用重载方法
    buildStatementFromContext(list, null);
}

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  //3. 对每一个增删改查标签做处理
  for (XNode context : list) {

    //3. 又是一个对象,这方法栈真深啊,看名字就是解析一个增删改查操作,跟 jdbc statement 有关的
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      //4. 具体的一个解析逻辑
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

3. XMLStatementBuilder.parseStatementNode():这个方法才真真的解析每个增删改查标签

public void parseStatementNode() {

    //1. 解析标签 id
    String id = context.getStringAttribute("id");
    
    //2. 应该跟数据源有关
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }
    
    //3. 标签类型(select|update|add|delete)
    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    //4. 缓存的属性
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);

    //5. 如果结果是 list,map 这种,保证结果顺序的,默认 false
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
    
    //6. 解析内部 <include> 标签
    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());
    
    //7. 参数类型属性
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    
    //8. 语言驱动属性
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);
    
    //9. 解析 <selectKey> 标签
    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    //10. 解析处理替换 <selectKey> 和 <Include> 标签后
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }
    
    //11. 源 sql
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

    //12. statement 类型
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));

    //13. 其他各种属性的解析
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");
    
    //14. 又是调用一个方法,继续拆解
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

这个方法解析的大概就是这些:

    1. 标签的解析。
    1. 标签的解析
    1. statementType,编译类型
StatementType 枚举
public enum StatementType {
  STATEMENT, PREPARED, CALLABLE
}
  • STATEMENT:直接操作sql,不进行预编译,获取数据:$-Statement
  • PREPARED:预处理,参数,进行预编译,获取数据:#,默认
  • CALLABLE:执行存储过程————CallableStatement

5. MapperBuilderAssistant.addMappedStatement():创建一个 statement,并设置到 Configure 中

public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class<?> parameterType,
      String resultMap,
      Class<?> resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {

    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);
    
    //1. 是否 select
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    
    //2. Statement 构建器
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);
    
    //3. 应该是参数类型吧
    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }
    
    //4. 构建 MappedStatement,并且赋值到 Configuration 中
    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
}
    1. MappedStatement.cache() 方法,将解析的缓存放到 MappedStatement 中
    1. configuration 持有一个存放解析完成的 Statement 对象的 map,Map<String, MappedStatement> mappedStatements,key 是全限定名
      public void addMappedStatement(MappedStatement ms) {
        mappedStatements.put(ms.getId(), ms);
      }
    

6. XMLMapperBuilder.bindMapperForNamespace():绑定类对象
定位调用位置

private void bindMapperForNamespace() {
    
    //1. 命名空间
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
  
        //2. 根据命名空间,反射 Mapper 接口
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        // ignore, bound type is not required
      }
      if (boundType != null && !configuration.hasMapper(boundType)) {
        // Spring may not know the real resource name so we set a flag
        // to prevent loading again this resource from the mapper interface
        // look at MapperAnnotationBuilder#loadXmlResource
        
        //3. 命名空间添加到 configure 的 loadResource 中
        configuration.addLoadedResource("namespace:" + namespace);

        //4. 反射的 class 添加到 configure
        configuration.addMapper(boundType);
      }
    }
}
    1. 这里应该就是通过 xml 的 nameSpace,将 Mapper 接口和 xml 关联起来

7. Configuration 的 addMapper()

public <T> void addMapper(Class<T> type) {
  mapperRegistry.addMapper(type);
}

configure 中有个 mapperRegistry 参数,用来记录反射的 mapper

8. MapperRegistry.addMapper(): 将反射类,放到 mapperRegistry 的 knowMappers 中(一个 map)

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 {

        //放到一个 Map 中
        knownMappers.put(type, new MapperProxyFactory<>(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);
        }
      }
    }
}

3. 总结

    1. 这个过程最终创建了一个 SqlSessionFactory 实例 Bean
    1. 通过对 xml 的扫描,每个 xml 使用一个 XMLMapperBuilder 来解析。
    1. XMLMapperBuilder 解析增删改查的时候,又通过 XMLStatementBuilder 来解析每一个增删改查标签。
    1. SqlSessionFactory.Configure.mapperRegistry.knowMappers,使用 map 结构存放通过 xml 的 nameSpace 属性反射的接口信息,key 是接口本身类。
    1. sqlSessionFactory.Configure.mappedStatements,存放解析后的 xml,使用 map 结构存放 xml 解析后的信息,key 是方法全限定名。

至此,我们额 SqlSessionFactory 加载完了。Mapper 接口信息,还有 xml 解析信息都放在 configurtion 属性中。

4. SqlSessionTemplate 的注册

SqlSessionTemplate 是 Spring 提供的一个对 MyBatis 的 SqlSession 的一个增强类,它的作用就是将SqlSession与当前的事务所绑定, 而且是线程安全的,一个 SqlSessionTemplate 可以被多个dao所共享。每个数据源对应一个 SqlSessionTemplate。定义同样是在 MybatisAutoConfiguration 类中通过 MybatisAutoConfiguration 方法定义注册。

MybatisAutoConfiguration.sqlSessionTemplate() 通过 new 一个 SqlSessionTemplate 来创建 bean

@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  ExecutorType executorType = this.properties.getExecutorType();
  if (executorType != null) {
    return new SqlSessionTemplate(sqlSessionFactory, executorType);
  } else {
    return new SqlSessionTemplate(sqlSessionFactory);
  }
}

  这里创建了一个 SqlSessionTemplate 的 bean;

SqlSessionTemplate 四个属性:

//传入的SqlSessionFactory
private final SqlSessionFactory sqlSessionFactory;

//Executor类型
private final ExecutorType executorType;

//SqlSession的动态代理
private final SqlSession sqlSessionProxy;

private final PersistenceExceptionTranslator exceptionTranslator;

字段

类型

描述

sqlSessionFactory

SqlSessionFactory

session工厂

executorType

ExecutorType

Executor的三种类型类型:
· SIMPLE是默认执行器,根据对应的sql直接执行,不会做一些额外的操作。
· REUSE是可重用执行器,重用对象是Statement(即该执行器会缓存同一个sql的Statement,省去Statement的重新创建,优化性能)(即会重用预处理语句)
· BATCH执行器会重用预处理语句,并执行批量更新。

sqlSessionProxy

SqlSession

SqlSession代理对象,注册了SqlSessionInterceptor反射处理器,实际上的方法调用都是通过SqlSessionInterceptor反射实现的。

exceptionTranslator

PersistenceExceptionTranslator

Spring提供的接口,用于处理持久化框架的异常

最终调用的构造器

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;

  //这是 sqlSession 的一个代理
  this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
      new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}

5. SqlSession 的创建

  上述创建的 SqlSessionTemplate 其实是 SqlSession 的一个增强代理类,增强的逻辑在 SqlSessionInterceptor 类中,我们通过 SqlSessionTemplate 调用 selectOne,selectMap 等方法,最终是调用代理 this.sqlSessionProxy 的方法,最终是 SqlSessionInterceptor.invoke() 方法。

这里使用 jdk 代理,回忆一下 JDK 代理:https://www.cnblogs.com/cnff/p/17009123.html#2jdk-代理原理

SqlSessionInterceptor.invoke()

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      
      //获取当前事务下绑定的SqlSession,由于Spring通过ThreadLocal将线程与事务绑定,所以也可以认为获取的是当前线程绑定的SqlSession
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {

        //执行SqlSession的方法
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {

          //如果当前在非事务环境下允许,则强制commit一下,因为有些数据库要求在close()方法前要先调用commit()或rollback()
          // 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 {

        //关闭sqlSession
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

SqlSessionUtils.getSqlSession() 至此才真正开始获取一个 SqlSession。

SqlSession 表示一个会话连接。

SqlSessionUtils.getSqlSession() 方法获取一个 SqlSession

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

  notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
  notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

  //1. 通过工厂,获取一个 SqlSessionHolder,至于这个 SqlSessionHolder  是啥不管
  SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    
  //2. 从获取的这个 holder 里边获取 SqlSession
  SqlSession session = sessionHolder(executorType, holder);
  if (session != null) {
    return session;
  }
  
  //3. 如果前面没有拿到 sqlSession,这里打开一个新的 sqlSession
  LOGGER.debug(() -> "Creating a new SqlSession");
  session = sessionFactory.openSession(executorType);
  
  //4. 将刚刚新建的 sqlSession 注册一下
  registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

  return session;
}

这里分两种情况,第一种(1,2)是可以直接获取到 SqlSession,第二种(3,4)是新建 SqlSession。

1. 通过 TransactionSynchronizationManager.getResource() 来获取暂时不知道是啥的 holder

@Nullable
public static Object getResource(Object key) {

    //转换了下 key,具体逻辑不管
	Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
	
    //通过 doGetResource() 方法获取一个东西,具体是啥暂时不知道
    Object value = doGetResource(actualKey);
	if (value != null && logger.isTraceEnabled()) {
		logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
				Thread.currentThread().getName() + "]");
	}
	return value;
}

//一个 ThreadLocal 变量
private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<>("Transactional resources");

@Nullable
private static Object doGetResource(Object actualKey) {
	
    //这里就是从 ThreadLocal 中获取 key 为当前线程的一个 Map
    Map<Object, Object> map = resources.get();
	if (map == null) {
		return null;
	}
    //根据这个转换后的 key,从 map 中拿到一个 Object,暂时不知道是啥
	Object value = map.get(actualKey);
	// Transparently remove ResourceHolder that was marked as void...
	if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
		map.remove(actualKey);
		// Remove entire ThreadLocal if empty...
		if (map.isEmpty()) {
			resources.remove();
		}
		value = null;
	}
	return value;
}

这里就是从 ThreadLocal 中拿到当前线程的 Map,然后根据 sessionFactory 不知道转换了一个 actualKey,根据这个 key 从 Map 中拿一个东西(SqlSessionHolder )

2. SqlSessionUtil.sessionHolder(executorType, holder),从 holder 中拿到 SqlSession

private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) {
  SqlSession session = 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();

    LOGGER.debug(() -> "Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
    
    //直接从 holder 中 get 一个 SqlSession,好像没啥玄幻,跳过。重要的是下面怎么把 SqlSession 放到 holder 中
    session = holder.getSqlSession();
  }
  return session;
}

3. sessionFactory.openSession(executorType),这里才是真正的创建一个 SqlSession,以默认的 DefaultSqlSessionFactory 为例

@Override
public SqlSession openSession(ExecutorType execType) {
  return openSessionFromDataSource(execType, null, false);
}

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);
      //终于是 new 了一个 SqlSession,这里边传入了 configuration 配置信息,executor 执行器,autoCommit 是否自动提交
      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();
    }
  }

这里才是真正执行了一个创建 SqlSession 的操作。 configuration.newExecutor(tx, execType) 创建了一个执行器

4. SqlSessionUtil.registerSessionHolder(); 将刚刚创建的 Session 保存一下,1、2 取 sqlSession 的过程

private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
   SqlSessionHolder holder;
  if (TransactionSynchronizationManager.isSynchronizationActive()) {
    Environment environment = sessionFactory.getConfiguration().getEnvironment();

    if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
      LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");
      
      //这里创建了一个 holder,把刚刚创建的 SqlSession 放了进去,对应过程 2 从 holder 中取 SqlSession
      holder = new SqlSessionHolder(session, executorType, exceptionTranslator);

      //这里的 bindResource 对应过程 1 的 getResource
      TransactionSynchronizationManager.bindResource(sessionFactory, holder);

      TransactionSynchronizationManager
          .registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
      holder.setSynchronizedWithTransaction(true);
      holder.requested();
    } else {
      if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
        LOGGER.debug(() -> "SqlSession [" + session
            + "] was not registered for synchronization because DataSource is not transactional");
      } else {
        throw new TransientDataAccessResourceException(
            "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
      }
    }
  } else {
    LOGGER.debug(() -> "SqlSession [" + session
        + "] was not registered for synchronization because synchronization is not active");
  }
}

TransactionSynchronizationManager.bindResource() 将刚刚的 holder 放到 threadLocal 里

public static void bindResource(Object key, Object value) throws IllegalStateException {
    //老规矩,不知道啥样的转换规则
	Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
	Assert.notNull(value, "Value must not be null");

    //获取当前线程的 map
	Map<Object, Object> map = resources.get();
	// set ThreadLocal Map if none found
	if (map == null) {
		map = new HashMap<>();
		resources.set(map);
	}

    //把刚刚建好的 SqlSession 放进去,返回值是原来的 value
	Object oldValue = map.put(actualKey, value);
	// Transparently suppress a ResourceHolder that was marked as void...
	if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
		oldValue = null;
	}
	if (oldValue != null) {
		throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
				actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
	}
	if (logger.isTraceEnabled()) {
		logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
				Thread.currentThread().getName() + "]");
	}
}

总结: SqlSessionTemplat 获取 SqlSession 的过程,SqlSessionTemplate 本身是 SqlSession 的一个代理类,线程安全,用来绑定线程和 SqlSession。每次获取一个 SqlSession,首先从 ThreadLocal 中获取当前线程的 SqlSession,如果获取不到,那就 new 一个,然后放到 ThreadLocal 中。当然代码走到这里只是创建好了 SqlSessionTemplate,创建 SqlSession 啥的要到执行 mapper 时候才会执行。
这里并没有看事务相关的代码,之后另开一篇

6. Mapper 的扫描

Mapper 的解析入口,同样是在 MybatisAutoConfiguration 中,有 MapperScannerRegistrarNotFoundConfigurationAutoConfiguredMapperScannerRegistrar 两个内部类。

这两个内部类,实际上就代表了 @Mapper 和 @MapperScan 的加载规则

//将此类注册成一个配置类
@org.springframework.context.annotation.Configuration
//导入 AutoConfiguredMapperScannerRegistrar
@Import(AutoConfiguredMapperScannerRegistrar.class)
//当没有 MapperFactoryBean 和 MapperScannerConfigurer 这两个的时候,才注册此类 
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {

  @Override
  public void afterPropertiesSet() {
    logger.debug(
        "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
  }

}

public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
  //内容省略,先不看。。。
}

  从上述 MapperScannerRegistrarNotFoundConfiguration 类的几个类主键分析,@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class }) 当不存在 MapperFactoryBean 和 MapperScannerConfiguer 两个 Bean 的时候,才加载 MapperScannerRegistrarNotFoundConfiguration 这个 Bean。MapperFactoryBean 是什么暂时不知道,通过 registry.getBeanDefinitionNames() 查看 Bean 定义中是没有这个,暂时不去管他。

  这个地方说明这里分成了两种情况,有 MapperScannerConfigurer 和没有的情况。那么就要从 MapperScanConfigure 是否注册来探讨了。

1. 入口一:MapperScannerConfigurer 的注册

MapperScannerConfiguer 是什么呢,我们可以看下这个类的源码

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, 
                                                InitializingBean, ApplicationContextAware, BeanNameAware {
}

  看上去是个后置处理器,这里先了解一下 BeanDefinitionRegistryPostProcessor 接口(https://www.cnblogs.com/cnff/p/18156913)。

然后这个 MapperScannerConfigurer 何时才会注册呢?此时要看到 @MapperScan 注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//重要的一个
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
  //代码略
}

@Import(MapperScannerRegistrar.class) 是导入了一个 MapperScannerRegistrar 类。

MapperScannerRegistrar 类部分源码

//实现了 ImportBeanDefinitionRegistrar 接口,那么这个类就拥有了向 Spring 容器中添加 BeanDefinition 的能力
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
  
  // importingClassMetadata 是提供对注解的操作对象,registry 这里是 Springboot 容器
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
    //获取注解的元数据
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {

      //调用下面的 registerBeanDefinitions 方法
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
  }

  void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
      BeanDefinitionRegistry registry, String beanName) {
    
    //这里就是 MapperScannerConfigurer 注册的根源
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);

    //省略。。。
    。。。
    
    //MapperFactoryBean 不知道是不是上面的 MapperFactoryBean
    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
    }

    。。。

    //中间的看不懂,也不想看,这里是注册一个 bean 到 SpringBoot 中
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  }

}

ImportBeanDefinitionRegistrarAnnotationMetadata 这里不深究,参考:https://www.cnblogs.com/cnff/p/18156913。

总结:如果有 @MapperScan 注解,会加载 MapperScannerRegistrar 类,最后就是注册了一个 MapperScannerConfigurer 实例到 SpringBoot 中。由此看来 @MapperScan 和 @Mapper 是冲突的,@MapperScan 优先级高于 @Mapper。

2. 入口二:MapperScannerConfigurer 没注册

根据 MapperScannerRegistrarNotFoundConfiguration 头上的注解 @Import(AutoConfiguredMapperScannerRegistrar.class),此时是走 AutoConfiguredMapperScannerRegistrar 这个类的。

MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar 内部类源码分析

  // 这个类同 MapperScannerRegistrar 一样也实现了 ImportBeanDefinitionRegistrar  接口
 public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
    
    //这个其实就是 SpringBoot 自己
    private BeanFactory beanFactory;
    
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

      if (!AutoConfigurationPackages.has(this.beanFactory)) {
        logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
        return;
      }

      logger.debug("Searching for mappers annotated with @Mapper");

      List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
      if (logger.isDebugEnabled()) {
        packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
      }

      //这里也是 MapperScannerConfigurer,然后各种属性赋值
      BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
      builder.addPropertyValue("processPropertyPlaceHolders", true);
      builder.addPropertyValue("annotationClass", Mapper.class);
      builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
      BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
      Stream.of(beanWrapper.getPropertyDescriptors())
          // Need to mybatis-spring 2.0.2+
          .filter(x -> x.getName().equals("lazyInitialization")).findAny()
          .ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"));

      //这里最终也是注册了一个 MapperScannerConfigurer 类型的 BeanDefinition
      registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
      this.beanFactory = beanFactory;
    }

  }

看起来与 MapperScannerRegistrar 一样,最终注册一个 MapperScannerConfigurer 的 Bean 定义。只是 basePackage, annotationClass 等属性不同。这个是针对 @Mapper 注解。

3. 分析 MapperScannerConfigurer

从上述的分析得出,不管是扫描 @Mapper 还是通过 @MapperScan,最终都得到一个 @MapperScannerConfigurer。

MapperScannerConfigurer 定义:

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
}

实现了 BeanDefinitionRegistryPostProcessor 这个接口,说明后续还要搞事情,那就看他实现这个接口的 postProcessBeanDefinitionRegistry 方法。

MapperScannerConfigurer.postProcessBeanDefinitionRegistry()

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  if (this.processPropertyPlaceHolders) {
    processPropertyPlaceHolders();
  }
  
  //1. 创建一个 ClassPathMapperScanner,并填充相应属性
  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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
  if (StringUtils.hasText(lazyInitialization)) {
    scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
  }
  //2. 注册俩过滤器,有两种Filter,includeFilters:要扫描的;excludeFilters:要排除的
  scanner.registerFilters();
  //3. 扫描basePackage,basePackage可通过",; \t\n"来填写多个,
  scanner.scan(
      StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
  1. ClassPathMapperScanner 继承了 Spring 的 ClassPathBeanDefinitionScanner 类,说明是一个路径扫描类,这一步就是注册扫描器,制定扫描规则。
  2. 过滤器,定义扫描或者不扫描哪些类和包。
  3. 执行扫描。

MapperScannerConfigurer 扫描方法,scan 和 doScan()

public int scan(String... basePackages) {
	int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
    
    //1. 扫描符合的类
	doScan(basePackages);

	// Register annotation config processors, if necessary.
	if (this.includeAnnotationConfig) {
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}

	return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
   
  //2. 调用父类的 doScan 方法,返回扫描到定义好的 BeanDefinition
  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 {
    //3. 将扫描到的 BeanDefinition 做个转换
    processBeanDefinitions(beanDefinitions);
  }

  return beanDefinitions;
}
  1. 第 2 步 super.doScan 调用父类 ClassPathBeanDefinitionScanner 的 doScan() 方法,这个方法扫描后的 BeanDefintion 添加到了 SpringBoot 容器中。
  2. 将上述扫描的 BeanDefintion 做个转换,比如 EMapper 转换为 MapperFactoryBean,这是为啥接口能实例化的原因

ClassPathMapperScanner.processBeanDefinitions() 转换 BeanDefintion 类型

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  for (BeanDefinitionHolder holder : beanDefinitions) {
    definition = (GenericBeanDefinition) holder.getBeanDefinition();
    String beanClassName = definition.getBeanClassName();
    LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
        + "' mapperInterface");

    // the mapper interface is the original class of the bean
    // but, the actual class of the bean is MapperFactoryBean
    //1. 设置构造方法
    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
    //2. 设置 beanClass 为 mapperFactoryBeanClass,在创建 bean 时,会根据 BeanDefinition 的 BeanClass,和构造方法进行创建
    definition.setBeanClass(this.mapperFactoryBeanClass);

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

    boolean explicitFactoryUsed = false;
    //3. 设置 sqlSessionFactoryBeanName、sqlSessionTemplateBeanName,不过貌似都不会执行。
    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) {
      LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
      //4. 如果前面的sqlSessionFactoryBeanName、sqlSessionTemplateBeanName没有指定的话,那么就设置其注入模型安装类型进行
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
    definition.setLazyInit(lazyInitialization);
  }
}
  1. 指定了需要使用构造器
  2. 将原来的类,替换成了MapperFactoryBean,使用MapperFactoryBean来创建bean
  3. 相当于手动添加了个 @Autowired

总结:ClassPathMapperScanner 在后置处理器方法中,注入了扫描到的 Mapper 的 BeanDefintion,其中重要的一步是,BeanDefinition 的 BeanClass 由原来的类型转换成了 MapperFactoryBean。至此,准备工作已经完成,接下来就是注入 Mapper,执行 sql 了。

7. Mapper 的实例化

单独使用 mybatis 的模式下,我们的是使用 sqlSession 来获取 Mapper 的,解析一下这个是如何操作的。

DefaultSqlSession.getMapper():就以这个 SqlSession 为例

@Override
public <T> T getMapper(Class<T> type) {
  return configuration.getMapper(type, this);
}

Configuration.getMapper()

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}

MapperRegistry.getMapper():终于到了干活的地方

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  //1. 根据类型取出对应的 mapperProxyFactory(mapper 代理工厂)
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);

  //这里很巧妙,这个 type 是通过 getMapper<T t> 这个 t 传过来的,也就是接口的类型。而 MapperRegistry 我们前面分析过,是从 xml 的 nameSpance 解析过来的,所以这里通过接口的 type 来获取,就可以对应 mapper 关系。
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    //2. 根据这个工厂反射 mapper 的代理类
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}

MapperProxyFactory,用来生成 MapperProxy 的工厂类

protected T newInstance(MapperProxy<T> mapperProxy) {
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

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

通过 jdk 动态代理,生成一个 Mapper 接口泛型的代理 Mapper,所以我们实际用的 Mapper 是一个代理类。

正常的 mybatis,每次 getMapper() 一次,就生成一个 Mapper 实例。但是在 SpringBoot 中,我们使用 @Autowired 来注入单例实例。在 转换 BeanClass( definition.setBeanClass(this.mapperFactoryBeanClass) )时,设置了 BeanClass = MapperFactoryBean.class,这个类实现了 FactoryBean,getObject() 方法就是调用 SqlSessiongetMapper() 方法,也就有了一个对应的 Mapper bean。

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

FactoryBean 参考 https://www.cnblogs.com/cnff/p/18156913

8. Mapper 执行一次方法

我们通过 Mapper 来执行一次 sql 查询操作,这个操作具体是如何执行的呢?

@Autowired
private FMapper fMapper;

@RequestMapping("/t")
public void mapper(){
    System.out.println( fMapper.list1("鳄鱼","张三") );
}

从上面的分析,我们知道 fMapper 对象实际上是一个 jdk 代理对象,实际类型是 MapperProxy,因此实际我们调用的方法,都会执行增强类的 invoke 方法。

1. MapperProxy

1. MapperProxy.invoke() 方法,mapper 方法执行的入口

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    //1. 如果是 Object 类方法,就直接执行
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    } else {
      //2. 解析方法执行
      return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
}

//3. 大概意思就是把方法解析缓存下来
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      // A workaround for https://bugs.openjdk.java.net/browse/JDK-8161372
      // It should be removed once the fix is backported to Java 8 or
      // MyBatis drops Java 8 support. See gh-1929

      //4. 从 methodCache 参数中获取
      MapperMethodInvoker invoker = methodCache.get(method);
      if (invoker != null) {
        return invoker;
      }
      
      //5. 如果 methodCache 中搞不到这个 method,就造一个
      return methodCache.computeIfAbsent(method, m -> {
      
        //6. 如果是默认方法
        if (m.isDefault()) {
          try {
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw new RuntimeException(e);
          }
        } else {
          //7. 生成一个新的对象,正常 mapper 方法我们都是走这里的。
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
}

//8. 这是一个内部接口,接第二步 `invoke` 方法,从 `cachedInvoker` 方法分析,正常的 Mapper 方法,这里我们调用 PlainMethodInvoke 的 invoke 方法。
interface MapperMethodInvoker {
  Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}

//9. 又是一个内部类
private static class PlainMethodInvoker implements MapperMethodInvoker {
  private final MapperMethod mapperMethod;

  public PlainMethodInvoker(MapperMethod mapperMethod) {
    super();
    this.mapperMethod = mapperMethod;
  }
  
  //10. 这里的 invoke 方法是真正调用的,这个 mapperMethod 就是过程 7 中 new 的哪个 MapperMethod
  @Override
  public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    return mapperMethod.execute(sqlSession, args);
  }
}
    1. 如果方法是 toString(),equals() 等 Object 类的方法,就直接执行
    1. computeIfAbsent 方法,如果 Map 中没有这个 key,就执行第二个参数提供的 Function 函数构造一个,放到 map 中的同时返回给调用者。
    1. 处理接口 default 修饰的默认方法的,掠过。
    1. 生成了一个 PlainMethodInvoker 对象,内部有方法信息,接口信息,配置信息等等。内部持有一个 MapperMethod,MapperMethod 解析来看看这个
    1. mapperMethod.execute方法 看代码 3

2. MapperMethod

1. MapperMethod 构造方法

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;


  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }
  
  public static class SqlCommand {
    
    //MappedStatement 的 id,类全限定+方法名   com.demo.dao.FMapper.list1
    private final String name;
    //MappedStatemnt 的类型 SELECT/UPDATE。。。
    private final SqlCommandType type;
    
    //构造方法
    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
        //获取方法名
        final String methodName = method.getName();
        //反射获取方法所在的类
        final Class<?> declaringClass = method.getDeclaringClass();
        //从 configuration 中获取方法对应的 MappedStatement
        MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
            configuration);
        //如果没有获取到 MappedStatement
        if (ms == null) {
          if (method.getAnnotation(Flush.class) != null) {
            name = null;
            type = SqlCommandType.FLUSH;
          } else {
            throw new BindingException("Invalid bound statement (not found): "
                + mapperInterface.getName() + "." + methodName);
          }
        } else {
          //获取到 MappedStatemnt 的情况下,赋值
          name = ms.getId();
          type = ms.getSqlCommandType();
          if (type == SqlCommandType.UNKNOWN) {
            throw new BindingException("Unknown execution method for: " + name);
          }
        }
        
    }
  }
  
  public static class MethodSignature {
    
    //返回值类型是否多条,Collection 类型
    private final boolean returnsMany;
    //返回值 map
    private final boolean returnsMap;
    //返回值类型是否 void
    private final boolean returnsVoid;
    //返回类型是游标,不知道是个啥
    private final boolean returnsCursor;
    //返回类型是 Optional,用来防止 null 的,没用过
    private final boolean returnsOptional;
    //判断接口方法返回值类型
    private final Class<?> returnType;
    private final String mapKey;
    //返回结果处理器
    private final Integer resultHandlerIndex;
    //分批查询相关
    private final Integer rowBoundsIndex;
    //参数转换相关
    private final ParamNameResolver paramNameResolver;

    public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
      //反射获取接口方法返回值类型
      Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
      if (resolvedReturnType instanceof Class<?>) {
        this.returnType = (Class<?>) resolvedReturnType;
      } else if (resolvedReturnType instanceof ParameterizedType) {
        this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
      } else {
        this.returnType = method.getReturnType();
      }
      //返回值类型
      this.returnsVoid = void.class.equals(this.returnType);
      this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
      this.returnsCursor = Cursor.class.equals(this.returnType);
      this.returnsOptional = Optional.class.equals(this.returnType);
      this.mapKey = getMapKey(method);
      this.returnsMap = this.mapKey != null;
      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
      //跟参数解析有关
      this.paramNameResolver = new ParamNameResolver(configuration, method);
    }
  }
}
  • 反正就是 MapperMethod 两个内部类属性的各种赋值操作。new ParamNameResolver(configuration, method) 这里初始化了一个参数解析器

2. MapperMethod.execute():上面 PlainMethodInvoker 类 invoke 方法调用到这个方法

/**
 * @Param sqlSession 是 SqlSessionTemplate
 * @Param args 是参数值 ["王二麻子","张三"] 
 */
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;

    //1. 判断不同的操作类型,就行不同的操作
    switch (command.getType()) {
      //2. 新增
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      //3. 修改
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      //4. 删除
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      //5. 查询
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          //6. 就以这个为例继续查下去
          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);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      //7. 不知道是撒意思
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      //8. 直接抛异常
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    //9. 返回结果不行也抛异常
    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;
}

//第 6 步调用,以这个为例
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
  List<E> result;
  
  //10. 参数的解析
  Object param = method.convertArgsToSqlCommandParam(args);
  //11. 判断有没有分页
  if (method.hasRowBounds()) {
    RowBounds rowBounds = method.extractRowBounds(args);
    result = sqlSession.selectList(command.getName(), param, rowBounds);
  } else {
    //12. 调用 selectList 方法
    result = sqlSession.selectList(command.getName(), param);
  }
  // issue #510 Collections & arrays support
  if (!method.getReturnType().isAssignableFrom(result.getClass())) {
    if (method.getReturnType().isArray()) {
      return convertToArray(result);
    } else {
      return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
    }
  }
  return result;
}

//第 10 步使用 ParamNameResolver 处理参数
public Object convertArgsToSqlCommandParam(Object[] args) {
  return paramNameResolver.getNamedParams(args);
}
    1. 对增删改查采用不同的处理。
    1. 以查询为例,根据返回值采用不同的处理。
    1. 以这个为例,主要概括为两个过程,参数解析和 SqlSession 执行 sql。
    1. 参数解析
    1. sqlSession.selectList() 调用 selectList() 方法,这里 sqlSession 实际时 SqlSessionTemplate, 点击分析

3. ParamNameResolver

用来解析参数的,这个实际上是在初始化 MapperMethod 的时候就 初始化了。
1. ParamNameResolver 类定义

public class ParamNameResolver {
  //前缀
  public static final String GENERIC_NAME_PREFIX = "param";
  //是否使用真实参数名
  private final boolean useActualParamName;
  //参数名列表
  private final SortedMap<Integer, String> names;
  //是否有 @Param 参数注解
  private boolean hasParamAnnotation;

  public ParamNameResolver(Configuration config, Method method) {

    //判断是否使用真是参数名,默认 true
    this.useActualParamName = config.isUseActualParamName();
    //反射获取接口方法参数类型
    final Class<?>[] paramTypes = method.getParameterTypes();
    //反射获取接口方法参数注解
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    final SortedMap<Integer, String> map = new TreeMap<>();
    //参数个数
    int paramCount = paramAnnotations.length;
    //这里要遍历参数了
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
      //特殊参数 RowBounds 和 ResultHandler
      if (isSpecialParameter(paramTypes[paramIndex])) {
        // skip special parameters
        continue;
      }
      String name = null;
      //遍历注解
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        //当参数注解是 @Param
        if (annotation instanceof Param) {
          //属性赋值 true
          hasParamAnnotation = true;
          name = ((Param) annotation).value();
          break;
        }
      }
      //如果 name == null,说明参数都没有 @Param 注解
      if (name == null) {
        // @Param was not specified.
        if (useActualParamName) {
          //这里获取的应该是编译后的参数名 arg0
          name = getActualParamName(method, paramIndex);
        }
        if (name == null) {
          // use the parameter index as the name ("0", "1", ...)
          // gcode issue #71
          name = String.valueOf(map.size());
        }
      }
      //按照顺序,放到参数 map 里
      map.put(paramIndex, name);
    }
    //顺序
    names = Collections.unmodifiableSortedMap(map);
  }
}
  • List<Map<String, Object>> list1(@Param("p")String p, @Param("x")String x);
  • List<Map<String, Object>> list3(String p, String x);
  • List<Map<String, Object>> list4(@Param("p")String p, String x);

总结来说,这个初始化参数名列表的。如果使用了 @Param,那么记录的是参数名,没有使用则记录的是 arg0 这种。


2. ParamNameResolver.getNamedParams() 方法:参数解析

public Object getNamedParams(Object[] args) {
  final int paramCount = names.size();
  //没参数不处理
  if (args == null || paramCount == 0) {
    return null;
  //没 @Param 注解并且只有一个参数
  } else if (!hasParamAnnotation && paramCount == 1) {
    Object value = args[names.firstKey()];
    return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
  } else {
    final Map<String, Object> param = new ParamMap<>();
    int i = 0;
    //names 是在初始化时写入的参数名
    for (Map.Entry<Integer, String> entry : names.entrySet()) {
      //参数键值对
      param.put(entry.getValue(), args[entry.getKey()]);
      // add generic param names (param1, param2, ...)
      final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
      // ensure not to overwrite parameter named with @Param
      //带前缀 Param 再放一次
      if (!names.containsValue(genericParamName)) {
        param.put(genericParamName, args[entry.getKey()]);
      }
      i++;
    }
    return param;
  }
}
  • List<Map<String, Object>> list1(@Param("p")String p, @Param("x")String x);
  • List<Map<String, Object>> list3(String p, String x);
  • List<Map<String, Object>> list4(@Param("p")String p, String x);

总结来说:方法参数组装了起来。

4. SqlSessionTemplate

此时参数啥的也解析好了,该执行 sql 了。之前的 SqlSession,指的都是 SqlSessionTemplate 这个单例模板对象,所以调用的就是 SqlSessionTemplate 的方法。

1. SqlSessionTemplate.select() 为例:

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

实际是使用 SqlSessionTemplate.sqlSessionProxy 属性的 selectList 方法,在 SqlSessionTemplate 初始化时候可知,这玩意又是个代理类,所以执行的是增强类的 invoke 方法。

2. SqlSessionTemplate.SqlSessionInterceptor.invoke():这玩意还是个内部类

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //1. 这个才是获取真正的 SqlSession
    SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
    try {
      //2. 反射执行方法
      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);
      }
    }
}
    1. 这里开始获取一个真正的 SqlSession,之前的 sqlSession 指的都是 SqlSessionTemplate。这里在前面 SqlSession 的创建 已经分析过。
    1. method.invoke(sqlSession, args) 通过反射来执行对应的方法,此时参数 sqlSession 是 getSqlSession() 方法获取的,默认就是 DefaultSqlSession,通过反射执行对应的方法,下面我们以 selectList 为例

3. DefalutSqlSession.selectList():通过反射执行到这个

//两个参数,所以默认执行这个方法
@Override
public <E> List<E> selectList(String statement, Object parameter) {
  return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
//实际上执行的是这个重载方法
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    //1. 获取 MapperStatement
    MappedStatement ms = configuration.getMappedStatement(statement);
    //2. 执行方法
    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();
  }
}

5. Executor

Executor,用来执行 sql。继续上面 executor.query() 来分析。

首先看看这个 exccutor 是怎么来到 DefaultSqlSession 的,看样子就是创建 SqlSession 的时候,Executor 也 new 了 一个,作为一个参数传入了 DefaultSqlSession。

1. configuration.newExecutor(tx, execType):首先分析一下创建 Executor 的方法

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    //1. 不知道什么操作,反正就是执行器类型的处理
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    //2. 这里是针对三种类型的执行器
    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);
    }
    //3. 缓存相关执行器
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    //4. 貌似是个拦截器链,具体干啥的不知道
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}
    1. 这里针对三种执行器类型有个简单说明
    • SIMPLE是默认执行器,根据对应的sql直接执行,不会做一些额外的操作。
    • REUSE是可重用执行器,重用对象是Statement(即该执行器会缓存同一个sql的Statement,省去Statement的重新创建,优化性能)(即会重用预处理语句)
    • BATCH执行器会重用预处理语句,并执行批量更新。
    1. 根据 cacheEnabled 参数,executor 是否要换成 cachingExecutor,盲猜这个跟一二级缓存有关。
     protected boolean cacheEnabled = true;
    

  貌似默认是 true,就是单纯的要生成一个 CachingExecutor,内部持有一个 SimpleExecutor。这个 cachingExecutor 就是 SimpleExecutor 的装饰器。

装饰器:为目标增强核心功能,就是为了SimpleExecutor和ReuseExecutor和BatchExecutor增强他们的缓存这个核心功能的。这三个Executor是访问数据库的,CachingExcutor作为他们的装饰器就是为了增强他们的查询功能、提升查询效率。CachingExcutor在这里也是采用了一种套娃的方式

2. CachingExecutor.query():看 executor 如何执行 sql

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  //1. 从 MappedStatement 中操作 sql 和 参数,这方法也懒得看
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  //2. 根据一系列东西生成的 key,用来标记缓存的,具体怎么生成的不想知道
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
  //3. 这直接从 MappedStatement 抠出来一个缓存,二级缓存
  Cache cache = ms.getCache();
  //4. 如果缓存不为空
  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.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  //3. 白扯半天,就跟一个缓存有关,最后执行还得放到其他 Executor
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
    1. 这个过程中,解析动态 sql,替换 ${} 参数
    1. 这里 delegate 实际是一个 Executor,默认的就是 SimpleExecutor,而 SimpleExecutor 没有重写 queue() 方法,所以是父类 BaseExecutor 的 queue() 方法。

CachingExecutor 只是处理了下二级缓存,真正执行 sql 的还得继续往下看。

3. BaseExecutor.queue():又是一个执行 sql

/**
 * MappedStatement ms:接卸 xml 对应的元素 
 * Object parameter:方法参数
 * RowBounds rowBounds:分页
 * ResultHandler resultHandler:应该是结果处理器 
 * CacheKey key:缓存的key
 * BoundSql boundSql:sql相关的
 */
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
 
  ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  //1. queryStack 不知道,后面这个应该是刷新缓存,这个 LocalCache 应该是一级缓存了
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    clearLocalCache();
  }
  List<E> list;
  try {
    queryStack++;
    //2. 从一级缓存中获取数据
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if (list != null) {
      //3. 命中缓存后,还要搞事情,貌似是存储过程相关的,不管他
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
      //4. 没有命中缓存,这次应该真查询了吧
      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
  } finally {
    queryStack--;
  }
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    // issue #601
    deferredLoads.clear();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482
      clearLocalCache();
    }
  }
  return list;
}

//4. 又是一个这次该查询了吧的方法
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  //本地缓存先放一个东西
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    //5. doQuery 了,这次应该真正执行了
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    //把刚刚放到本地缓存的东西删了
    localCache.removeObject(key);
  }
  //6. 把查询出来的东西,放到一级缓存中
  localCache.putObject(key, list);
  //7. 参数还得缓存一下?存储过程,不管他
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}

    1. 执行完,先把查询到的内容放到 localCache 中,这就是一级缓存一定开启的原因,根本就不带参数判断的。
    1. 再次调用方法,这次要用的是 SimpleExecutor 重写的 doQuery() 方法

4. SimpleExecutor.doQuery():这个应该真的执行 sql 了把

@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    //1. 先把配置从 MappedStatement 中掏出来
    Configuration configuration = ms.getConfiguration();
    //2. 这用来造一个 Statement(JDBC 中的那个),可算是看到底层了
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    //3. 创建 statement
    stmt = prepareStatement(handler, ms.getStatementLog());
    //7. 还是调用 StatementHandler 来执行 sql
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

//3. 看看是怎么创建 statement 的
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //4. 开启一个连接
    Connection connection = getConnection(statementLog);
    //5. 根据 connection 来创建 statment 
    stmt = handler.prepare(connection, transaction.getTimeout());
    //6. 处理参数
    handler.parameterize(stmt);
    return stmt;
  }

5. configuration.newStatementHandler():创建 StatementHandler

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  //1. 创建 RoutingStatementHandler,往下走
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  //2. 执行插件链
  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  return statementHandler;
}

6. StatementHandler

创建语句处理器,主要是对 Statement 的操作,从 RoutingStatementHandler 开始。这玩意也是个装饰者模式

1. RoutingStatementHandler 构造方法:

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    switch (ms.getStatementType()) {
      case STATEMENT:  //普通语句
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:  //预编译(默认)
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:  //存储过程
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
}

这里分为三个实现类,分别调用各自的构造方法,不过都是调用父类 BaseStatementHandler 的构造方法,所以跳到父类构造方法。

2. BaseStatementHandler.BaseStatementHandler() 类定义

public abstract class BaseStatementHandler implements StatementHandler {
  
  protected final Configuration configuration;
  protected final ObjectFactory objectFactory;
  protected final TypeHandlerRegistry typeHandlerRegistry;
  protected final ResultSetHandler resultSetHandler;
  protected final ParameterHandler parameterHandler;

  protected final Executor executor;
  protected final MappedStatement mappedStatement;
  protected final RowBounds rowBounds;

  protected BoundSql boundSql;

  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    
    //这几个都是直接赋值
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;
    
    //分部拿到类型处理器何对象工厂
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();
    
    if (boundSql == null) { // issue #435, get the key before calculating the statement
      // 这一行是针对语句中,有对 selectKey设置,并且是执行前的操作时,才要处理
      generateKeys(parameterObject);
      // 获取boundSql
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }
    
    //赋值
    this.boundSql = boundSql;
    
    // 1. 创建参数处理器,比较简单,只提供了设置和获取参数两种操作,内部也有对插件链的执行
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    // 2. 创建结果集处理器
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }
}

3. RoutingStatementHandler.prepare()
在这里调用

@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
  //1. 调用 prepare() 方法
  return delegate.prepare(connection, transactionTimeout);
}
    1. 根据 RoutingStatementHandler 初始化 ,知道这个 delegate 可能有三种,默认 PreparedStatementHandler,没有重写 prepare 方法,使用父类 BaseStatementHandler 的。

4. BaseStatementHandler.prepare()

@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
  ErrorContext.instance().sql(boundSql.getSql());
  Statement statement = null;
  try {
    //1. 用来创建 statement 的
    statement = instantiateStatement(connection);

    //2. 设置超时时间和分页
    setStatementTimeout(statement, transactionTimeout);
    setFetchSize(statement);
    return statement;
  } catch (SQLException e) {
    closeStatement(statement);
    throw e;
  } catch (Exception e) {
    closeStatement(statement);
    throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
  }
}

//1. 是个抽象方法 
protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
    1. 由于是个抽象方法,所以调用子类的这个方法,也就是 preparedStatementHandler 类。

5. preparedStatementHandler.instantiateStatement():初始化 Statement 的

@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
  String sql = boundSql.getSql();
  if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
    String[] keyColumnNames = mappedStatement.getKeyColumns();
    if (keyColumnNames == null) {
      return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
    } else {
      return connection.prepareStatement(sql, keyColumnNames);
    }
  } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
    return connection.prepareStatement(sql);
  } else {
    return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
  }
}
  • 没啥好看的,就是 jdbc 的创建 preparedStatement。至此,我们有了 PreparedStatement。

6. PreparedStatementHandler.query():预编译的 Sql 执行最终执行到这里
上接这里

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  //1. jdbc 的执行 sql
  ps.execute();
  //2. 获取执行结果
  return resultSetHandler.handleResultSets(ps);
}
    1. 千辛万苦,这里终于是执行了 sql
    1. mybatis 封装的解析结果集看这里

7. ParameterHandler

参数处理器,在这里初始化的

1. DefaultParameterHandler.setParameters()

接下来回到 StatementHandler.parameterize() 方法的调用,看参数的处理。一系列的调用不管,就直接看这个做事情的方法。

public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 从 boundSql 中取出参数映射列表 parameterMappings
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
        // 有值,就遍历
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            // 参数必须是输入模式
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                Object value;
                // 参数名
                String propertyName = parameterMapping.getProperty();
                // 针对 DynamicSqlSource 的额外参数(其实这个,可以自定义,比如定义成一个Map传参,但是定义的时候,要注意key只能是“_parameter”)
                if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
                    // 获得对应额外参数值
                    value = boundSql.getAdditionalParameter(propertyName);
                } else if (parameterObject == null) {
                    // 没有参数值,设Null
                    value = null;
                } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    // 类型处理器中,有对应的类型,直接赋值,关于类型处理器的描述,可以进入TypeHandlerRegistry类源码看一下就知道了
                    value = parameterObject;
                } else {
                    // 如果是普通对象,那就解析对象属性
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    value = metaObject.getValue(propertyName);
                }
                // 接下来就是java数据类型跟jdbc中类型的转换了
                TypeHandler typeHandler = parameterMapping.getTypeHandler();
                JdbcType jdbcType = parameterMapping.getJdbcType();
                if (value == null && jdbcType == null) {
                    jdbcType = configuration.getJdbcTypeForNull();
                }
                try {
                    //1. 给参数设置值,这一步就会将java类型的数据转换成jdbc类型的
                    typeHandler.setParameter(ps, i + 1, value, jdbcType);
                } catch (TypeException | SQLException e) {
                    throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                }
            }
        }
    }
}

    1. 其他都是一些转换操作,这一步是设置值。结果好长一系列的调用,中间过程省略了,最后调用到 StringTypeHandler.setNonNullParameter()

2. StringTypeHandler.setNonNullParameter():设置预编译参数

@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
    throws SQLException {
  //这是 jdbc 的设置了
  ps.setString(i, parameter);
}

至此参数也设置完了,按照 jdbc 的过程, 该执行 Sql 了

8. ResultSetHandler

用来处理结果集的接口,只有一个实现类 DefaultResultSetHandler在这里初始化的

1. DefaultResultSetHandler 构造方法

public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler, ResultHandler<?> resultHandler, BoundSql boundSql,
                                 RowBounds rowBounds) {
    this.executor = executor;
    this.configuration = mappedStatement.getConfiguration();
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;
    this.parameterHandler = parameterHandler;
    this.boundSql = boundSql;
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();
    this.reflectorFactory = configuration.getReflectorFactory();
    this.resultHandler = resultHandler;
}

清一色赋值操作,不看了,从这里调用的

2. DefaultResultSetHandler.handleResultSets()

@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    //1. 获取结果集
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    //语句的ResultMap集合,就是返回值类型,多个 ResultMap 配置时通过","分隔
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    //校验 resultMap 不能少于一个
    validateResultMapsCount(rsw, resultMapCount);
    //处理结果集
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      // 4. 真正处理结果集的方法
      handleResultSet(rsw, resultMap, multipleResults, null);
      // 当多个结果集时 rsw 才不为 null,用于下次循环
      rsw = getNextResultSet(stmt);
      // 清空处理过程中产生的未处理完的,其实就是有异常,没法处理的
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
    
    // 对应<select>标签的resultSets属性,一般不使用该属性
    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }
    
    // 如果只有一个结果集合,则直接从多结果集中取出第一个
    return collapseSingleResultList(multipleResults);
}

//1. 获取结果集
private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
    //2. jdbc 也是这么获取结果集的
    ResultSet rs = stmt.getResultSet();
    while (rs == null) {
      // move forward to get the first resultset in case the driver
      // doesn't return the resultset as the first result (HSQLDB 2.1)
      if (stmt.getMoreResults()) {
        rs = stmt.getResultSet();
      } else {
        if (stmt.getUpdateCount() == -1) {
          // no more results. Must be no resultset
          break;
        }
      }
    }
    //3. 如果结果集不为 null,封装成一个 ResultSetWapper 返回
    return rs != null ? new ResultSetWrapper(rs, configuration) : null;
}

//4. 真正处理结果集的方法
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
          //普通的是走到这个分支的,就看这里了
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          //5. 解析数据
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          //解析出来的数据,再加到 multipleResults 里
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
}

//5. 继续上面的第哦啊用,看是咋处理结果集的
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    //判断是否有嵌套 resultMap
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      //6. 以这个方法继续往下
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
}

//6. 循环解析数据,就是封装 jdbc 的解析
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
    throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    skipRows(resultSet, rowBounds); // 跳过不要的行
    // 跳过 offset 的行数后,如果后面还有数据,就取出来自己要的行数
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) { // 移动游标,一行行找
        /* discriminator – 使用结果值来决定使用哪个 resultMap
			case – 基于某些值的结果映射
				嵌套结果映射 – case 也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射
		*/
        // discriminator 的处理,这里不做扩展
        ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
        // 7. ★★★★取出每一行的值,并生成返回类型对象
        Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
        // 将结果存储到 resultHandler 的定义的容器中,默认 DefaultResultHandler 是存放到 List 中,这也就是为什么在SqlSession底层,哪怕查询一条数据,也是走的selectList方法,ResultHandler 是一个接口类,可以自行扩展
        storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
}

// 7. 取出每一行的值,生成返回类型对象
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    // 延迟加载的映射信息
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    //8. 创建出对应的一个对象,此时还是个空对象,属性没有任何值。内部原理是反射,这个就不详细看了。
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;
      // 是否应用自动映射,也就是通过resultType进行映射
      if (shouldApplyAutomaticMappings(resultMap, false)) {
        // 9. 根据columnName和type属性名映射赋值
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
      }
      // 根据我们配置ResultMap的column和property映射赋值
      // 如果映射存在nestedQueryId,会调用getNestedQueryMappingValue方法获取返回值
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
}

//8. 创建对象
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader,
            String columnPrefix) throws SQLException {
        this.useConstructorMappings = false; // reset previous mapping result
        final List<Class<?>> constructorArgTypes = new ArrayList<>();
        final List<Object> constructorArgs = new ArrayList<>();
        // 创建结果映射的PO类对象
        Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
        if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
            // 获取要映射的PO类的属性信息
            final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
            for (ResultMapping propertyMapping : propertyMappings) {
                // issue gcode #109 && issue #149
                // 延迟加载处理
                if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
                    // 通过动态代理工厂,创建延迟加载的代理对象
                    resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration,
                            objectFactory, constructorArgTypes, constructorArgs);
                    break;
                }
            }
        }
        this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping
                                                                                                // result
        return resultObject;
}
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes,
            List<Object> constructorArgs, String columnPrefix) throws SQLException {
        final Class<?> resultType = resultMap.getType();
        final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
        final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
        if (hasTypeHandlerForResultObject(rsw, resultType)) {
            return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
        } else if (!constructorMappings.isEmpty()) {
            return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes,
                    constructorArgs, columnPrefix);
        } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
            // 对象工厂创建对象
            return objectFactory.create(resultType);
        } else if (shouldApplyAutomaticMappings(resultMap, false)) {
            return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix);
        }
        throw new ExecutorException("Do not know how to create an instance of " + resultType);
}

//9. 根据columnName和type属性名映射赋值
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
            String columnPrefix) throws SQLException {
        List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
        boolean foundValues = false;
        if (!autoMapping.isEmpty()) {
            for (UnMappedColumnAutoMapping mapping : autoMapping) {
                final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
                if (value != null) {
                    foundValues = true;
                }
                if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
                    // gcode issue #377, call setter on nulls (value is not 'found')
                    metaObject.setValue(mapping.property, value);
                }
            }
        }
        return foundValues;
}

//Rowbounds 处理,假分页,用不着。
private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
    if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
        if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
            rs.absolute(rowBounds.getOffset()); // 跳转到设置的起始偏移行
        }
    } else {
        for (int i = 0; i < rowBounds.getOffset(); i++) {
            if (!rs.next()) {
                break;
            }
        }
    }
}		


    1. ResultSetWrapper 里边封装了 ResultSet 在内的许多一些东西,columnNames,classNames,jdbcTypes 等也是通过 jdbc 来获取的,太深奥了呀,不看了。。。
    1. 这是从结果集取数过程,只有这个比较重要,看看。
    1. 最终调用来调用去,就用到这里
    1. 根据返回值类型创建对象
    1. 根据columnName和type属性名映射赋值

9. 缓存

1. 回顾下二级缓存

二级缓存是 SqlSessionFactory 级别的缓存,默认是不开启的,需要手动开启:

1. 开启二级缓存

在 mapper.xml 文件中加入下边配置, 或者 使用注解SQL开发的话使用 @CacheNamespace,@CacheNamespaceRef 来开启。

<cache/>   //开启这个 Mapper.xml 的缓存
或者
<cache-ref namespace="com.xxx.xxx.mapper.UserMapper"/>  //引用某个缓存 

cache 标签有几个属性:

  • type:支持自定义的缓存
    • 默认MyBatis自身提供的缓存实现
    • 用户自定义的Cache接口实现
    • 跟第三方内存缓存库的集成,比如 redis
  • eviction: 回收策略
    • LRU 最近最少使用的,移除最长时间不被使用的对象,这是默认值
    • FIFO 先进先出,按对象进入缓存的顺序来移除它们
    • SOFT 软引用,移除基于垃圾回收器状态和软引用规则的对象
    • WEAK 弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象
  • flushInterval: 刷新间隔,以毫秒为单位,100000表示每100秒刷新一次缓存。不设置的话,则每次调用语句时刷新。
  • readOnly: 只读,属性可以被设置为true后者false。只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势。可读写的缓存会通过序列化返回缓存对象的拷贝,这种方式会慢一些,但很安全,因此默认为false。
  • size: 可以被设置为任意的正整数,要记住缓存的对象数目和运行环境的可用内存资源数目,默认1024。
  • blocking:大意是说在执行同一SQL查询时当前线程会先去获取锁,其他执行该查询的SQL线程只能等待当前线程查询完成后才能继续查询而不是直接命中数据库;




mybatis 单独使用的时候,在 mybtais 配置文件还有个 <setting name="cacheEnabled" value="true"/> 表示开启二级缓存,这个参数在创建 Executor 的时候有所体现,而在 SpringBoot 中则不需要这个,我猜是因为:

2. flushCache 和 useCache

可以在Mapper的具体方法下设置对二级缓存的访问意愿:

<select flushCache="true" useCache="true" id="list1" resultType="java.util.Map">
    select * from b where name != #{p} and name != #{x};
</select>
  • flushCache默认为false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存。
  • useCache默认为true,表示会将本条语句的结果进行二级缓存。
  • 在insert、update、delete语句时: flushCache默认为true,表示任何时候语句被调用,都会导致本地缓存和二级缓存被清空。 useCache属性在该情况下没有。update 的时候如果 flushCache="false",则当你更新后,查询的数据数据还是老的数据。

2. cache 标签的解析

缓存的标签 Cachecache-ref 跟随 xml 文件的解析,传送至 xml 解析处

1. XMLMapperBuilder.cacheElement(context.evalNode("cache")):首先看看 cache 的解析

private void cacheElement(XNode context) {
  //如果有 cache 这个标签
  if (context != null) {
    //获取标签内各个属性
    String type = context.getStringAttribute("type", "PERPETUAL");
    Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
    String eviction = context.getStringAttribute("eviction", "LRU");
    Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
    Long flushInterval = context.getLongAttribute("flushInterval");
    Integer size = context.getIntAttribute("size");
    boolean readWrite = !context.getBooleanAttribute("readOnly", false);
    boolean blocking = context.getBooleanAttribute("blocking", false);
    Properties props = context.getChildrenAsProperties();
    //1. new 了一个 cache
    builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
  }
}
    1. 当有 <cache> 标签时,builderAssistant.useNewCache() 应该是用来创建一个 Cache 的。builderAssistant 是 XMLMapperBuilder 的一个属性。

2. MapperBuilderAssistant.useNewCache():用来构建缓存

public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    //1. 构建缓存
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    //2. 将构建的缓存放 configuration
    configuration.addCache(cache);
    //3. 当前缓存赋值
    currentCache = cache;
    return cache;
}

先来看下缓存接口:

public interface Cache {

  String getId();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  Object removeObject(Object key);
  void clear();
  int getSize();
  default ReadWriteLock getReadWriteLock() {
    return null;
  }
}

  其实主要就是一个 id 和一个 Object。缓存的实现类以及装饰器:

3. CacheBuilder 构造 Cache 过程

public class CacheBuilder {
  //构造方法,从上面看是把 nameSpace 当作 id 了
  public CacheBuilder(String id) {
    this.id = id;
    this.decorators = new ArrayList<>();
  }

  //其他方法忽略,就是做一些初始化
  。。。
  
  //构建 cache
  public Cache build() {
    //设置默认的缓存类型(PerpetualCache)和缓存装饰器(LruCache)
    setDefaultImplementations();
    //通过反射创建缓存
    Cache cache = newBaseCacheInstance(implementation, id);
    //1. 设置 cache 属性
    setCacheProperties(cache);
    // issue #352, do not apply decorators to custom caches
    //仅对内置缓存PerpetualCache应用装饰器
    if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class<? extends Cache> decorator : decorators) {
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      cache = setStandardDecorators(cache);
    //应用具有日志功能的缓存装饰器
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
    return cache;
  }
}
    1. 貌似跟第三方缓存有关,暂且不表 todo

    反正这个过程造了一个 Cache, id 是 xml 的命名空间。

4. Configuration.addCache():将构建的缓存放到 Configuration

public void addCache(Cache cache) {
  caches.put(cache.getId(), cache);
}

放到了 Configuration 的 caches 属性中,是个 Map。

总结:

  1. 往 configruation.caches 里边 push 了 key 是当前 namespace,value 为 cache 的元素
  2. 在 configuration.mappedStatements 里,对应 namesapce 里边的 mapperStatement,设置 cache 属性。

3. cache-ref 标签解析

同样的,cache-ref 跟随 xml 文件的解析,传送至 xml 解析处

1. XMLMapperBuilder.cacheRefElement():解析 cache-ref 标签

private void cacheRefElement(XNode context) {
  //如果 cache-ref 不为 null 时候执行以下操作
  if (context != null) {
    //1. 将对应关系添加到 Configuration 的 cacheRef 属性中
    configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
    //2. 初始化一个 cache 引用转换器
    CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
    try {
      //3. 转换器执行一下
      cacheRefResolver.resolveCacheRef();
    } catch (IncompleteElementException e) {
      //4. 做个补偿操作,比如当被引用的缓存不存在时,实际上也是放到 Configuration 的一个 map 属性中,不重要不管他
      configuration.addIncompleteCacheRef(cacheRefResolver);
    }
  }
}
    1. 这里就跟 cache 标签有区别了,cache 标签是放到 configuration 的 caches 属性,这个是放到 cacheRefMap 中,是个对应关系

2. MapperBuilderAssistant.useCacheRef():cacheRefResolver.resolveCacheRef() 最终是调用到这个方法

public Cache useCacheRef(String namespace) {
  if (namespace == null) {
    throw new BuilderException("cache-ref element requires a namespace attribute.");
  }
  try {
    unresolvedCacheRef = true;
    Cache cache = configuration.getCache(namespace);
    if (cache == null) {
      throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
    }
    //重要的就这一句话,当前 cache 设置成引用的 cache
    currentCache = cache;
    unresolvedCacheRef = false;
    return cache;
  } catch (IllegalArgumentException e) {
    throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
  }
}

总结:

  1. 往 configuration.cacheRefMap 中放个对应关系,key 是当前 namespace,value 是被引用 key 的 nameSpace
  2. 在 configuration.mappedStatements 里,对应 namesapce 里边的 mapperStatement,设置 cache 属性,这个 cache 是引用的。

4. flushCache 和 useCache 属性解析

至于 flushCache 和 useCache 两个属性:则是跟随其他属性一起解析到 MapperStatement 中去了。

1. XMLStatementBuilder.parseStatementNode:对 flushCache 和 useCache 的处理

public void parseStatementNode() {
    
    //flushCache 和 useCache 属性
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    
    ...
    
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

2. MapperBuilderAssistant.addMappedStatement():赋值给 MappedStatement

public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class<?> parameterType,
      String resultMap,
      Class<?> resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {

    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        //在这设置到了每个 MapperStatement 上
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }

    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
}

//2. 要设置默认值
private <T> T valueOrDefault(T value, T defaultValue) {
  return value == null ? defaultValue : value;
}

    1. 这个地方,如果是 select 标签,默认 flushCacheRequered 是 false,useCache 是 true,增删改则相反。

总结:

  1. 在 configuration.mappedStatements 中,向对应的 MappedStatement 设置 useCache 和 flushCache 属性

5. 二级缓存的使用

  对于二级缓存的使用,我们要追溯到 Executor 执行器的创建。通过之前的分析,在 Springboot 环境下,貌似 Executor 就是 CachingExecutor。然后执行 sql 代码我们就可以追溯到 CacheExecutor 执行 sql 。再来分析一下这个代码:

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  //1. 按照一定规则创建一个 key
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
  //2. 从 MappedStatement 中拿缓存
  Cache cache = ms.getCache();
  if (cache != null) {
    //3. 判断是否刷新缓存
    flushCacheIfRequired(ms);
    //4. 判断是否使用缓存
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, boundSql);
      @SuppressWarnings("unchecked")
      //5. 获取缓存数据
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
        //6. 这是没有获取到缓存,就执行代码了
        list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        //将数据放到缓存中
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  //6. 没缓存,那就执行方法
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

//3. 如果属性 flushCache 有,就清除缓存
private void flushCacheIfRequired(MappedStatement ms) {
  Cache cache = ms.getCache();
  if (cache != null && ms.isFlushCacheRequired()) {
    tcm.clear(cache);
  }
}

实际上,CachingExecutor 就是用来处理二级缓存的。

    1. 从 cache 和 cache-ref 解析,我们是把缓存放到了 MappedStatement 的 cache 属性上,这里取出来。
    1. 这个是跟 flushCache 属性有关了,这个为 true 就清空 cache,这个 cache 是 tcm 的,并不是 MappedStatment 中的那个 cache,需要提交事务才会更新到那里边去。
    1. 直接调用被装饰 Executor 执行sql。
    1. 理论上我们是直接对 Cache 操作,但是这里出现一个 tcm 属性,跟事务管理有关,要想知道为什么这么取,就要知道为什么这么存,接下来看下 tcm 是怎么来的
public class CachingExecutor implements Executor {

   private final Executor delegate;
   //tcm 就是这么来的,直接 new 一个。
   private final TransactionalCacheManager tcm = new TransactionalCacheManager();
}

那这个 tcm 是个啥呢?

public class TransactionalCacheManager {

  //看样子里边就是持有一个 Hashmap,key 是我们的 Cache
  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
  
  //通过这个 putObject,看样子实际 cache 的东西是存在 value 中,也就是 TransactionCache 对象中
  public void putObject(Cache cache, CacheKey key, Object value) {
    getTransactionalCache(cache).putObject(key, value);
  }
  
  //这个 getObject,看样子是从 TransactionCache 中拿
  public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }
  
  //获取 TransactionCache 的时候,如果没拿到,就 new 一个放进去,并返回这个 new 的
  private TransactionalCache getTransactionalCache(Cache cache) {
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
  }
}

再看 TransactionalCache 又是个啥?

public class TransactionalCache implements Cache {

  private static final Log log = LogFactory.getLog(TransactionalCache.class);
  
  //持有一个缓存对象,在 TransactionalCacheManager.getTransactionalCache() 中传的这个,再往上追溯,这个 cache 就是对应的 MappedStatement 的 cache 属性的那个。
  private final Cache delegate;
  private boolean clearOnCommit;
  //通过 putObject 方法来看,是存放缓存的地方了
  private final Map<Object, Object> entriesToAddOnCommit;
  private final Set<Object> entriesMissedInCache;
  
  //唯一的构造方法
  public TransactionalCache(Cache delegate) {
    this.delegate = delegate;
    this.clearOnCommit = false;
    this.entriesToAddOnCommit = new HashMap<>();
    this.entriesMissedInCache = new HashSet<>();
  }

  //这个 putObject 还是放到一个 entriesToAddOnCommit 属性中
  @Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }
  
  //获取缓存
  @Override
  public Object getObject(Object key) {
    // 这里是通过 传入的 Cache 属性,从 delegate 来获取的
    Object object = delegate.getObject(key);
    if (object == null) {
      entriesMissedInCache.add(key);
    }
    // issue #146
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }
}

通过上述分析,发现两个问题

    1. CachingExecutor 有个 tcm 属性,这个 tcm 属性是个 TransactionCacheManager 类型的实例,内部持有一个 Map<Cache, TransactionalCache> transactionalCaches 样的 map,这个 value 是一个 TransactionCache,这个内部又持有一个 Map<Object, Object> entriesToAddOnCommit, 这个 map 的 key,value 才是缓存的键和数据。而 cachingExecutor 则是 SqlSession 的一个属性,所以这个缓存实际上是存在 SqlSession 中的,但是我记得老师说,二级缓存是跨 SqlSession 的啊?
    1. putObject 的时候,放到了 TransactionalCache.entriesToAddOnCommit 属性中,getObject 的时候,却是从 delegate 也就是 MappedStatement.cache 中拿到,这是?

所以必然有将 entriesToAddOnCommit 内容放到 MappedStatementCache 中的操作,在哪?发现这个 getObject()方法,一层层的调用了很多 getObject() 方法,

传说中的装饰者模式,最终终于是在 PerpetualCache 的 getObject() 中发现找到了真正存储的 map:

public class PerpetualCache implements Cache {

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

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

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }
}

通过 idea debug 找出对应 MappedStatement 的 Cache

不管啥时候放进去的,都要经过 PerpetualCache.putObject() 方法,所以在这个方法打上断点,只要找到这个 Cache 对象何时调用这个方法,就可以向上找到方法栈。启动一个查询,找到这个对象调用 putObject(),如果是反射进来的,那就凉凉。。。

此时的方法栈调用:

然后根据方法栈定位到:

所以是在 SqlSession 提交的时候,将缓存内容放到 MappedStatemnt 中的。

总结:

  1. 二级缓存跟随 xml 解析,cache 标签添加到 Configuration.caches,同时设置到对应的 MappedStatement.cache 属性中,cache-ref 则是添加到 configuration.cacheRefMap 中,同时设置到 对应的 MappedStatements 属性中。
  2. flushCache,usecache 属性分别设置到对应的 MappedStatement.flushCache 和 MappedStatement.useCache 中,查询模型默认 flushCache=false, useCache=true,增删改相反。
  3. CachingExecutor 用来处理二级缓存。查询操作,缓存最开始放在 sqlSession 相关的 CachingExecutor 中,事务提交时候才放到 Configuration.MappedStatment 中。
  4. 能够到 namespace 级别,通过 Cache 接口实现类不同的组合,对 Cache 的可控性也更强,但是对于同一个 nameSpace 下每个 MappedStatemnt 都是同一个 cache,无法做到方法级别的控制。
  5. 由于每个 namespace 对应一个缓存,对于不同的 namespace 直接出现相同的表处理,可能会出现脏数据(设计缺陷)。
  6. 分布式环境下,由于 Cache 实现都是基于本地,所以也会出现脏数据。可以使用 redis,Memcached 等来处理。

6. 一级缓存

一级缓存是默认开启的,也无法关闭。

MyBatis的一些关键特性(例如通过建立级联映射、避免循环引用(circular references)、加速重复嵌套查询等)都是基于MyBatis一级缓存实现的,而且MyBatis结果集映射相关代码重度依赖CacheKey,所以目前MyBatis不支持关闭一级缓存。

更改一级缓存作用范围:默认是 session,换成 Statement 可以避免多个 SqlSession 之间脏数据。

<settings>
  <setting name="localCacheScope" value="STATEMENT"/>
</settings>

当执行完 CachingExecutor 执行完毕,CachingExecutor.delegate() 是被装饰 Executor,跟随之前继续分析:

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  // flushCacheRequeired 还是那个 flushCache 属性,二级缓存中分析过
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    //1. 清除一级缓存的操作
    clearLocalCache();
  }
  List<E> list;
  try {
    queryStack++;
    //从一级缓存中获取
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if (list != null) {
      //存储过程相关的缓存,不管
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
      //3. 没查询到的时候,去查询数据
      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
  } finally {
    queryStack--;
  }
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    // issue #601
    deferredLoads.clear();
    //2. 根据 localcacheScope 参数来决定要不要清除一级缓存
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482
      clearLocalCache();
    }
  }
  return list;
}

@Override
public void clearLocalCache() {
  if (!closed) {
    //清除一级缓存,就是个 map
    localCache.clear();
    //这好像是存储过程的缓存,不管他
    localOutputParameterCache.clear();
  }
}

//3. 执行查询,并且存储一级缓存
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  //先存个不知道啥
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    //执行查询
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    //把刚刚存的不知道是啥扔了
    localCache.removeObject(key);
  }
  //把刚刚查询的结果放到缓存中去
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    //如果是存储过程,就把结果放到这个属性中
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}

总结:

  1. 相比二级缓存,一级缓存就没那么多事了。在 CachingExecutor 装饰的 BaseExecutor,持有一个 localCache 属性,用来存储一级缓存,存取都是在这个属性中,当 SqlSession 没了,这个也就不存在了,所以是 SqlSession 级别的。
  2. 多个 SqlSession 之间,可能存在脏数据。

10. 拦截器(插件)

Mybatis 拦截器的使用:https://www.cnblogs.com/cnff/p/17073997.html#20-拦截器

1. 拦截器的加载

1. MybatisAutoConfiguration 构造方法:

public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,
      ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider,
      ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,
      ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
    this.properties = properties;
    //1. 这里将自定义的拦截器加载进来
    this.interceptors = interceptorsProvider.getIfAvailable();
    this.typeHandlers = typeHandlersProvider.getIfAvailable();
    this.languageDrivers = languageDriversProvider.getIfAvailable();
    this.resourceLoader = resourceLoader;
    this.databaseIdProvider = databaseIdProvider.getIfAvailable();
    this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
}
    1. 虽然还不知道什么原理,反正拦截器加载进来了

2. MybatisAutoConfiguration.sqlSessionFactory()

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
  ...

  //这里将刚才的 interceptors 放到了 SqlSessionFactoryBean 的 Plugins 中
  if (!ObjectUtils.isEmpty(this.interceptors)) {
      factory.setPlugins(this.interceptors);
    }
  ...
  //1. 返回一个 SqlSessionFactory,继续跟这个代码
  return factory.getObject();
}

3. SqlSessionFactoryBean.buildSqlSessionFactory():最后是调用到这个方法

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    。。。

    if (!isEmpty(this.plugins)) {
      Stream.of(this.plugins).forEach(plugin -> {
        //1. 添加拦截器
        targetConfiguration.addInterceptor(plugin);
        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
      });
    }
    。。。
}
    1. 将拦截器假如到 targetConfiguration

4. Configration.addInterceptor()

protected final InterceptorChain interceptorChain = new InterceptorChain();

public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
}
  • 所以最后是将拦截器加到了 Configuration.interceptorChain 属性中,下面来看看这个属性类

5. InterceptorChain 类

public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}
  • 就是持有个拦截器元素类型的集合。
  • 最后这个 configuration 会跟随 targetConfiguration 给到 SqlSessionFactory

2. 拦截器如何生效

根据上面,我们知道拦截器最终放入了 Configuration.interceptorChain 属性中,那么看这个属性在哪里出现

刚好时对应四大组件,难怪四大组件的创建,最后都是在 Configuration 类中创建的。

1. Configuration 类创建四大组件的方法

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    //植入拦截器
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
    ResultHandler resultHandler, BoundSql boundSql) {
  ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
  //植入拦截器
  resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
  return resultSetHandler;
}

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  //植入拦截器
  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  return statementHandler;
}

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;
}
  • 不难看出,这几个方法的共同点就是调用 interceptorChain.pluginAll() 这个方法,那么看看这个方法是个啥

2. InterceptorChain.pluginAll() 方法

public Object pluginAll(Object target) {
  for (Interceptor interceptor : interceptors) {
    target = interceptor.plugin(target);
  }
  return target;
}
  • 每个拦截器执行 interceptor.plugin() 方法,继续跟这个代码

3. Interceptor.plugin()

default Object plugin(Object target) {
  return Plugin.wrap(target, this);
}
  • 如果没有重写,就执行默认的这个方法,如果重写了,就执行自己的,这里就先按照默认方法跟踪
  • 好像还是没看出来啥,再跟

4. Plugin.wrap() 方法

public class Plugin implements InvocationHandler {

  private final Object target;
  private final Interceptor interceptor;
  private final Map<Class<?>, Set<Method>> signatureMap;

  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }

  /**
   * target 是我们要代理的对象,四大组件中的一个。
   * interceptor 是我们定义的一个拦截器
   **/
  public static Object wrap(Object target, Interceptor interceptor) {
      //1. 解析注解
      Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
      //2. 当前组件类型(四大组件实现类)
      Class<?> type = target.getClass();
      //3. 获取 target 及父类实现的接口
      Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
      if (interfaces.length > 0) {
        //4. 生成代理对象
        return Proxy.newProxyInstance(
            type.getClassLoader(),
            interfaces,
            new Plugin(target, interceptor, signatureMap));
      }
      //5. 过程 3 获取接口为空时候,直接将这个对象返回
      return target;
  }

  //1. 解析拦截器注解,生成一个 map
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    //1.1 反射获取拦截器的 @Intercepts 注解
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    // issue #251
    if (interceptsAnnotation == null) {
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
    }
    //1.2 获取 @Intercepts 的注解值,也就是 @Signature 数组
    Signature[] sigs = interceptsAnnotation.value();
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
    //1.3 对每个 @Signature 解析
    for (Signature sig : sigs) {
      //1.4 signatureMap 又 sig.type() 类型,就取出来,没有就 put 一个进去
      Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
      try {
        //1.5 sig.type() 是一个 Class,所以这里是反射出来这个方法
        Method method = sig.type().getMethod(sig.method(), sig.args());
        //1.6 将反射出来的方法添加到集合中
        methods.add(method);
      } catch (NoSuchMethodException e) {
        throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
      }
    }
    return signatureMap;
  }

  /**
   * 3. type 是四大组件类型,
   *    signatureMap 第一步解析注解得来的
   **/ 
  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<>();
    while (type != null) {
      //3.1 获取 type 实现的接口
      for (Class<?> c : type.getInterfaces()) {
        //3.2 如果实现的接口,在 signatureMap 中,就将接口添加到数组中
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      //3.3 获取父类
      type = type.getSuperclass();
    }
    //3.4 将实现的接口转换为数组返回
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }
}
    1. 这个方法就是解析了 @Intercepts 注解,生成一个 Map,key 是四大组件类型,value 是方法组成的 Set。
    1. 获取 target 及父类实现的接口,如果在 @Intercepts 注解有标记,就返回这个接口。
  • 此时回到四大组件创建,说就是生成一个代理对象,又是 JDK 动态代理,根据特性,此时调用啥方法,都是 Plugin.invoke() 方法。

5. Plugin.invoke() 方法

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    //1. 获取上面获取的类型为 key,方法们为 值的 map
    Set<Method> methods = signatureMap.get(method.getDeclaringClass());
    //2. 如果此时调用的方法在刚 map 中能找到,就是用 intercept 的 intercept() 方法替代原方法
    if (methods != null && methods.contains(method)) {
      return interceptor.intercept(new Invocation(target, method, args));
    }
    //3. 没找着就执行原方法
    return method.invoke(target, args);
  } catch (Exception e) {
    throw ExceptionUtil.unwrapThrowable(e);
  }
}
    1. 这个 Map 就是解析 @Intercepts 获取到的 map
  • 至此拦截器解析完毕!撒花

参考文献

Mybatis 自动装配原理:https://www.cnblogs.com/sword-successful/p/14799975.html
了解 SqlSessionTemplate:https://cloud.tencent.com/developer/article/2093591
SpringBoot 和 Mybatis 整合:https://blog.csdn.net/qq_35634181/article/details/106396443
Mybatis 源码:https://blog.csdn.net/zhang527294844/category_12586574.html

posted @ 2024-04-29 17:22  primaryC  阅读(345)  评论(0编辑  收藏  举报