BaseExecutor-抽象类

其是Executor接口的实现类但为抽象类,另外一个则为具体实现类为CachingExecutor,主要是通过装饰器的设计模式在原来的executor上再附上缓存的属性,有兴趣的可自行查阅。先从构造函数看一发

  protected BaseExecutor(Configuration configuration, Transaction transaction) {
//事务对象
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
//表明exector的状态
this.closed = false;
//主文件属性,主要获取MappedStatement对象
this.configuration = configuration;
this.wrapper = this;
}

BaseExecutor#update()-SqlSession之insert/update/delete入口

具体源码如下

  @Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清除本地缓存,基于SqlSession范围作用
clearLocalCache();
//供子类复写执行CUD操作
return doUpdate(ms, parameter);
}

BaseExecutor#query()-SqlSession之select入口

具体源码如下

  @Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取绑定的sql,并将参数对象与sql语句的#{}一一对应
BoundSql boundSql = ms.getBoundSql(parameter);
//获取cacheKey供缓存,包含完整的语句、参数等,确保CacheKey的唯一性
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
//比原先多传入CacheKey和BoundSql参数
return query(ms, parameter, rowBounds, resultHandler, key, 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());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//是否清除本地缓存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
//如果查询的语句已存在本地缓存中,则直接从本地获取,反之从数据库中读取内容
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//此处尝试对Callable类型的表达式进行处理,主要是针对mode=out类型的参数
//此参数主要是通过map来定义,直接从map中获取
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对CRUD操作均转化为对子类的doUpdate()/doQuery()方法的调用,并一般都会相应的结果进行缓存以免频繁请求数据库导致性能下降(称之为一级缓存)。本文则从SimpleExecutor子类来进行分析

SimpleExecutor

分别看下SimpleExecutor复写的doUpdate()和doQuery()方法,具体源码如下

doUpdate()

  @Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Configuration configuration = ms.getConfiguration();
//创建StatementHandler来处理update()
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
//创建表达式对象Statement
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
}

doQuery()

  @Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
//创建StatementHandler来处理query()
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//创建表达式对象Statement
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
}

SimpleExecutor#prepareStatement()-创建预表达式对象

逻辑如下

  //handler对象对应的为RoutingStatementHandler对象,其实也是个适配管理类
//可根据MappedStatement的statementType来确定表达式处理handler类,后续讲解
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//获取连接对象,如果该日志等级为debug,则会打印相应的处理日志,采用代理实现
Connection connection = getConnection(statementLog);
//创建真实的Statement对象,比如SimpleStatement/PreparedStatement/CallableStatement
stmt = handler.prepare(connection, transaction.getTimeout());
//请求参数,常用在preparedStatement用来设置相应的请求参数
handler.parameterize(stmt);
return stmt;
}

关于缓存

这里的org.apache.ibatis.executor.BaseExecutor以及org.apache.ibatis.executor.CachingExecutor在执行相应的SQL语句查询前都会进行一次相应的缓存处理。前者称之为一级缓存,后者称之为二级缓存

什么意思呢???


一级缓存

首先有必要先解释下一级缓存,从上文的代码中可以发现其就是针对相同的SQL查询,会优先从本地缓存查询,如果没有再从数据库查询;如果对应的SqlSession一旦有CUD操作,则SqlSession内的本地缓存将被重新清除,下一次的R操作则必须从数据库中读取了~~~~

由此可以得知,mybatis的一级缓存是基于SqlSession的,不同的SqlSession针对相同的SQL操作有可能得到的返回结果是不一样。且一旦有更新操作,则一级缓存缓存的所有结果集都被清空~~~


二级缓存

再而对二级缓存作下解释,其是由CachingExecutor在执行query()方法的时候会再加一个判断,笔者把代码贴出来好针对性的理解

  @Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 针对MappedStatement级别获取Cache对象
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
// query操作一般此判断均通过 ResultHandler一般DAO接口不会定义此入参
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 从mappedStatement的cache对象中获取对应的结果集
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 为空则还是尝试通过一级缓存去获取
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 放入对应的cache中(当Sqlsession执行commit()操作时则塞入)
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

可以发现,二级缓存是基于namespace作用域来的,也就是MappedStatement级别。一旦有对应的MappedStatement对象执行了CUD操作则会清空MappedStatement级别对应的Cache,而不影响其它MappedStatement的对象。

举例子,也就是说针对用户表以及用户具体信息两张表的MappedStatement,在开启二级缓存的时候就是独立而互相不影响的~~~这或许会造成不一致的现象


写下简短的结论

小结

04-15 13:08