您的位置:首页 > 数据库

精尽MyBatis源码分析 - SQL执行过程(一)之 Executor

2020-11-24 15:39 1041 查看

该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址Mybatis-Spring 源码分析 GitHub 地址Spring-Boot-Starter 源码分析 GitHub 地址)进行阅读

MyBatis 版本:3.5.2

MyBatis-Spring 版本:2.0.3

MyBatis-Spring-Boot-Starter 版本:2.1.4

MyBatis的SQL执行过程

在前面一系列的文档中,我已经分析了 MyBatis 的基础支持层以及整个的初始化过程,此时 MyBatis 已经处于就绪状态了,等待使用者发号施令了

那么接下来我们来看看它执行SQL的整个过程,该过程比较复杂,涉及到二级缓存,将返回结果转换成 Java 对象以及延迟加载等等处理过程,这里将一步一步地进行分析:

MyBatis中SQL执行的整体过程如下图所示:

在 SqlSession 中,会将执行 SQL 的过程交由

Executor
执行器去执行,过程大致如下:

  1. 通过
    DefaultSqlSessionFactory
    创建与数据库交互的
    SqlSession
    “会话”,其内部会创建一个
    Executor
    执行器对象
  2. 然后
    Executor
    执行器通过
    StatementHandler
    创建对应的
    java.sql.Statement
    对象,并通过
    ParameterHandler
    设置参数,然后执行数据库相关操作
  3. 如果是数据库更新操作,则可能需要通过
    KeyGenerator
    先设置自增键,然后返回受影响的行数
  4. 如果是数据库查询操作,则需要将数据库返回的
    ResultSet
    结果集对象包装成
    ResultSetWrapper
    ,然后通过
    DefaultResultSetHandler
    对结果集进行映射,最后返回 Java 对象

上面还涉及到一级缓存二级缓存延迟加载等其他处理过程

SQL执行过程(一)之Executor

在MyBatis的SQL执行过程中,Executor执行器担当着一个重要的角色,相关操作都需要通过它来执行,相当于一个调度器,把SQL语句交给它,它来调用各个组件执行操作

其中一级缓存和二级缓存都是在Executor执行器中完成的

Executor
执行器接口的实现类如下图所示:

  • org.apache.ibatis.executor.BaseExecutor
    :实现Executor接口,提供骨架方法,支持一级缓存,指定几个抽象的方法交由不同的子类去实现

  • org.apache.ibatis.executor.SimpleExecutor
    :继承 BaseExecutor 抽象类,简单的 Executor 实现类(默认)

  • org.apache.ibatis.executor.ReuseExecutor
    :继承 BaseExecutor 抽象类,可重用的 Executor 实现类,相比SimpleExecutor,在Statement执行完操作后不会立即关闭,而是缓存起来,执行的SQL作为key,下次执行相同的SQL时优先从缓存中获取Statement对象

  • org.apache.ibatis.executor.BatchExecutor
    :继承 BaseExecutor 抽象类,支持批量执行的 Executor 实现类

  • org.apache.ibatis.executor.CachingExecutor
    :实现 Executor 接口,支持二级缓存的 Executor 的实现类,实际采用了装饰器模式,装饰对象为左边三个Executor类

Executor

org.apache.ibatis.executor.Executor
:执行器接口,代码如下:

public interface Executor {
/**
* ResultHandler 空对象
*/
ResultHandler NO_RESULT_HANDLER = null;
/**
* 更新或者插入或者删除
* 由传入的 MappedStatement 的 SQL 所决定
*/
int update(MappedStatement ms, Object parameter) throws SQLException;
/**
* 查询,带 ResultHandler + CacheKey + BoundSql
*/
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
CacheKey cacheKey, BoundSql boundSql) throws SQLException;
/**
* 查询,带 ResultHandler
*/
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
throws SQLException;
/**
* 查询,返回 Cursor 游标
*/
<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
/**
* 刷入批处理语句
*/
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);
/**
* 判断是否缓存
*/
boolean isCached(MappedStatement ms, CacheKey key);
/**
* 清除本地缓存
*/
void clearLocalCache();
/**
* 延迟加载
*/
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
/**
* 获得事务
*/
Transaction getTransaction();
/**
* 关闭事务
*/
void close(boolean forceRollback);
/**
* 判断事务是否关闭
*/
boolean isClosed();
/**
* 设置包装的 Executor 对象
*/
void setExecutorWrapper(Executor executor);
}

执行器接口定义了操作数据库的相关方法:

  • 数据库的读和写操作
  • 事务相关
  • 缓存相关
  • 设置延迟加载
  • 设置包装的 Executor 对象

BaseExecutor

org.apache.ibatis.executor.BaseExecutor
:实现Executor接口,提供骨架方法,指定几个抽象的方法交由不同的子类去实现,例如:

protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;

protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) throws SQLException;

protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds,
BoundSql boundSql) throws SQLException;

上面这四个方法交由不同的子类去实现,分别是:更新数据库、刷入批处理语句、查询数据库和查询数据返回游标

构造方法

public abstract class BaseExecutor implements Executor {

private static final Log log = LogFactory.getLog(BaseExecutor.class);

/**
* 事务对象
*/
protected Transaction transaction;
/**
* 包装的 Executor 对象
*/
protected Executor wrapper;
/**
* DeferredLoad(延迟加载)队列
*/
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
/**
* 本地缓存,即一级缓存,内部就是一个 HashMap 对象
*/
protected PerpetualCache localCache;
/**
* 本地输出类型参数的缓存,和存储过程有关
*/
protected PerpetualCache localOutputParameterCache;
/**
* 全局配置
*/
protected Configuration configuration;
/**
* 记录当前会话正在查询的数量
*/
protected int queryStack;
/**
* 是否关闭
*/
private boolean closed;

protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
}

其中上面的属性可根据注释进行查看

这里提一下

localCache
属性,本地缓存,用于一级缓存,MyBatis的一级缓存是什么呢?

每当我们使用 MyBatis 开启一次和数据库的会话,MyBatis 都会创建出一个 SqlSession 对象,表示与数据库的一次会话,而每个 SqlSession 都会创建一个 Executor 对象

