我正在参加「·启航方案」

MyBatis 是一款优异的耐久层框架,它支持自界说 SQL、存储过程以及高级映射。MyBatis 免除了几乎一切的 JDBC 代码以及设置参数和获取成果集的作业。MyBatis 能够经过简略的 XML 或注解来装备和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通旧式 Java 目标)为数据库中的记载。现在让咱们来探寻一下 Mybatis 的奥秘,源码比较多谨慎食用。

建立一个简略的 MyBatis 项目

咱们先来建立一个简略的 MyBatis 项目,以此来展开对 MyBatis 的剖析。

引进 maven 文件

<dependencies>
    <!-- 引进 mybatis  -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.6</version>
    </dependency>
    <!-- 引进 mysql 衔接器  -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.11</version>
    </dependency>
    <!-- 引进 lombok  -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.22</version>
    </dependency>
</dependencies>

创立装备文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
      <package name="org.example.mybatis.mapper"/>
  </mappers>
</configuration>

创立表

CREATE TABLE `test` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='test';

创立对应的实体以及 Mapper

@Data
public class Test {
    private Long id;
    private String name;
}
public interface TestMapper {
    @Select("select * from test where id = #{id}")
    Test findById(Long id);
}

履行查询办法

public class Main {
    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        try (SqlSession session = sqlSessionFactory.openSession()) {
            TestMapper mapper = session.getMapper(TestMapper.class);
            Test test = mapper.findById(1L);
            System.out.println(test);
        }
    }
}

经过上面的例子能够看到运用 MyBatis 与数据库交互的过程是:

  • XML 文件中构建 SqlSessionFactory
  • SqlSessionFactory 中获取 SqlSession
  • 运用 SqlSession 获取对应的 Mapper 目标。
  • 经过 Mapper 目标履行相应的办法。

MyBatis 履行流程

本节会简述一下 Mybatis 的中心目标以及 MyBatis 的履行流程。

中心目标简介

  • MapperProxy: 用于生成 Mapper 接口的署理类。
  • SqlSession:作为 MyBatis 作业的首要顶层 API ,表明和数据库交互的会话,完结必要数据库增修改查功用。
  • ExecutorMyBatis 履行器,是 MyBatis 调度的中心,担任 SQL 句子的生成和查询缓存的维护。
  • StatementHandler:封装了 JDBC Statement 操作,担任对 JDBC Statement 的操作。
  • ParameterHandler:担任对用户传递的参数转化成 JDBC Statement 所需求的参数。
  • ResultSetHandler:担任将 JDBC 回来的 ResultSet 成果集目标转化成 List 类型的调集。
  • TypeHandler:担任 java 数据类型和 JDBC 数据类型之间的映射和转化。

履行流程

MyBatis 的履行流程图如下所示:
MyBatis履行流程.png

咱们先简述一下 MyBatis 的履行流程,后边将会详细介绍各个细节。

  • Mapper 调用增修改查操作时,实际上是调用了 MapperProxy.invoke() 办法。
  • MapperProxy.invoke() 调用了 SqlSession 供给的数据库交互的 API
  • SqlSession 与把与数据库交互的作业交给 Executor 去履行。
  • Executor 担任 SQL 句子的生成和查询缓存的维护,将与数据库衔接的作业交给 StatementHandler
  • StatementHandler 封装了 JDBC Statement 操作,他经过 ParameterHandler 将 用户传递的参数转化成 JDBC Statement 所需求的参数,然后运用 JDBC 真实的操作数据库,然后用 ResultSetHandlerJDBC 回来的 ResultSet 成果集目标转化成 List 类型的调集。

MyBatis 中心目标介绍

MapperProxy

经过 session.getMapper(TestMapper.class) 获取的类反编译的成果为:

/*
 * Decompiled with CFR.
 *
 * Could not load the following classes:
 *  org.example.mybatis.entity.Test
 *  org.example.mybatis.mapper.TestMapper
 */
package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import org.example.mybatis.entity.Test;
import org.example.mybatis.mapper.TestMapper;
public final class $Proxy3
extends Proxy
implements TestMapper {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    public $Proxy3(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }
    // ...省掉部分无关代码
    public final Test findById(Long l) {
        try {
            return (Test)this.h.invoke(this, m3, new Object[]{l});
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("org.example.mybatis.mapper.TestMapper").getMethod("findById", Class.forName("java.lang.Long"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            return;
        }
        catch (NoSuchMethodException noSuchMethodException) {
            throw new NoSuchMethodError(noSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException classNotFoundException) {
            throw new NoClassDefFoundError(classNotFoundException.getMessage());
        }
    }
}

经过上面的代码咱们看到咱们在调用 findById 时真实调用的则是 return (Test)this.h.invoke(this, m3, new Object[]{l});。我们能会疑惑 this.h 是什么,它其实是 Proxy 类的一个成员变量 protected InvocationHandler h; 。下面让咱们看一下这个 h 它到底是哪个类,这个署理类最终是经过 MapperProxyFactory.newInstance() 生成的。经过下面的代码能够看出 h 其实便是 MapperProxy

public class MapperProxyFactory<T> {
  // 省掉...
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    //  将 MapperProxy 作为 InvocationHandler 生成署理类 
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
}

咱们来看一下 MapperProxyinvoke() 办法,它是最终调用了 MapperMethod.execute()

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

MapperMethod.execute() 最终是经过 SqlSession 来履行真实的逻辑。

public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) {
    case INSERT: {
    Object param = method.convertArgsToSqlCommandParam(args);
       //  sqlSession 调用刺进办法
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
   // 省掉部分代码
  return result;
}

到这儿咱们就捋清了怎样获取 Mapper 以及到最终运用 SqlSession 与数据库交互的流程,下面咱们将看一下 SqlSession 是怎样和数据库交互的。

SqlSession

SqlSessionMyBatis 中是十分强壮的一个类。它包括了一切履行句子、提交或回滚业务以及获取映射器实例的办法。

DefaultSqlSession

DefaultSqlSessionSqlSession 的默许完结类。DefaultSqlSession 中运用到了战略形式,DefaultSqlSession 扮演了 Context 的人物,而将一切数据库相关的操作悉数封装到 Executor 接口完结中,并经过 executor 字段选择不同的 Executor 完结。

DefaultSqlSessionFactory

DefaultSqlSessionFactory 是一个详细工厂类,完结了 SqlSessionFactory 接口。DefaultSqlSessionFactory 首要供给了两种创立 DefaultSqlSession 的办法,一种办法经过数据源获取数据库衔接,并创立 Executor 目标以及 DefaultSqlSession,该办法的详细完结如下所示:

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

另一种办法是用户供给数据库衔接目标 DefaultSqlSessionFactory 运用数据库衔接目标创立 Executor 目标以及 DefaultSqlSession 目标,详细完结如下:

private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
    try {
      boolean autoCommit;
      try {
        // 获取当时衔接的业务是否为自动提交办法
        autoCommit = connection.getAutoCommit();
      } catch (SQLException e) {
        // Failover to true, as most poor drivers
        // or databases won't support transactions
        // 当时数据库驱动供给的衔接不支持业务,则或许会抛 反常
        autoCommit = true;
      }
      // 获取装备文件中的Environment目标
      final Environment environment = configuration.getEnvironment();
      // 获取TransactionFactory目标
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 创立Transaction目标
      final Transaction tx = transactionFactory.newTransaction(connection);
      // 依据装备创立Executor目标
      final Executor executor = configuration.newExecutor(tx, execType);
      // 创立DefaultSqlSession目标
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

DefaultSqlSessionFactory 供给的一切 openSession() 办法重载都是依据上述两种办法创立 DefaultSqlSession 目标的,这儿不再赘述。

SqlSessionManager

SqlSessionManager 一起完结 SqlSession 接口和 SqlSessionFactory 接口,也就一起供给了 SqlSessionFactory 创立 SqlSession 以及 SqlSession 操纵数据库的功用。

SqlSessionManager 各个字段的意义如下:

 // 底层封装的SqlSessionFactory目标
  private final SqlSessionFactory sqlSessionFactory;
  // localSqlSession中记载的SqlSession目标的署理目标
  private final SqlSession sqlSessionProxy;
  // ThreadLocal变量,记载一个与当时线程绑定的SqlSession目标
  private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();

SqlSessionManagerDefaultSqlSessionFactory 的首要不同点是 SqlSessionManager 供给了两种形式:一种形式与 DefaultSqlSessionFactory 的行为相同,同一线程每次经过 SqlSessionManager 目标拜访数据库时,都会创立新的 DefaultSession 目标完结数据库操作;第二种形式是 SqlSessionManager 经过 localSqlSession 这个 ThreadLocal,记载与当时线程绑定的 SqlSession 目标,供当时线程循环运用,从而防止在同一线程屡次创立 SqlSession 目标带来的功用损失。

首要看 SqlSessionManager 的结构办法,其结构办法都是私有的,假如要创立 SqlSessionManager 目标,需求调用其 newInstance() 办法。

 private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
    // 运用动态署理的办法生成SqlSession的署理目标
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
  }
  public static SqlSessionManager newInstance(SqlSessionFactory sqlSessionFactory) {
    return new SqlSessionManager(sqlSessionFactory);
  }

SqlSessionManager.openSession() 办法以及其重载是直接经过调用其间底层封装的 SqlSessionFactory.openSession 办法来创立 SqlSession 目标的。

SqlSessionManager 中完结的 SqlSession 接口办法,例如 select*()、update() 等,都是直接调用 sqlSessionProxy 字段记载的 SqlSession 署理目标的相应办法完结的。在创立该署理目标时运用的 InvocationHandler 目标是 SqlSessionlnterceptor 目标,它是界说在 SqlSessionManager 中的内部类,invoke() 办法完结如下:

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 获取当时线程绑定的SqlSession目标
      final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
      if (sqlSession != null) {
        try {
          // 调用真实的SqlSession目标,完结数据库的相关操作
          return method.invoke(sqlSession, args);
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      } else {
        try (SqlSession autoSqlSession = openSession()) {
          try {
            // 经过新建SqlSession目标完结数据库操作
            final Object result = method.invoke(autoSqlSession, args);
            // 提交业务
            autoSqlSession.commit();
            return result;
          } catch (Throwable t) {
            // 回滚业务
            autoSqlSession.rollback();
            throw ExceptionUtil.unwrapThrowable(t);
          }
        }
      }
    }

经过对 SqlSessionInterceptor 的剖析可知,第一种形式中新建的 SqlSession 在运用完结后会封闭。在第二种形式中与当时线程绑定的 SqlSession 目标需求先经过 SqlSessionManager.startManagedSession() 办法进行设置,详细完结如下:

public void startManagedSession() {
  this.localSqlSession.set(openSession());
}

当需求提交/回滚业务或是封闭 localSqlSession 中记载的 SqlSession 目标时,需求经过 SqlSessionManager.commit()、rollback() 以及 close() 办法完结,其间会先检测当时线程是否绑定 SqlSession 目标,假如未绑定则抛出反常 ,假如绑定了则调用该 SqlSession 目标的相应办法。

Executor

ExecutorMyBatis 的中心接口之一,其间界说了数据库操作的根本办法。在实际运用中常常涉及的 SqlSession 接口的功用,都是依据 Executor 接口完结的。Executor 接口中界说的办法如下:

public interface Executor {
  ResultHandler NO_RESULT_HANDLER = null;
  // 履行insert、update、delete等操作
  int update(MappedStatement ms, Object parameter) throws SQLException;
  // 履行 select类型的SQL句子,回来位分为成果目标列表或游标目标
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
  // 批量履行SQL句子
  List<BatchResult> flushStatements() throws SQLException;
  // 提交业务
  void commit(boolean required) throws SQLException;
  // 回滚业务
  void rollback(boolean required) throws SQLException;
  // 创立缓存顶用的CacheKey目标
  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
  // 依据CacheKey目标查找缓存
  boolean isCached(MappedStatement ms, CacheKey key);
  // 清空一级缓存
  void clearLocalCache();
  // 推迟加载一级缓存中的数据
  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
  // 获取业务目标
  Transaction getTransaction();
  // 封闭Executor目标
  void close(boolean forceRollback);
  // 检测Executor是否已封闭
  boolean isClosed();
  void setExecutorWrapper(Executor executor);
}

BaseExecutor

BaseExecutor 是一个完结了 Executor 接口的抽象类,它完结了 Executor 接口的大部分办法,其间就运用了模板办法形式。BaseExecutor 首要供给了缓存办理和业务办理的根本功用,承继 BaseExecutor 的子类只要完结四个根本办法来完结数据库的相关操作即可,这四个办法分别 doUpdate() 办法、doQuery() 办法、doQueryCursor() 办法、doFlushStatement() 办法,其他的功用在 BaseExecutor 完结。

BaseExecutor 各个字段的意义如下:

  // Transaction目标,完结业务的提交、回滚和封闭操作
  protected Transaction transaction;
  // 封装的Executor目标
  protected Executor wrapper;
  // 推迟加载队列
  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
  // 一级缓存,用于缓存该Executor目标查询成果集映射得到的成果目标
  protected PerpetualCache localCache;
  // 一级缓存,用于缓存输出类型的参数
  protected PerpetualCache localOutputParameterCache;
  // 用来记载嵌套查询的层数
  protected int queryStack;

一级缓存

履行 select 句子查询数据库是最常用的功用,BaseExecutor.query() 办法会首要创立 CacheKey 目标,并依据该 CacheKey 目标查找一级缓存,假如缓存射中则回来缓存中记载的成果目标,假如缓存未射中则查询数据库得到成果集,之后将成果集映射成成果目标并保存到一级缓存中,一起回来成果目标。query() 办法的详细完结如下:

@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取BoundSql目标
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 创立CacheKey目标
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // 调用query()的一个重载
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }

这儿关注 BaseExecutor.createCacheKey() 办法创立的 CacheKey 目标由哪几部分构成,createCacheKey() 办法详细完结如下:

 @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    // 检测Executor是否封闭
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    // 增加MappedStatement的id
    cacheKey.update(ms.getId());
    // 增加offset
    cacheKey.update(rowBounds.getOffset());
    // 增加limit
    cacheKey.update(rowBounds.getLimit());
    // 增加SQL句子
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    // 获取实参并增加到CacheKey
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    // 增加Environment的id
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

由此可知该 CacheKey 目标由 MappedStatementid、对应的 offse、limit、SQL 句子(包括 ?占位符)、用户传递的实参以及 Environment.id 这五部分构成。

持续来看上述代码中调用的 query() 办法的另一重载的详细完结,该重载会依据前面创立的 CacheKey 目标查询一级缓存,假如缓存射中则将缓存中记载的成果目标回来,假如缓存未射中,则调用 doQuery() 办法完结数据库的查询操作并得到成果目标,之后将成果目标记载到一级缓存中。详细完结如下:

@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());
    // 检测Executor是否封闭
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      // 非嵌套查询,而且<select>节点装备的flushCache特点为true时,才会清空一级缓存
      clearLocalCache();
    }
    List<E> list;
    try {
      // 增加查询层数
      queryStack++;
      // 查询一级缓存
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        // 针对存储过程调用的处理,其功用是:在一级缓存射中时,获取缓存中保存的输出类型参数,并设置到用户传入的实参目标中
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 调用doQuery()办法完结数据库查询,并得到映射后的成果目标,
        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;
  }

上面介绍了 BaseExecutor 中缓存的第一种功用,也便是缓存查询得到的成果目标。除此之外,一级缓存还有第二个功用:前面在剖析嵌套查询时,假如一级缓存中缓存了嵌套查询的成果目标,则能够从一级缓存中直接加载该成果目标;假如一级缓存中记载的嵌套查询的成果目标并未彻底加载,则能够经过 DeferredLoad 完结相似推迟加载的功用。

Executor 中与上述功用直接相关的办法有两个,一个是 isCached() 办法担任检测是否缓存指定查询的成果目标,详细完结如下:

 @Override
  public boolean isCached(MappedStatement ms, CacheKey key) {
    // 检测缓存中是否缓存了对应的目标
    return localCache.getObject(key) != null;
  }

另一个是 deferLoad() 办法,它担任创立 DeferredLoad 目标并将其增加到 deferredLoads 调集中,详细完结如下:

  @Override
  public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    return doQueryCursor(ms, parameter, rowBounds, boundSql);
  }
  @Override
  public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // 创立DeferredLoad目标
    DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
    if (deferredLoad.canLoad()) {
      // 一级缓存中现已记载了指定查询的成果目标 直接从缓存中加载目标,并设置到外层目标中
      deferredLoad.load();
    } else {
      // 将DeferredLoad目标增加到deferredLoads队列中,待整个外层查询完毕后,再加载该成果目标
      deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
    }
  }

DeferredLoad 是界说在 BaseExecutor 中的内部类,它担任从 localCache 缓存中推迟加载成果目标,其字段的意义如下:

  // 外层目标对应的MetaObject目标
    private final MetaObject resultObject;
    // 推迟加载的局生称号
    private final String property;
    // 推迟加载的特点的类型
    private final Class<?> targetType;
    // 推迟加载的成果目标在一级缓存中相应的CacheKey目标
    private final CacheKey key;
    // 一级缓存
    private final PerpetualCache localCache;
    private final ObjectFactory objectFactory;
    // 担任结采目标的类型转化
    private final ResultExtractor resultExtractor;

DeferredLoad.canLoad() 办法担任检测缓存项是否现已彻底加载到了缓存中。首要要说明彻底加载的意义BaseExecutor.queryFromDatabase() 办法中,开端查询调用 doQuery() 办法查询数据库之前,会先在 localCache 中增加占位符,待查询完结之后才将真实的成果目标放到 localCache 中缓存,此时该缓存项才算彻底加载。BaseExecutor.queryFromDatabase() 办法详细完结如下:

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

DeferredLoad.canLoad() 办法的详细完结如下:

  public boolean canLoad() {
      // 检测缓存是否存在指定的成果目标和是否为占位符
      return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
    }

DeferredLoad.load() 办法担任从缓存中加载成果目标,并设置到外层目标的相应特点中,详细完结如下:

public void load() {
      @SuppressWarnings("unchecked")
      // we suppose we get back a List
      // 从缓存中查询成果目标
      List<Object> list = (List<Object>) localCache.getObject(key);
      // 将成果目标转化成指定类型
      Object value = resultExtractor.extractObjectFromList(list, targetType);
      // 设置到外层目标的对应特点
      resultObject.setValue(property, value);
    }

介绍完 DeferredLoad 目标之后,来看触发 DeferredLoad 缓存中加载成果目标的相关代码,这段代码在 BaseExecutor.query() 办法中,详细完结如下:

 @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  	// ...
    // 推迟加载
    if (queryStack == 0) {
      // 在最外层的查询完毕时,一切嵌套查询也现已完结,相关缓存项也现已彻底加载,所以在这儿能够触发DeferredLoad加载一级缓存中记载的嵌套查询的成果目标
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      // 加载完结后清空deferredLoads
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        // 依据localCacheScope装备决议是否清空一级缓存
        clearLocalCache();
      }
    }
    return list;
  }

BaseExecutor.queryCursor() 办法的首要功用也是查询数据库,这一点与 query() 办法相似,但它不会直接将成果集映射为成果目标,而是将成果集封装成 Cursor 目标并回来,待用户遍历 Cursor 时才真实完结成果集的映射操作。另外,queryCursor() 办法是直接调用 doQueryCursor() 这个根本办法完结的,并不会像 query() 办法那样运用查询一级缓存。

BaseExecutor.update() 办法担任履行 insert、update、delete 三类 SQL 句子,它是调用 doUpdate() 模板办法完结的。在调用 doUpdate() 办法之前会清空缓存,因为履行 SQL 句子之后,数据库中的数据现已更新,一级缓存的内容与数据库中的数据或许现已不一致了,所以需求调用 clearLocalCache() 办法清空一级缓存中的脏数据。

@Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    // 判别当时Executor是否现已封闭
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // 清楚一级缓存
    clearLocalCache();
    // 调用doUpdate()办法
    return doUpdate(ms, parameter);
  }

业务相关操作

BatchExecutor 完结中,能够缓存多条 SQL 句子,等候适宜的时机将缓存的多条 SQL 句子,并发送到数据库履行。 Executor.flushStatements() 办法首要是针对批处理多条 SQL 句子的,它会调用 doFlushStatements() 这个根本办法处理 Executor 缓存的多条 SQL 句子。在BaseExecutor.commit()、rollback() 等办法中都会首要调用 flushStatements() 办法,然后再履行相关业务操作。

BaseExecutor.flushStatements() 办法的详细完结如下:

public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
    // 判别当时Executor是否现已封闭
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // 调用doFlushStatements()这个根本办法,其参数isRollBack表明是否履行Executor中缓存的SQL句子,false表明履行,true表明不履行
    return doFlushStatements(isRollBack);
  }

BaseExecutor.commit() 办法首要会清空一级缓存、调用 flushStatements() 办法,最终才依据参数决议是否真实提交业务。commit() 办法的完结如下:

  @Override
  public void commit(boolean required) throws SQLException {
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    // 清空一级缓存
    clearLocalCache();
    // 履行缓存的SQL句子,其间调用了flushStatements(false)办法
    flushStatements();
    // 依据required参数决议是否提交业务
    if (required) {
      transaction.commit();
    }
  }

BaseExecutor.rollback() 办法的完结与 commit() 完结相似,同样会依据参数决议是否真实回滚业务 ,差异是其间调用的是 flushStatements() 办法的 isRollBack 参数为 true 这就会导致 Executor 中缓存的 SQL 句子悉数被疏忽,即不会被发送到数据库履行。

BaseExecutor.close() 办法首要会调用 rollback() 办法疏忽缓存的SQL句子,之后依据参数决议是否封闭底层的数据库衔接。

SimpleExecutor

SimpleExecutor 承继了 BaseExecutor 抽象类,它是最简略的 Executor 接口完结。正如前面所说,Executor 运用了模板办法形式,一级缓存等固定不变的操作都封装到了BaseExecutorSimpleExecutor 中就不必再关怀一级缓存等操作,只需求专注完结四个根本办法的完结即可。

首要来看 SimpleExecutor.doQuery() 办法的详细完结:

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      // 获取装备目标
      Configuration configuration = ms.getConfiguration();
      // StatementHandler目标 ,实际回来的是RoutingStatementHandler目标
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 完结Statement的创立和初始化
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 调用StatementHandler .query()办法,履行SQL句子,并经过ResultSetHandler,完结成果集的映射
      return handler.query(stmt, resultHandler);
    } finally {
      // 封闭Statement目标
      closeStatement(stmt);
    }
  }
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    // 创立Statements目标
    stmt = handler.prepare(connection, transaction.getTimeout());
    // 处理占位符
    handler.parameterize(stmt);
    return stmt;
  }

SimpleExecutor.doQueryCursor() 办法、doUpdate() 办法与 doQuery() 办法完结相似,这儿不再剖析。 SimpleExecutor 不供给批量处理 SQL 句子的功用,所以其 doFlushStatements() 办法直接回来空调集,不做其他任何操作。

ReuseExecutor

在传统的 JDBC 编程中,重用 Statement 目标是常用的一种优化手法,该优化手法能够削减 SQL 预编译的开支以及创立和销毁 Statement 目标的开支,从而进步功用。

ReuseExecutor 供给了 Statement 重用的功用,ReuseExecutor 中经过 statementMap 字段缓存运用过的 Statement 目标,keySQL 句子,valueSQL 对应的 Statement 目标。ReuseExecutor.doQuery()、doQueryCursor()、doUpdate() 办法的完结与S impleExecutor 对应办法的完结相同,差异在于其间调用的 preparestatement() 办法,SimpleExecutor 每次都会经过 JDBC Connection 创立新的 Statement 目标,而 ReuseExecutor 会先测验重用 StaternentMap 缓存的Statement 目标。

ReuseExecutor.prepareStatement() 办法的详细完结如下:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 获取BoundSql目标
    BoundSql boundSql = handler.getBoundSql();
    String sql = boundSql.getSql();
    // 判别该SQL是否缓存过Statement
    if (hasStatementFor(sql)) {
      // 获取缓存的Statement目标
      stmt = getStatement(sql);
      // 修改超时时刻
      applyTransactionTimeout(stmt);
    } else {
      Connection connection = getConnection(statementLog);
      // 创立新的Statement目标,并缓存到statementMap调集中
      stmt = handler.prepare(connection, transaction.getTimeout());
      putStatement(sql, stmt);
    }
    handler.parameterize(stmt);
    return stmt;
  }

当业务提交或回滚、衔接封闭时,都需求封闭这些缓存的 Statement 目标 。前面介绍了 BaseExecutor.commit()、 rollback()close() 办法时提到,其间都会调用 doFlushStatements() 办法,所以在该办法完结封闭 Statement 目标的逻辑十分适宜,详细完结如下:

@Override
  public List<BatchResult> doFlushStatements(boolean isRollback) {
    // 遍历statementMap调集并封闭其间的Statement目标
    for (Statement stmt : statementMap.values()) {
      closeStatement(stmt);
    }
    // 清空statementMap
    statementMap.clear();
    return Collections.emptyList();
  }

这儿需求留意一下 ReuseExecutor.queryCursor() 办法的运用,了解 JDBC 编程的读者知道,每个 Statement 目标只能对应一个成果集,当屡次调用 queryCursor() 办法履行同 SQL 句子时,会复用 Statement 目标,只要最终一个 ResultSet 是可用的。而 queryCursor() 办法回来的 Cursor 目标,在用户迭代 Cursor 目标时,才会真实遍历成果集目标并进行映射操作,这就或许导致运用前面 Cursor 目标中封装的成果集封闭。

BatchExecutor

运用体系在履行一条 SQL 句子时,会将 SQL 句子以及相关参数经过网络发送到数据库体系。关于频繁操作数据库的运用体系来说,假如履行 SQL 句子就向数据库发送一次恳求,许多时刻会浪费在网络通信上。运用批量处理的优化办法能够在客户端缓存多条 SQL 句子,并在适宜的时机将多条 SQL 句子打包发送给数据库履行,从而削减网络方面的开支,提高体系的功用。

不过有一点需求留意,在批量履行多条 SQL 句子时,每次向数据库发送的 SQL 句子条数是有上限的,假如超越这个上限,数据库会拒绝履行这些 SQL 句子井抛出反常 所以批量发送 SQL 句子的时机很重要。

BatchExecutor 完结了批处理多条 SQL 句子的功用,其间中心字段的意义如下:

 // 缓存多个Statement目标其间每个Statement目标中都缓存了多条SQL句子
  private final List<Statement> statementList = new ArrayList<>();
  // 记载批处理的成果,BatchResult中经过updateCounts字段记载每个Statement履行批处理的成果
  private final List<BatchResult> batchResultList = new ArrayList<>();
  // 记载当时履行的SQL句子
  private String currentSql;
  // 记载当时履行的MappedStatement目标
  private MappedStatement currentStatement;

JDBC 中的批处理只支持 insert、update、delete 等类型的 SQL 句子,不支持 select 类型的 SQL 句子,所以下面要剖析的是 BatchExecutor.doUpdate 办法。

BatchExecutor.doUpdate() 办法在增加一条 SQL 句子时,首要会将 currentSql 字段记载的 SQL 句子以及 currentStatement 字段记载的 MappedStatement 目标与当时增加的 SQL 以及 MappedStatement 目标进行比较,假如相同则加到同一个 Statement 目标等候履行,假如不同则创立新的 Statement 目标并将其缓存到 statementList 调集中等候履行。doUpdate() 办法详细完结如下:

@Override
  public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
    // 获取装备目标
    final Configuration configuration = ms.getConfiguration();
    // 创立StatementHandler目标
    final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
    final BoundSql boundSql = handler.getBoundSql();
    // 获取SQL句子
    final String sql = boundSql.getSql();
    final Statement stmt;
    // 假如当时履行的SQL形式与前次履行的SQL形式相同且对应的MappedStatement目标相同
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
      // 获取statementList中的最终一个
      int last = statementList.size() - 1;
      stmt = statementList.get(last);
      applyTransactionTimeout(stmt);
      // 绑定实参
      handler.parameterize(stmt);
      // 查找对应的BatchResult目标,并记载用户传入的实参
      BatchResult batchResult = batchResultList.get(last);
      batchResult.addParameterObject(parameterObject);
    } else {
      Connection connection = getConnection(ms.getStatementLog());
      // 创立新的Statement目标
      stmt = handler.prepare(connection, transaction.getTimeout());
      // 绑定实参
      handler.parameterize(stmt);    
      // 更新currentSql、currentStatement
      currentSql = sql;
      currentStatement = ms;
      // 增加刚创立的Statement目标
      statementList.add(stmt);
      // 增加新的BatchResult目标
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
    // 底层经过调用Statement.addBatch()办法添SQL句子
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }

JDBC 批处理功用中 Statement 能够增加不同形式的 SQL,可是每增加一个新形式的 SQL 句子都会触发一次编译操作 PreparedStatement 中只能增加同一形式的 SQL 句子,只会触发一次编译操作,可是能够经过绑定多组不同的实参完结批处理。经过上面临 doUpdate() 办法的剖析可知,BatchExecutor 会将连续增加的、相同形式的 SQL 句子增加到同一个 Statement/PreparedStatement 目标中,这样能够有效地削减编译操作的次数。

在增加完待履行的SQL句子之后来看一下 BatchExecutor.doFlushStatemnts() 办法是怎么批量处理这些 SQL 句子的:

@Override
  public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    try {
      // 用于贮存批量处理的成果
      List<BatchResult> results = new ArrayList<>();
      // 假如清晰指定了要回滚业务,则直接回来空调集,疏忽statementList调集中记载的SQL句子
      if (isRollback) {
        return Collections.emptyList();
      }
      // 遍历statementList调集
      for (int i = 0, n = statementList.size(); i < n; i++) {
        // 获取Statement目标
        Statement stmt = statementList.get(i);
        applyTransactionTimeout(stmt);
        // /获取对应的BatchResult目标
        BatchResult batchResult = batchResultList.get(i);
        try {
          // 调用Statement.executeBatch()办法批量履行其间记载的SQL句子,并运用回来的int数组
          // 更新BatchResult.updateCounts字段,其间每一个元素都表明一条SQL句子影响的记载条数
          batchResult.setUpdateCounts(stmt.executeBatch());
          MappedStatement ms = batchResult.getMappedStatement();
          List<Object> parameterObjects = batchResult.getParameterObjects();
          // 获取装备的KeyGenerator目标
          KeyGenerator keyGenerator = ms.getKeyGenerator();
          if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {
            Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
            // 获取数据库生成的主键,并设置到parameterObjects中
            jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
          } else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { //issue #141
            // 关于其他类型的keyGenerator,会调用其processAfter()办法
            for (Object parameter : parameterObjects) {
              keyGenerator.processAfter(this, ms, stmt, parameter);
            }
          }
          // Close statement to close cursor #1109
          // 封闭Statement目标
          closeStatement(stmt);
        } catch (BatchUpdateException e) {
          StringBuilder message = new StringBuilder();
          message.append(batchResult.getMappedStatement().getId())
              .append(" (batch index #")
              .append(i + 1)
              .append(")")
              .append(" failed.");
          if (i > 0) {
            message.append(" ")
                .append(i)
                .append(" prior sub executor(s) completed successfully, but will be rolled back.");
          }
          throw new BatchExecutorException(message.toString(), e, results, batchResult);
        }
        // 增加BatchResult到results调集
        results.add(batchResult);
      }
      return results;
    } finally {
      // 封闭Statement目标,并清空currentSql字段、清空statementList调集、清空batchResultList调集
      for (Statement stmt : statementList) {
        closeStatement(stmt);
      }
      currentSql = null;
      statementList.clear();
      batchResultList.clear();
    }
  }

BatchExecutor.doQuerydoQueryCursor() 办法的完结与前面介绍的 SimpleExecutor 相似,首要差异便是 BatchExecutor 中的这两个办法在最开端都会先调用 flushStatement() 办法,履行缓存 SQL 句子,这样才能从数据库中查询到最新的数据。

CachingExecutor

CachingExecutorExecutor 接口的装饰器,它为 Executor 目标增加了二级缓存的相关的功用,先来简略介绍 MyBatis 中的二级缓存及其依靠的相关组件。

二级缓存简介

MyBatis 中供给的二级缓存是运用级别的缓存,它的生命周期与运用程序的生命周期相同。与二级缓存相关的装备有三个:

(1)首要是 mybatis-config.xml 装备文件中的 cacheEnabled 装备,它是二级缓存的总开关。只要当该装备设置为true 时,后边两项的装备才会有效果,cacheEnabled 的默许值为 true。详细装备如下:

 <settings>
        <setting name="cacheEnabled" value="true"/>
 </settings>

(2)在前面介绍映射装备文件的解析流程时提到,映射装备文件中能够装备 <cache> 节点或 <cache-ref> 节点。假如映射装备文件中装备了这两者中的任何一个节点,则表明敞开了二级缓存功用。假如装备了 <cache> 节点,在解析时会为该映射装备文件指定的命名空间创立相应的 Cache 目标作为其二级缓存,默许是 PerpetualCache 目标,用户能够经过 <cache> 节点的 type 特点指定自界说 Cache 目标。假如装备了<cache-ref> 节点,在解析时则不会为当时映射装备文件指定的命名 空间创立独立的 Cache 目标,而是认为它与 <cache-ref> 节点的 namespace 特点指定的命名空间同享同一个 Cache 目标。经过 <cache> 节点和<cache-ref> 节点的装备,用户能够在命名空间的粒度上办理二级缓存的敞开和封闭。

(3)最终一个装备项是 <select> 节点中的 useCache 特点,该特点表明查询操作发生的成果目标是否要保存到二级缓存中。useCache 特点的默许值是 true

TransactionalCache&TransactionalCacheManager

TransactionalCacheTransactionalCacheManagerCachingExecutor 依靠的两个组件。其间,TransactionalCache 承继了Cache 接口,首要用于保存在某个SqlSession 的某个业务中需求向某个二
级缓存中增加的缓存数据。TransactionalCache中中心字段的意义如下:

 // 底层封装的二级缓存所对应的Cache目标
  private final Cache delegate;
  // 当该字段为true时,则表明当时TransactionalCache不行查询,且提交业务时会将底层Cache清空
  private boolean clearOnCommit;
  // 暂时记载增加到TransactionalCache中的数据。在业务提交时,会将其间的数据增加到二级缓存中
  private final Map<Object, Object> entriesToAddOnCommit;
  // 记载缓存未射中的CacheKey目标
  private final Set<Object> entriesMissedInCache;

TransactionalCache.putObject() 办法并没有直接将成果目标记载到其封装二级缓存中,而是暂时保存在 entriesToAddOnCommit 调集中,在业务提交时才会将这些成果目标从 entriesToAddOnCommit 调集增加到二级缓存中。putObject() 办法的详细完结如下:

  @Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }

再来看 TransactionalCache.getObject() 办法,它首要会查询底层的二级缓存,并将未射中的 key 增加到 entriesMissedInCache 中,之后会依据 clearOnCommit 字段的值决议详细的回来值,详细完结如下:

@Override
  public Object getObject(Object key) {
    // issue #116
    Object object = delegate.getObject(key);
    if (object == null) {
      // 将未射中的key增加到entriesMissedInCache中
      entriesMissedInCache.add(key);
    }
    // issue #146
    // 依据clearOnCommit字段的值决议详细的回来值
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }

TransactionalCache.clear() 办法会清空 entriesToAddOnCommit 调集,并设置 clearOnCommitrue

TransactionalCache.commit() 办法会依据 clearOnCommit 字段的值决议是否清空二级缓存,然后调用 flushPendingEntries() 办法将 entriesToAddOnCommit 调集中记载的成果目标保存到二级缓存中,详细完结如下:

 public void commit() {
    // 在业务提交前 清空二级缓存
    if (clearOnCommit) {
      delegate.clear();
    }
    // 将entriesToAddOnCommit调集中的数据保存到二级缓存
    flushPendingEntries();
    // 重置clearOnCommit为false ,并清空entriesToAddOnCommit和entriesMissedInCache 调集
    reset();
  }
 private void flushPendingEntries() {
    // 遍历entriesToAddOnCommit调集,将其间记载的缓存项增加到二级缓存中
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    // 遍历entriesMissedInCache调集,将entriesToAddOnCommit调集中不包括的缓存项增加到二级缓存中
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }

TransactionalCache.rollback() 办法会将 entriesMissedlnCache 调集中记载的缓存项从二级缓存中删去,并清空 entriesToAddOnCommit 调集和 entriesMissedlnCache 调集,详细完结如下:

public void rollback() {
    // 将entriesMissedInCache调集中记载的缓存项从二级缓存中删去
    unlockMissedEntries();
    // 遍历entriesMissedInCache调集,将entriesToAddOnCommit调集中不包括的缓存项增加到二级缓存中
    reset();
  }

TransactionalCacheManager 用于办理 CachingExecutor 运用的二级缓存目标,其间只界说了transactionalCaches 字段,它的 key 是对应的 CachingExecutor 运用的二级缓存目标,value 是相应 TransactionaCach 目标,在该 TransactionalCache 中封装了对应二级缓存目标,也便是这儿的 key

TransactionalCacheManager 的完结比较简略,下面简略介绍各个办法的功用和完结。

clear()办法、 putObject办法、 getObject()办法: 调用指定二级缓存对应的 TransactionalCache 目标的对应办法,假如 transactionalCaches 调集中没有对应的 TransactionalCache 目标,则经过 getTransactionalCache() 办法创立。

  private TransactionalCache getTransactionalCache(Cache cache) {
    // 假如transactionalCaches调集中没有对应的TransactionalCache目标,则新建
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
  }

commit()办法、 rollback()办法: 遍历 transactionalCaches 调集,井调用其间各个 TransactionalCache 目标的相应办法。

CachingExecutor的完结

CachingExecutor.query() 办法履行查询操作的过程如下:

(1)获取 BoundSql 目标,创立查询句子对应的 CacheKey 目标。

(2)检测是否敞开了二级缓存,假如没有敞开二级缓存,则直接调用底层 Executor 目标的 query() 办法查询数据库。假如敞开了二级缓存,则持续后边的过程。

(3)检测查询操作是否包括输出类型的参数,假如是这种状况,则报错

(4)调用 TransactionalCacheManager.getObject() 办法查询二级缓存,假如二级缓存中查找到相应的成果目标,则直接将该成果目标回来。

(5)假如二级缓存没有相应的成果目标,则调用底层 Executor 目标的 query() 办法。正如前面介绍的 ,它会先查询一级缓存,一级缓存未射中时,才会查询数据库。最终还会将得到的成果目标放入 TransactionalCache.entriesToAddOnCommit 调集中保存。

CachingExecutor.query() 办法的详细代码如下:

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取BoundSql目标
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 创立CacheKey目标
    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 {
    // 获取查询句子地点命名空间对应的二级缓存
    Cache cache = ms.getCache();
    // :是否敞开了二级缓存功用
    if (cache != null) {
      // 依据<select>节点的装备,决议是否需妥清空二级缓存
      flushCacheIfRequired(ms);
      // 检测SQL节点的useCache装备以及是否运用了resultHandler装备
      if (ms.isUseCache() && resultHandler == null) {
        // 二级缓存不能保存输出类型的参数 假如查询操作调用了包括输出参数的存储过程,则报错
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        // 查询二级缓存
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          // 二级缓存没有相应的成果目标,调用封装的Executor目标的query()办法
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          // 将查询成果保存到TransactionalCache.entriesToAddOnCommit调集中
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    // 没有发动二级缓存,调用封装的Executor目标的query()办法
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

再看 CachingExecutor.commit()rollback() 办法的完结,它们首要调用底层的 Executor 目标的对应办法完结提交和回滚,然后调用 TransactionalCacheManager 的对应办法完结对二级缓存的操作。详细完结如下:

@Override
  public void commit(boolean required) throws SQLException {
    // 调用底层的Executor提交业务
    delegate.commit(required);
    // 遍历相关TransactionalCache目标履行commit()办法
    tcm.commit();
  }
  @Override
  public void rollback(boolean required) throws SQLException {
    try {
      // 调用底层的Executor回滚业务
      delegate.rollback(required);
    } finally {
      if (required) {
        // 遍历相关TransactionalCache目标履行rollback()办法
        tcm.rollback();
      }
    }
  }

StatementHandler

StatementHandler 接口中的功用许多,例如创立 Statement 目标,为 SQL 句子绑定实参,履行 select、insert、update、delete 等多种类型的 SQL 句子,批量履行 SQL 句子,将成果集映射成成果目标。
StatementHandler 接口的界说如下:

public interface StatementHandler {
  // 从衔接中获取一个Statement
  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;
  // 参数绑定
  void parameterize(Statement statement)
      throws SQLException;
  // 批量履行
  void batch(Statement statement)
      throws SQLException;
  // 履行update、insert、delete操作
  int update(Statement statement)
      throws SQLException;
  // 履行select操作
  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;
  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;
  BoundSql getBoundSql();
  ParameterHandler getParameterHandler();
}

RoutingStatementHandler

RoutingStatementHandler 会依据 MappedStatement 指定的 statementTyp 字段创立对应的 StatementHandler 接口完结。RoutingStatementHandler 类的详细完结如下:

public class RoutingStatementHandler implements StatementHandler {
  // 封装的真实的StatementHandler目标
  private final StatementHandler delegate;
  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 依据MappedStatement的装备,生成一个对应的StatementHandler目标,并设置到delegate字段中
    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());
    }
  }
 //...经过delegate调用对应的办法
}

BaseStatementHandler

BaseStatementHandler 是一个完结了 StatementHandler 接口的抽象类,它只供给了一些参数绑定相关的办法,并没有完结操作数据库的办法。BaseStatementHandler 字段的意义如下:

 // 记载运用的ResultSetHandler目标,将成果集映射成成果目标
  protected final ResultSetHandler resultSetHandler;
  // 记载运用的ParameterHandler目标,运用传入的实参替换SQL句子的中的?占位符
  protected final ParameterHandler parameterHandler;
  // 记载履行SQL句子的Executor目标
  protected final Executor executor;
  // 记载SQL句子对应的MappedStatement、BoundSql目标
  protected final MappedStatement mappedStatement;
  // RowBounds记载了用户设置的offset,limit,用于在成果集中定位映射的起始位置和完毕位置
  protected final RowBounds rowBounds;

BaseStatementHandler 的结构办法中,除了初始化上述字段之外,还会调用 KeyGenerator.processBefore() 办法初始化 SQL 句子的主键,详细完结如下:

 protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  	// ...其他字段疏忽
    if (boundSql == null) { 
      // 调用keyGenerator.processBefore()办法获取主键
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }
  }
 protected void generateKeys(Object parameter) {
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    ErrorContext.instance().store();
    keyGenerator.processBefore(executor, mappedStatement, null, parameter);
    ErrorContext.instance().recall();
  }

BaseStatementHandler 完结了 StatementHandler 接口中的 prepare() 办法,该办法首要调 instantiateStatement() 抽象办法初始化 Java.sqI.Statement 目标 然后为其装备超时时刻以及 fetchSize 等设置。

ParameterHandler

经过前面临动态 SQL 的介绍可知,在 BoundSql 中记载的 SQL 句子中或许包括 ? 占位符,而每个 ? 占位符都对应了 BoundSql、parameterMappings 调集中的一个元素,在该 ParameterMapping 中记载了对应参数称号以及该参数的相关特点。

ParameterHandler 接口中只界说了一个 setParameters() 办法,该办法首要担任调用 PreparedStatement.set*() 办法为 SQL 句子绑定实参。MyBatis 只为 ParameterHandler 接口供给了一个完结类 DefaultParameterHandler.DefaultParameterHandler 中中心字段的意义如下:

  // 办理mybatis中的悉数TypeHandler目标
  private final TypeHandlerRegistry typeHandlerRegistry;
  // 其间记载SQL节点相应的装备信息
  private final MappedStatement mappedStatement;
  // 用户传入的实参目标
  private final Object parameterObject;
  // 对应的BoundSql目标,需求设置参数的PreparedStatement目标
  private final BoundSql boundSql;

DefaultParameterHandler.setParameters() 办法中会遍历 BoundSql.parameterMappings 调集中记载的 ParameterMapping 目标,井依据其间记载的参数称号查找相应实参,然后与 SQL 句子绑定。setParameters() 办法的详细完结如下:

 public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 取出sql中的参数映射列表
    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();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            // 获取对应的实参值
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            // 实参为空
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            // 依据TypeHandler转化成JdbcType
            value = parameterObject;
          } else {
            // 获取目标中相应的特点位或查找Map目标中值
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          // 获取ParameterMapping中设置的TypeHandler目标
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            // //经过TypeHandler.setParameters()办法会调用PreparedStatement.set*()办法为SQL句子绑定相应的实参
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

SQL 句子绑定完实参之后,就能够调用 Statement 目标相应的 execute() 办法,将 SQL 句子交给数据库履行。

SimpleStatementHandler

SimpleStatementHandler 承继了BaseStatementHandler 抽象类。它底层运用 java.sql.Statement 目标来完结数据库的相关操作,所以 SQL 句子中不能存在占位符相应的,SimpleStatementHandler.parameterize() 办法是空完结。

SimpleStatementHandler.instantiateStatement() 办法直接经过 JDBCConnection 创立 Statement 目标,详细完结如下:

@Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      return connection.createStatement();
    } else {
      // 设置成果集是否能够翻滚及其游标是否能够上下移动,设置成果集是否可更新
      return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

上面创立的 Statement 目标之后会被用于完结数据库操作,SimpleStatementHandler.query() 办法等完结了数据库查询的操作,并经过 ResultSetHandler 将成果集映射成成果目标。

@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 获取SQL句子
    String sql = boundSql.getSql();
    // 履行SQL句子
    statement.execute(sql);
    //  映射成果集
    return resultSetHandler.handleResultSets(statement);
  }

SimpleStatementHandler 中的 queryCursor()、batch() 办法与 query() 办法完结相似,也是直接调用 Statement 目标的相应办法,不再赘述。

SimpleStatementHandler.update() 办法担任履行 insert、update、delete 等类型的 SQL 句子,而且会依据装备的 KeyGenerator 获取数据库生成的主键 详细完结如下:

 @Override
  public int update(Statement statement) throws SQLException {
    // 获取SQL句子
    String sql = boundSql.getSql();
    // 获取实参目标
    Object parameterObject = boundSql.getParameterObject();
    // 获取装备的KeyGenerator目标
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    int rows;
    if (keyGenerator instanceof Jdbc3KeyGenerator) {
      // 履行SQL句子
      statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
      // 获取受影响的行
      rows = statement.getUpdateCount();
      // 将数据库生成的主键增加到parameterObject中
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else if (keyGenerator instanceof SelectKeyGenerator) {
      // 履行SQL句子
      statement.execute(sql);
      // 获取受影响的行
      rows = statement.getUpdateCount();
      // 履行<selectKey>节点中装备的SQL句子获取数据库生成的主键,增加到parameterObject中
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else {
      statement.execute(sql);
      rows = statement.getUpdateCount();
    }
    return rows;
  }

PreparedStatementHandler

PreparedStatementHandler 底层依靠于 java.sql.PreparedStatement 目标来完结数据库的相关操作。在 SimpleStatementHandler.parameterize() 办法中, 会调用前面介绍的 ParameterHandler.setParameters() 办法完结 SQL 句子的参数绑定。

PreparedStatementHandler.instantiateStatement() 办法直接调用 JDBC ConnectionprepareStatement() 办法创立 PreparedStatement 目标, 详细完结如下:

@Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    // 获取SQL句子
    String sql = boundSql.getSql();
    // 获取mappedStatement.getKeyGenerator()创立prepareStatement目标
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        // insert句子履行完结之后,会将keyColumnNames指定的列回来
        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);
    }
  }

PreparedStatementHandler 中其他办法的完结与 SimpleStatementHandler 对应办法的完结相似,这儿就不再赘述。

CallableStatementHandler

CallableStatementHandler 底层依靠于 java.sql.CallableStatement 存储过程 parameterize() 办法也会调用 ParameterHandler.setParameters() 办法完结 SQL 句子的参数绑定指定输出参数的索引位置和 JDBC 类型。其他办法与前面介绍 SimpleStatementHandler 完结相似,仅有差异是会调用 resultSetHandler.handleOutputParameters() 处理输出参数。

总结

本文从一个简略的 MyBatis 示例带领我们从源码的角度剖析 MyBatis 的履行流程。希望我们在运用 MyBatis 时知其所以然,一起也帮助我们在运用 MyBatis 时遇到问题有方向去排查。感谢我们的阅览。

参阅文章

mybatis 官网

《MyBatis技能内情》