在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,每一次查询都会访问一次数据库,如果在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,如果不采取一些措施的话,可能造成很大的资源浪费

为了解决这一问题,减少资源的浪费,MyBatis 会在每一次 SqlSession 会话对象中建立一个简单的缓存,将每次查询到的结果缓存起来,当下次查询的时候,如果之前已有完全一样的查询,则会先尝试从这个简单的缓存中获取结果返回给用户,不需要再进行一次数据库查询了 😈 注意,这个“简单的缓存”就是一级缓存,且默认开启,无法“关闭”

如下图所示,MyBatis 的一次会话:在一个 SqlSession 会话对象中创建一个

localCache
本地缓存,对于每一次查询,都会根据查询条件尝试去
localCache
本地缓存中获取缓存数据,如果存在,就直接从缓存中取出数据然后返回给用户,否则访问数据库进行查询,将查询结果存入缓存并返回给用户(如果设置的缓存区域为STATEMENT,默认为SESSION,在一次会话中所有查询执行后会清空当前 SqlSession 会话中的
localCache
本地缓存,相当于**“关闭”了一级缓存**)

所有的数据库更新操作都会清空当前 SqlSession 会话中的本地缓存

如上描述,MyBatis的一级缓存在多个 SqlSession 会话时,可能导致数据的不一致性,某一个 SqlSession 更新了数据而其他 SqlSession 无法获取到更新后的数据,出现数据不一致性,这种情况是不允许出现了,所以我们通常选择**“关闭”一级缓存**

clearLocalCache方法

clearLocalCache()
方法,清空一级(本地)缓存,如果全局配置中设置的
localCacheScope
缓存区域为
STATEMENT
(默认为
SESSION
),则在每一次查询后会调用该方法,相当于关闭了一级缓存,代码如下:

@Override
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}

createCacheKey方法

createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql)
方法,根据本地查询的相关信息创建一个
CacheKey
缓存key对象,代码如下:

@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// <1> 创建 CacheKey 对象
CacheKey cacheKey = new CacheKey();
// <2> 设置 id、offset、limit、sql 到 CacheKey 对象中
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
// <3> 设置 ParameterMapping 数组的元素对应的每个 value 到 CacheKey 对象中
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
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) {
// 入参对象为空则直接返回 null
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
// 入参有对应的类型处理器则直接返回该参数
value = parameterObject;
} else {
// 从入参对象中获取该属性的值
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
// <4> 设置 Environment.id 到 CacheKey 对象中
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
  1. 创建一个

    CacheKey
    实例对象

  2. 将入参中的

    id
    offset
    limit
    sql
    ,通过
    CacheKey
    update
    方法添加到其中,它的方法如下:

    public void update(Object object) {
    // 方法参数 object 的 hashcode
    int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
    this.count++;
    // checksum 为 baseHashCode 的求和
    this.checksum += baseHashCode;
    // 计算新的 hashcode 值
    baseHashCode *= this.count;
    this.hashcode = this.multiplier * this.hashcode + baseHashCode;
    // 添加 object 到 updateList 中
    this.updateList.add(object);
    }
  3. 获取本次查询的入参值,通过

    CacheKey
    update
    方法添加到其中

  4. 获取本次环境的

    Environment.id
    ,通过
    CacheKey
    update
    方法添加到其中

  5. 返回

    CacheKey
    实例对象,这样就可以为本次查询生成一个唯一的缓存key对象,可以看看
    CacheKey
    重写的
    equal
    方法:

    @Override
    public boolean equals(Object object) {
    if (this == object) {
    return true;
    }
    if (!(object instanceof CacheKey)) {
    return false;
    }
    final CacheKey cacheKey = (CacheKey) object;
    
    if (hashcode != cacheKey.hashcode) {
    return false;
    }
    if (checksum != cacheKey.checksum) {
    return false;
    }
    if (count != cacheKey.count) {
    return false;
    }
    for (int i = 0; i < updateList.size(); i++) {
    Object thisObject = updateList.get(i);
    Object thatObject = cacheKey.updateList.get(i);
    if (!ArrayUtil.equals(thisObject, thatObject)) {
    return false;
    }
    }
    return true;
    }

query相关方法

查询数据库因为涉及到一级缓存,所以这里有多层方法,最终访问数据库的

doQuery
方法是交由子类去实现的,总共分为三层:

  1. 根据入参获取BoundSql和CacheKey对象,然后再去调用查询方法

  2. 涉及到一级缓存和延迟加载的处理,缓存未命中则再去调用查询数据库的方法

  3. 保存一些信息供一级缓存使用,内部调用

    doQuery
    方法执行数据库的读操作

接下来我们分别来看看这三个方法

query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
方法,数据库查询操作的入口,代码如下

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
throws SQLException {
// <1> 获得 BoundSql 对象
BoundSql boundSql = ms.getBoundSql(parameter);
// <2> 创建 CacheKey 对象
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// <3> 查询
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
  1. 通过
    MappedStatement
    对象根据入参获取
    BoundSql
    对象,在《MyBatis初始化(四)之SQL初始化(下)》中的SqlSource小节中有讲到这个方法,如果是动态SQL则需要进行解析,获取到最终的SQL,替换成
    ?
    占位符
  2. 调用
    createCacheKey
    方法为本次查询创建一个
    CacheKey
    对象
  3. 继续调用
    query(...)
    方法执行查询

query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
方法,处理数据库查询操作,涉及到一级缓存,代码如下:

@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());
// <1> 已经关闭,则抛出 ExecutorException 异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// <2> 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存(配置了 flushCache = true)
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
// <3> queryStack + 1
queryStack++;
// <4> 从一级缓存中,获取查询结果
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) { // <4.1> 获取到,则进行处理
// 处理缓存存储过程的结果
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else { // <4.2> 获得不到,则从数据库中查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
// <5> queryStack - 1
queryStack--;
}
if (queryStack == 0) { // <6> 如果当前会话的所有查询执行完了
// <6.1> 执行延迟加载
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
// <6.2> 清空 deferredLoads
deferredLoads.clear();
// <6.3> 如果缓存级别是 LocalCacheScope.STATEMENT ,则进行清理
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
// <7> 返回查询结果
return list;
}
  1. 当前会话已经被关闭则抛出异常

  2. 如果

    queryStack
    0
    (表示是当前会话只有本次查询而没有其他的查询了),并且要求清空本地缓存(配置了
    flushCache=true
    ),那么直接清空一级(本地)缓存

  3. 当前会话正在查询的数量加一,

    queryStack++

  4. localCache
    一级缓存获取缓存的查询结果

    如果有缓存数据,则需要处理储存过程的情况,将需要作为出参(
    OUT
    )的参数设置到本次查询的入参的属性中
  5. 如果没有缓存数据,则调用
    queryFromDatabase
    方法,执行数据库查询操作
  • 当前会话正在查询的数量减一,

    queryStack--

  • 如果当前会话所有查询都执行完

      执行当前会话中的所有的延迟加载

      deferredLoads
      ,这种延迟加载属于查询后的延迟,和后续讲到的获取属性时再加载不同,这里的延迟加载是在哪里生成的呢?

      DefaultResultSetHandler
      中进行结果映射时,如果某个属性配置的是子查询,并且本次的子查询在一级缓存中有缓存数据,那么将会创建一个
      DeferredLoad
      对象保存在
      deferredLoads
      中,该属性值先设置为
      DEFERRED
      延迟加载对象(final修饰的Object对象),待当前会话所有的查询结束后,也就是当前执行步骤,则会从一级缓存获取到数据设置到返回结果中

    1. 清空所有的延迟加载

      deferredLoads
      对象

    2. 如果全局配置的缓存级别为STATEMENT(默认为SESSION),则清空当前会话中一级缓存的所有数据

  • 返回查询结果

  • queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    方法,执行数据库查询操作,代码如下:

    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds,
    ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    // <1> 在缓存中,添加正在执行的占位符对象,因为正在执行的查询不允许提前加载需要延迟加载的属性,可见 DeferredLoad#canLoad() 方法
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
    // <2> 执行读操作
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
    // <3> 从缓存中,移除占位对象
    localCache.removeObject(key);
    }
    // <4> 添加到缓存中
    localCache.putObject(key, list);
    // <5> 如果是存储过程,则将入参信息保存保存,跟一级缓存处理存储过程相关
    if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
    }
    // <6> 返回查询结果
    return list;
    }
    1. 在缓存中,添加正在执行的
      EXECUTION_PLACEHOLDER
      占位符对象,因为正在执行的查询不允许提前加载需要延迟加载的属性,可见 DeferredLoad#canLoad() 方法
    2. 调用查询数据库
      doQuery
      方法,该方法交由子类实现
    3. 删除第
      1
      步添加的占位符
    4. 将查询结果添加到
      localCache
      一级缓存
    5. 如果是存储过程,则将入参信息保存保存,跟一级缓存处理存储过程相关,可见上面的第
      个方法的第
      4.1
    6. 返回查询结果

    update方法

    update(MappedStatement ms, Object parameter)
    方法,执行更新数据库的操作,代码如下:

    @Override
    public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    // <1> 已经关闭,则抛出 ExecutorException 异常
    if (closed) {
    throw new ExecutorException("Executor was closed.");
    }
    // <2> 清空本地缓存
    clearLocalCache();
    // <3> 执行写操作
    return doUpdate(ms, parameter);
    }
    1. 当前会话已经被关闭则抛出异常
    2. 清空当前会话中一级缓存的所有数据
    3. 调用更新数据库
      doUpdate
      方法,该方法交由子类实现

    其他方法

    除了上面介绍的几个重要的方法以外,还有其他很多方法,例如获取当前事务,提交事务,回滚事务,关闭会话等等,这里我就不一一列出来了,请自行阅读该类

    SimpleExecutor

    org.apache.ibatis.executor.SimpleExecutor
    :继承 BaseExecutor 抽象类,简单的 Executor 实现类(默认使用

    • 每次对数据库的操作,都会创建对应的Statement对象

    • 执行完成后,关闭该Statement对象

    代码如下:

    public class SimpleExecutor extends BaseExecutor {
    
    public SimpleExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
    }
    
    @Override
    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
    Configuration configuration = ms.getConfiguration();
    // 创建 StatementHandler 对象
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    // 初始化 Statement 对象
    stmt = prepareStatement(handler, ms.getStatementLog());
    // 通过 StatementHandler 执行写操作
    return handler.update(stmt);
    } finally {
    // 关闭 Statement 对象
    closeStatement(stmt);
    }
    }
    
    @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 对象
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    // 初始化 Statement 对象
    stmt = prepareStatement(handler, ms.getStatementLog());
    // 通过 StatementHandler 执行读操作
    return handler.query(stmt, resultHandler);
    } finally {
    // 关闭 Statement 对象
    closeStatement(stmt);
    }
    }
    
    @Override
    protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
    throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    Cursor<E> cursor = handler.queryCursor(stmt);
    stmt.closeOnCompletion();
    return cursor;
    }
    
    @Override
    public List<BatchResult> doFlushStatements(boolean isRollback) {
    return Collections.emptyList();
    }
    
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 获得 Connection 对象,如果开启了 Debug 模式,则返回的是一个代理对象
    Connection connection = getConnection(statementLog);
    // 创建 Statement 或 PrepareStatement 对象
    stmt = handler.prepare(connection, transaction.getTimeout());
    // 往 Statement 中设置 SQL 语句上的参数,例如 PrepareStatement 的 ? 占位符
    handler.parameterize(stmt);
    return stmt;
    }
    }

    我们看到这些方法的实现,其中的步骤差不多都是一样的

    1. 获取

      Configuration
      全局配置对象

    2. 通过上面全局配置对象的

      newStatementHandler
      方法,创建
      RoutingStatementHandler
      对象,采用了装饰器模式,根据配置的
      StatementType
      创建对应的对象,默认为
      PreparedStatementHandler
      对象,进入
      BaseStatementHandler
      的构造方法你会发现有几个重要的步骤,在后续会讲到😈

      然后使用插件链对该对象进行应用,方法如下所示:

      // Configuration.java
      public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
      Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
      /*
      * 创建 RoutingStatementHandler 路由对象
      * 其中根据 StatementType 创建对应类型的 Statement 对象,默认为 PREPARED
      * 执行的方法都会路由到该对象
      */
      StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,
      rowBounds, resultHandler, boundSql);
      // 将 Configuration 全局配置中的所有插件应用在 StatementHandler 上面
      statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
      return statementHandler;
      }
    3. 调用

      prepareStatement
      方法初始化
      Statement
      对象

      从事务中获取一个
      Connection
      数据库连接,如果开启了Debug模式,则会为该
      Connection
      创建一个动态代理对象的实例,用于打印Debug日志
    4. 通过上面第
      2
      步创建的
      StatementHandler
      对象创建一个
      Statement
      对象(默认为
      PrepareStatement
      ),还会进行一些准备工作,例如:如果配置了
      KeyGenerator
      (设置主键),则会设置需要返回相应自增键,在后续会讲到😈
    5. Statement
      对象中设置SQL的参数,例如
      PrepareStatement
      ?
      占位符,实际上是通过
      DefaultParameterHandler
      设置占位符参数,在前面的《MyBatis初始化(四)之SQL初始化(下)》中有讲到
    6. 返回已经创建好的
      Statement
      对象,就等待着执行数据库操作了
  • 通过

    StatementHandler
    Statement
    进行数据库的操作,如果是查询操作则会通过
    DefaultResultSetHandler
    进行参数映射(非常复杂,后续逐步分析😈)

  • ReuseExecutor

    org.apache.ibatis.executor.ReuseExecutor
    :继承 BaseExecutor 抽象类,可重用的 Executor 实现类

    • 每次对数据库的操作,优先从当前会话的缓存中获取对应的Statement对象,如果不存在,才进行创建,创建好了会放入缓存中
    • 数据库操作执行完成后,不关闭该Statement对象
    • 其它的和SimpleExecutor是一致的

    我们来看看他的

    prepareStatement
    方法就好了:

    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    BoundSql boundSql = handler.getBoundSql();
    String sql = boundSql.getSql();
    /*
    * 根据需要执行的 SQL 语句判断 是否已有对应的 Statement 并且连接未关闭
    */
    if (hasStatementFor(sql)) {
    // 从缓存中获得 Statement 对象
    stmt = getStatement(sql);
    // 重新设置事务超时时间
    applyTransactionTimeout(stmt);
    } else {
    // 获得 Connection 对象
    Connection connection = getConnection(statementLog);
    // 初始化 Statement 对象
    stmt = handler.prepare(connection, transaction.getTimeout());
    // 将 Statement 添加到缓存中,key 值为 当前执行的 SQL 语句
    putStatement(sql, stmt);
    }
    // 往 Statement 中设置 SQL 语句上的参数,例如 PrepareStatement 的 ? 占位符
    handler.parameterize(stmt);
    return stmt;
    }

    在创建

    Statement
    对象前,会根据本次查询的SQL从本地的
    Map<String, Statement> statementMap
    获取到对应的
    Statement
    对象

    1. 如果缓存命中,并且该对象的连接未关闭,那么重新设置当前事务的超时时间

    2. 如果缓存未命中,则执行和

      SimpleExecutor
      中的
      prepareStatement
      方法相同逻辑创建一个
      Statement
      对象并放入
      statementMap
      缓存中

    BatchExecutor

    org.apache.ibatis.executor.BatchExecutor
    :继承 BaseExecutor 抽象类,支持批量执行的 Executor 实现类

    • 我们在执行数据库的更新操作时,可以通过

      Statement
      addBatch()
      方法将数据库操作添加到批处理中,等待调用
      Statement
      executeBatch()
      方法进行批处理

    • BatchExecutor
      维护了多个
      Statement
      对象,一个对象对应一个SQL(
      sql
      MappedStatement
      对象都相等),每个
      Statement
      对象对应多个数据库操作(同一个
      sql
      多种入参),就像苹果蓝里装了很多苹果,番茄蓝里装了很多番茄,最后,再统一倒进仓库

    由于JDBC不支持数据库查询的批处理,所以这里就不展示它数据库查询的实现方法,和SimpleExecutor一致,我们来看看其他的方法

    构造方法

    public class BatchExecutor extends BaseExecutor {
    
    public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;
    
    /**
    * Statement 数组
    */
    private final List<Statement> statementList = new ArrayList<>();
    /**
    * BatchResult 数组
    *
    * 每一个 BatchResult 元素,对应 {@link #statementList} 集合中的一个 Statement 元素
    */
    private final List<BatchResult> batchResultList = new ArrayList<>();
    /**
    * 上一次添加至批处理的 Statement 对象对应的SQL
    */
    private String currentSql;
    /**
    * 上一次添加至批处理的 Statement 对象对应的 MappedStatement 对象
    */
    private MappedStatement currentStatement;
    
    public BatchExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
    }
    }
    • statementList
      属性:维护多个
      Statement
      对象

    • batchResultList
      属性:维护多个
      BatchResult
      对象,每个对象对应上面的一个
      Statement
      对象,每个
      BatchResult
      对象包含同一个SQL和其每一次操作的入参

    • currentSql
      属性:上一次添加至批处理的
      Statement
      对象对应的SQL

    • currentStatement
      属性:上一次添加至批处理的
      Statement
      对象对应的
      MappedStatement
      对象

    BatchResult

    org.apache.ibatis.executor.BatchResult
    :相同SQL(
    sql
    MappedStatement
    对象都相等)聚合的结果,包含了同一个SQL每一次操作的入参,代码如下:

    public class BatchResult {
    
    /**
    * MappedStatement 对象
    */
    private final MappedStatement mappedStatement;
    /**
    * SQL
    */
    private final String sql;
    /**
    * 参数对象集合
    *
    * 每一个元素,对应一次操作的参数
    */
    private final List<Object> parameterObjects;
    
    /**
    * 更新数量集合
    *
    * 每一个元素,对应一次操作的更新数量
    */
    private int[] updateCounts;
    
    public BatchResult(MappedStatement mappedStatement, String sql) {
    super();
    this.mappedStatement = mappedStatement;
    this.sql = sql;
    this.parameterObjects = new ArrayList<>();
    }
    
    public BatchResult(MappedStatement mappedStatement, String sql, Object parameterObject) {
    this(mappedStatement, sql);
    addParameterObject(parameterObject);
    }
    
    public void addParameterObject(Object parameterObject) {
    this.parameterObjects.add(parameterObject);
    }
    }

    doUpdate方法

    更新数据库的操作,添加至批处理,需要调用

    doFlushStatements
    执行批处理,代码如下:

    @Override
    public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
    final Configuration configuration = ms.getConfiguration();
    // <1> 创建 StatementHandler 对象
    final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
    final BoundSql boundSql = handler.getBoundSql();
    final String sql = boundSql.getSql();
    final Statement stmt;
    // <2> 如果和上一次添加至批处理 Statement 对象对应的 currentSql 和 currentStatement 都一致,则聚合到 BatchResult 中
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
    // <2.1> 获取上一次添加至批处理 Statement 对象
    int last = statementList.size() - 1;
    stmt = statementList.get(last);
    // <2.2> 重新设置事务超时时间
    applyTransactionTimeout(stmt);
    // <2.3> 往 Statement 中设置 SQL 语句上的参数,例如 PrepareStatement 的 ? 占位符
    handler.parameterize(stmt);// fix Issues 322
    // <2.4> 获取上一次添加至批处理 Statement 对应的 BatchResult 对象,将本次的入参添加到其中
    BatchResult batchResult = batchResultList.get(last);
    batchResult.addParameterObject(parameterObject);
    } else { // <3> 否则,创建 Statement 和 BatchResult 对象
    // <3.1> 初始化 Statement 对象
    Connection connection = getConnection(ms.getStatementLog());
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt); // fix Issues 322
    // <3.2> 设置 currentSql 和 currentStatemen
    currentSql = sql;
    currentStatement = ms;
    // <3.3> 添加 Statement 到 statementList 中
    statementList.add(stmt);
    // <3.4> 创建 BatchResult 对象,并添加到 batchResultList 中
    batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
    // <4> 添加至批处理
    handler.batch(stmt);
    // <5> 返回 Integer.MIN_VALUE + 1002
    return BATCH_UPDATE_RETURN_VALUE;
    }
    1. 创建

      StatementHandler
      对象,和
      SimpleExecutor
      中一致,在后续会讲到😈

    2. 如果和上一次添加至批处理

      Statement
      对象对应的
      currentSql
      currentStatement
      都一致,则聚合到
      BatchResult

      获取上一次添加至批处理
      Statement
      对象
    3. 重新设置事务超时时间
    4. Statement
      中设置 SQL 语句上的参数,例如
      PrepareStatement
      ?
      占位符,在
      SimpleExecutor
      中已经讲到
    5. 获取上一次添加至批处理
      Statement
      对应的
      BatchResult
      对象,将本次的入参添加到其中
  • 否则,创建

    Statement
    BatchResult
    对象

      初始化
      Statement
      对象,在
      SimpleExecutor
      中已经讲到,这里就不再重复了
    1. 设置
      currentSql
      currentStatemen
      属性
    2. 添加
      Statement
      statementList
      集合中
    3. 创建
      BatchResult
      对象,并添加到
      batchResultList
      集合中
  • 添加至批处理

  • 返回

    Integer.MIN_VALUE + 1002
    ,为什么返回这个值?不清楚

  • doFlushStatements方法

    执行批处理,也就是将之前添加至批处理的数据库更新操作进行批处理,代码如下:

    @Override
    public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    try {
    List<BatchResult> results = new ArrayList<>();
    if (isRollback) { // <1> 如果 isRollback 为 true ,返回空数组
    return Collections.emptyList();
    }
    // <2> 遍历 statementList 和 batchResultList 数组,逐个提交批处理
    for (int i = 0, n = statementList.size(); i < n; i++) {
    // <2.1> 获得 Statement 和 BatchResult 对象
    Statement stmt = statementList.get(i);
    applyTransactionTimeout(stmt);
    BatchResult batchResult = batchResultList.get(i);
    try {
    // <2.2> 提交该 Statement 的批处理
    batchResult.setUpdateCounts(stmt.executeBatch());
    MappedStatement ms = batchResult.getMappedStatement();
    List<Object> parameterObjects = batchResult.getParameterObjects();
    /*
    * <2.3> 获得 KeyGenerator 对象
    * 1. 配置了 <selectKey /> 则会生成 SelectKeyGenerator 对象
    * 2. 配置了 useGeneratedKeys="true" 则会生成 Jdbc3KeyGenerator 对象
    * 否则为 NoKeyGenerator 对象
    */
    KeyGenerator keyGenerator = ms.getKeyGenerator();
    if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {
    Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
    // <2.3.1> 批处理入参对象集合,设置自增键
    jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
    } else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { // issue #141
    for (Object parameter : parameterObjects) {
    // <2.3.1> 一次处理每个入参对象,设置自增键
    keyGenerator.processAfter(this, ms, stmt, parameter);
    }
    }
    // Close statement to close cursor #1109
    // <2.4> 关闭 Statement 对象
    closeStatement(stmt);
    } catch (BatchUpdateException e) {
    // 如果发生异常,则抛出 BatchExecutorException 异常
    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);
    }
    // <2.5> 添加到结果集
    results.add(batchResult);
    }
    return results;
    } finally {
    // <3.1> 关闭 Statement 们
    for (Statement stmt : statementList) {
    closeStatement(stmt);
    }
    // <3.2> 置空 currentSql、statementList、batchResultList 属性
    currentSql = null;
    statementList.clear();
    batchResultList.clear();
    }
    }

    在调用doUpdate方法将数据库更新操作添加至批处理后,我们需要调用doFlushStatements方法执行批处理,逻辑如下:

    1. 如果

      isRollback
      true
      ,表示需要回退,返回空数组

    2. 遍历

      statementList
      batchResultList
      数组,逐个提交批处理

      获得
      Statement
      BatchResult
      对象
    3. 提交该
      Statement
      的批处理
    4. 获得
      KeyGenerator
      对象,用于设置自增键,在后续会讲到😈
    5. 关闭
      Statement
      对象
    6. BatchResult
      对象添加到结果集
  • 最后会关闭所有的

    Statement
    和清空当前会话中保存的数据

  • 二级缓存

    BaseExecutor
    中讲到的一级缓存中,缓存数据仅在当前的 SqlSession 会话中进行共享,可能会导致多个 SqlSession 出现数据不一致性的问题

    如果需要在多个 SqlSession 之间需要共享缓存数据,则需要使用到二级缓存

    开启

    二级缓存
    后,会使用
    CachingExecutor
    对象装饰其他的
    Executor
    类,这样会先在
    CachingExecutor
    进行
    二级缓存
    的查询,缓存未命中则进入装饰的对象中,进行
    一级缓存
    的查询

    流程如下图所示:

    《MyBatis初始化》的一系列文档中讲过MappedStatement会有一个Cache对象,是根据

    @CacheNamespace
    注解或
    <cache />
    标签创建的对象,该对象也会保存在
    Configuration
    全局配置对象的
    Map<String, Cache> caches = new StrictMap<>("Caches collection")
    中,key为所在的namespace,也可以通过
    @CacheNamespaceRef
    注解或
    <cache-ref />
    标签来指定其他namespace的Cache对象

    在全局配置对象中

    cacheEnabled
    是否开启缓存属性默认为
    true
    ,可以在
    mybatis-config.xml
    配置文件中添加以下配置关闭:

    <configuration>
    <settings>
    <setting name="cacheEnabled" value="false" />
    </settings>
    </configuration>

    我们来看看MyBatis是如何实现二级缓存

    CachingExecutor

    org.apache.ibatis.executor.CachingExecutor
    :实现 Executor 接口,支持二级缓存的 Executor 的实现类

    构造方法
    public class CachingExecutor implements Executor {
    /**
    * 被委托的 Executor 对象
    */
    private final Executor delegate;
    /**
    * TransactionalCacheManager 对象
    */
    private final TransactionalCacheManager tcm = new TransactionalCacheManager();
    
    public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    // 设置 delegate 被当前执行器所包装
    delegate.setExecutorWrapper(this);
    }
    }
    • delegate
      属性,为被委托的
      Executor
      对象,具体的数据库操作都是交由它去执行
    • tcm
      属性,
      TransactionalCacheManager
      对象,支持事务的缓存管理器,因为二级缓存是支持跨 SqlSession 共享的,此处需要考虑事务,那么,必然需要做到事务提交时,才将当前事务中查询时产生的缓存,同步到二级缓存中,所以需要通过
      TransactionalCacheManager
      来实现
    query方法

    处理数据库查询操作的方法,涉及到二级缓存,会将

    Cache
    二级缓存对象装饰成
    TransactionalCache
    对象并存放在
    TransactionalCacheManager
    管理器中,代码如下:

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
    ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    // <1> 获取 Cache 二级缓存对象
    Cache cache = ms.getCache();
    // <2> 如果配置了二级缓存
    if (cache != null) {
    // <2.1> 如果需要清空缓存,则进行清空
    flushCacheIfRequired(ms);
    // <2.2> 如果当前操作需要使用缓存(默认开启)
    if (ms.isUseCache() && resultHandler == null) {
    // <2.2.1> 如果是存储过程相关操作,保证所有的参数模式为 ParameterMode.IN
    ensureNoOutParams(ms, boundSql);
    // <2.2.2> 从二级缓存中获取结果,会装饰成 TransactionalCache
    List<E> list = (List<E>) tcm.getObject(cache, key);
    if (list == null) {
    // <2.2.3> 如果不存在,则从数据库中查询
    list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    // <2.2.4> 将缓存结果保存至 TransactionalCache
    tcm.putObject(cache, key, list); // issue #578 and #116
    }
    // <2.2.5> 直接返回结果
    return list;
    }
    }
    // <3> 没有使用二级缓存,则调用委托对象的方法
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
    1. 获取

      Cache
      二级缓存对象

    2. 如果该对象不为空,表示配置了二级缓存

      如果需要清空缓存,则进行清空
    3. 如果当前操作需要使用缓存(默认开启) 如果是存储过程相关操作,保证所有的参数模式为
      ParameterMode.IN
    4. 通过
      TransactionalCacheManager
      从二级缓存中获取结果,会装饰成
      TransactionalCach
      对象
    5. 如果缓存未命中,则调用委托对象的
      query
      方法
    6. 将缓存结果保存至
      TransactionalCache
      对象中,并未真正的保存至
      Cache
      二级缓存中,需要待事务提交才会保存过去,其中缓存未命中的也会设置缓存结果为null
    7. 直接返回结果
  • 没有使用二级缓存,则调用委托对象的方法

  • update方法
    @Override
    public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    // 如果需要清空缓存,则进行清空
    flushCacheIfRequired(ms);
    // 执行 delegate 对应的方法
    return delegate.update(ms, parameterObject);
    }
    
    private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    if (cache != null && ms.isFlushCacheRequired()) {
    tcm.clear(cache);
    }
    }

    数据库的更新操作,如果配置了需要清空缓存,则清空二级缓存

    这里就和一级缓存不同,一级缓存是所有的更新操作都会清空一级缓存

    commit方法
    @Override
    public void commit(boolean required) throws SQLException {
    // 执行 delegate 对应的方法
    delegate.commit(required);
    // 提交 TransactionalCacheManager
    tcm.commit();
    }

    在事务提交后,通过

    TransactionalCacheManager
    二级缓存管理器,将本次事务生成的缓存数据从
    TransactionalCach
    中设置到正真的
    Cache
    二级缓存中

    rollback方法
    @Override
    public void rollback(boolean required) throws SQLException {
    try {
    // 执行 delegate 对应的方法
    delegate.rollback(required);
    } finally {
    if (required) {
    // 回滚 TransactionalCacheManager
    tcm.rollback();
    }
    }
    }

    在事务回滚后,如果需要的话,通过

    TransactionalCacheManager
    二级缓存管理器,将本次事务生成的缓存数据从
    TransactionalCach
    中移除

    close方法
    @Override
    public void close(boolean forceRollback) {
    try {
    // issues #499, #524 and #573
    if (forceRollback) {
    tcm.rollback();
    } else {
    tcm.commit();
    }
    } finally {
    delegate.close(forceRollback);
    }
    }

    在事务关闭前,如果是强制回滚操作,则

    TransactionalCacheManager
    二级缓存管理器,将本次事务生成的缓存数据从
    TransactionalCach
    中移除,否则还是将缓存数据设置到正真的
    Cache
    二级缓存中

    TransactionalCacheManager

    org.apache.ibatis.cache.TransactionalCacheManager
    :二级缓存管理器,因为二级缓存是支持跨 SqlSession 共享的,所以需要通过它来实现,当事务提交时,才将当前事务中查询时产生的缓存,同步到二级缓存中,代码如下:

    public class TransactionalCacheManager {
    /**
    * Cache 和 TransactionalCache 的映射
    */
    private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
    
    public void clear(Cache cache) {
    getTransactionalCache(cache).clear();
    }
    
    public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
    }
    
    public void putObject(Cache cache, CacheKey key, Object value) {
    // 首先,获得 Cache 对应的 TransactionalCache 对象
    // 然后,添加 KV 到 TransactionalCache 对象中
    getTransactionalCache(cache).putObject(key, value);
    }
    
    public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
    txCache.commit();
    }
    }
    
    public void rollback() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
    txCache.rollback();
    }
    }
    
    private TransactionalCache getTransactionalCache(Cache cache) {
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
    }
    
    }
    • getTransactionalCache(Cache cache)
      方法,根据
      Cache
      二级缓存对象获取对应的
      TransactionalCache
      对象,如果没有则创建一个保存起来

    • getObject(Cache cache, CacheKey key)
      方法,会先调用
      getTransactionalCache(Cache cache)
      方法获取对应的
      TransactionalCache
      对象,然后根据
      CacheKey
      从该对象中获取缓存结果

    • putObject(Cache cache, CacheKey key, Object value)
      方法,同样也先调用
      getTransactionalCache(Cache cache)
      方法获取对应的
      TransactionalCache
      对象,根据该对象将结果进行缓存

    • commit()
      方法,遍历
      transactionalCaches
      ,依次调用
      TransactionalCache
      的提交方法

    • rollback()
      方法,遍历
      transactionalCaches
      ,依次调用
      TransactionalCache
      的回滚方法

    TransactionalCache

    org.apache.ibatis.cache.decorators.TransactionalCache
    :用来装饰二级缓存的对象,作为二级缓存一个事务的缓冲区

    在一个SqlSession会话中,该类包含所有需要添加至二级缓存的的缓存数据,当提交事务后会全部刷出到二级缓存中,或者事务回滚后移除这些缓存数据,代码如下:

    public class TransactionalCache implements Cache {
    
    private static final Log log = LogFactory.getLog(TransactionalCache.class);
    
    /**
    * 委托的 Cache 对象。
    *
    * 实际上,就是二级缓存 Cache 对象。
    */
    private final Cache delegate;
    /**
    * 提交时,清空 {@link #delegate}
    *
    * 初始时,该值为 false
    * 清理后{@link #clear()} 时,该值为 true ,表示持续处于清空状态
    *
    * 因为可能事务还未提交,所以不能直接清空所有的缓存,而是设置一个标记,获取缓存的时候返回 null 即可
    * 先清空下面这个待提交变量,待事务提交的时候才真正的清空缓存
    *
    */
    private boolean clearOnCommit;
    /**
    * 待提交的 Key-Value 映射
    */
    private final Map<Object, Object> entriesToAddOnCommit;
    /**
    * 查找不到的 KEY 集合
    */
    private final Set<Object> entriesMissedInCache;
    
    public TransactionalCache(Cache delegate) {
    this.delegate = delegate;
    this.clearOnCommit = false;
    this.entriesToAddOnCommit = new HashMap<>();
    this.entriesMissedInCache = new HashSet<>();
    }
    
    @Override
    public Object getObject(Object key) {
    // issue #116
    // <1> 从 delegate 中获取 key 对应的 value
    Object object = delegate.getObject(key);
    if (object == null) {// <2> 如果不存在,则添加到 entriesMissedInCache 中
    entriesMissedInCache.add(key);
    }
    // issue #146
    if (clearOnCommit) {// <3> 如果 clearOnCommit 为 true ,表示处于持续清空状态,则返回 null
    return null;
    } else {
    return object;
    }
    }
    
    @Override
    public void putObject(Object key, Object object) {
    // 暂存 KV 到 entriesToAddOnCommit 中
    entriesToAddOnCommit.put(key, object);
    }
    
    @Override
    public void clear() {
    // <1> 标记 clearOnCommit 为 true
    clearOnCommit = true;
    // <2> 清空 entriesToAddOnCommit
    entriesToAddOnCommit.clear();
    }
    
    public void commit() {
    // <1> 如果 clearOnCommit 为 true ,则清空 delegate 缓存
    if (clearOnCommit) {
    delegate.clear();
    }
    // 将 entriesToAddOnCommit、entriesMissedInCache 刷入 delegate 中
    flushPendingEntries();
    // 重置
    reset();
    }
    
    public void rollback() {
    // <1> 从 delegate 移除出 entriesMissedInCache
    unlockMissedEntries();
    // <2> 重置
    reset();
    }
    
    private void reset() {
    clearOnCommit = false;
    entriesToAddOnCommit.clear();
    entriesMissedInCache.clear();
    }
    
    private void flushPendingEntries() {
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
    delegate.putObject(entry.getKey(), entry.getValue());
    }
    for (Object entry : entriesMissedInCache) {
    if (!entriesToAddOnCommit.containsKey(entry)) {
    delegate.putObject(entry, null);
    }
    }
    }
    
    private void unlockMissedEntries() {
    for (Object entry : entriesMissedInCache) {
    try {
    delegate.removeObject(entry);
    } catch (Exception e) {
    log.warn("Unexpected exception while notifiying a rollback to the cache adapter."
    + "Consider upgrading your cache adapter to the latest version.  Cause: " + e);
    }
    }
    }
    }

    根据上面的注释查看每个属性的作用,我们依次来看下面的方法,看看在不同事务之前是如何处理二级缓存的

    • putObject(Object key, Object object)
      方法,添加缓存数据时,先把缓存数据保存在
      entriesToAddOnCommit
      中,这个对象属于当前事务,事务还未提交,其他事务是不能访问到的

    • clear()
      方法,设置
      clearOnCommit
      标记为true,告诉当前事务正处于持续清空状态,先把
      entriesToAddOnCommit
      清空,也就是当前事务中还未提交至二级缓存的缓存数据,事务还未提交,不能直接清空二级缓存中的数据,否则影响到其他事务了

    • commit()
      方法,事务提交后,如果
      clearOnCommit
      为true,表示正处于持续清空状态,需要先把二级缓存中的数据全部清空,然后再把当前事务生成的缓存设置到二级缓存中,然后重置当前对象

      这里为什么处于清空状态把二级缓存的数据清空后,还要将当前事务生成的缓存数据再设置到二级缓存中呢?因为当前事务调用

      clear()
      方法后可能有新生成了新的缓存数据,而不能把这些忽略掉

    • getObject(Object key)
      方法

        先从
        delegate
        二级缓存对象中获取结果
      1. 如果缓存未命中则将该key添加到
        entriesMissedInCache
        属性中,因为二级缓存也会将缓存未命中的key起来,数据为null
      2. 如果
        clearOnCommit
        为true,即使你缓存命中了也返回null,因为触发
        clear()
        方法的话,本来需要清空二级缓存的,但是事务还未提交,所以先标记一个缓存持续清理的这么一个状态,这样相当于在当前事务中既清空了二级缓存数据,也不影响其他事务的二级缓存数据
      3. 返回获取到的结果,可能为null

    Executor在哪被创建

    前面对Executor执行器接口以及实现类都有分析过,那么它是在哪创建的呢?

    《MyBatis初始化(一)之加载mybatis-config.xml》这一篇文档中讲到,整个的初始化入口在

    SqlSessionFactoryBuilder
    build
    方法中,创建的是一个
    DefaultSqlSessionFactory
    对象,该对象用来创建
    SqlSession
    会话的,我们来瞧一瞧:

    public class DefaultSqlSessionFactory implements SqlSessionFactory {
    
    private final Configuration configuration;
    
    @Override
    public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
    }
    
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,
    boolean autoCommit) {
    Transaction tx = null;
    try {
    // 获得 Environment 对象
    final Environment environment = configuration.getEnvironment();
    // 创建 Transaction 对象
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    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();
    }
    }
    }

    我们所有的数据库操作都是在MyBatis的一个

    SqlSession
    会话中执行的,在它被创建的时候,会先通过
    Configuration
    全局配置对象的
    newExecutor
    方法创建一个
    Executor
    执行器

    newExecutor(Transaction transaction, ExecutorType executorType)
    方法,根据执行器类型创建执行
    Executor
    执行器,代码如下:

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // <1> 获得执行器类型
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    // <2> 创建对应实现的 Executor 对象
    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);
    }
    // <3> 如果开启缓存,创建 CachingExecutor 对象,进行包装
    if (cacheEnabled) {
    executor = new CachingExecutor(executor);
    }
    // <4> 应用插件
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
    }
    1. 获得执行器类型,默认为
      SIMPLE
    2. 创建对应的
      Executor
      对象,默认就是
      SimpleExecutor
      执行器了
    3. 如果全局配置了开启二级缓存,则将
      Executor
      对象,封装成
      CachingExecutor
      对象
    4. 插件链应用该对象,在后续会讲到😈

    总结

    本文分析了MyBatis在执行SQL的过程中,都是在

    SimpleExecutor
    (默认类型)执行器中进行的,由它调用其他“组件”来完成数据库操作

    其中需要通过

    PrepareStatementHandler
    (默认)来创建对应的
    PrepareStatemen
    ,进行参数的设置等相关处理,执行数据库操作

    获取到结果后还需要通过

    DefaultResultSetHandler
    进行参数映射,转换成对应的Java对象,这两者在后续会进行分析

    关于MyBatis的缓存,存在局限性,我们通常不会使用,如有需要使用缓存,查看我的另一篇源码解析文档《JetCache源码分析》

    一级缓存

    仅限于单个 SqlSession 会话,多个 SqlSession 可能导致数据的不一致性,例如某一个 SqlSession 更新了数据而其他 SqlSession 无法获取到更新后的数据,出现数据不一致性,这种情况是不允许出现了

    二级缓存

    MyBatis配置

    二级缓存
    是通过在XML映射文件添加
    <cache / >
    标签创建的(注解也可以),所以不同的XML映射文件所对应的
    二级缓存
    对象可能不是同一个

    二级缓存
    虽然解决的
    一级缓存
    中存在的多个 SqlSession 会话可能出现脏读的问题,但还是针对同一个二级缓存对象不会出现这种情况,如果其他的XML映射文件修改了相应的数据,当前
    二级缓存
    获取到的缓存数据就不是最新的数据,也出现了脏读的问题

    例如,在一个XML映射文件中配置了二级缓存,获取到某个用户的信息并存放在对应的二级缓存对象中,其他的XML映射文件修改了这个用户的信息,那么之前那个缓存数据就不是最新的

    当然你可以所有的XML映射文件都指向同一个Cache对象(通过

    <cache-ref / >
    标签),这样就太局限了,所以MyBatis的缓存存在一定的缺陷,且缓存的数据仅仅是保存在了本地内存中,对于当前高并发的环境下是无法满足要求的,所以我们通常不使用MyBatis的缓存

    参考文章:芋道源码《精尽 MyBatis 源码分析》

    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: