2020年,你好。
在哪里
每次当我从sqlsession工厂获取一个sqlsession时,都会创建一个BaseExecutor执行器,而一级缓存跟随执行器一起创建
///DefaultSqlSessionFactory
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//创建一个执行器
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
//configuration
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
//BaseExecutor
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
//一级缓存
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
也就说一个会话配一个一级缓存。 一级缓存的作用范围是sqlsesion, 其生命周期也跟随sqlsession生命周期
长什么样
一级缓存用PerpetualCache类表示,PerpetualCache 实现了Cache接口。而Cache 接口是Mybatis定义的缓存接口。PerpetualCache只是众多Cache 实现中的一种。
public class PerpetualCache implements Cache {
private String id;
private Map<Object, Object> cache = new HashMap<Object, Object>();
public PerpetualCache(String id) {
this.id = id;
}
}
可以看出,PerpetualCache 的本质就是HashMap。
如何用
首先得讲讲缓存的K值
CacheKey
在Mybatis中不管是一级缓存,还是二级缓存,都使用CacheKey 类对象作为K值。
CacheKey 值的相等判断,作为命中的决定因素就显得尤为重要了。
看CacheKey的创建
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
CacheKey cacheKey = new CacheKey();
//1.statementId
cacheKey.update(ms.getId());
//2. rowBounds.offset
cacheKey.update(rowBounds.getOffset());
//3. rowBounds.limit
cacheKey.update(rowBounds.getLimit());
//4. SQL语句
cacheKey.update(boundSql.getSql());
//5. 将每一个要传递给JDBC的参数值也更新到CacheKey中
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
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);
}
}
//6. 环境id
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
//CacheKey hashcode计算
public void update(Object object) {
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
count++;
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
updateList.add(object);
}
从其创建可以看出CacheKey的决定条件:
statementId + rowBounds + 传递给JDBC的SQL + 传递给JDBC的参数值+Environment.id
每个条件都会被计算到hashcode 的值中。
开启一级缓存
默认是开启的
工作流程
当我们使用sqlsession 执行查询相关操作时, 最终会执行BaseExecutor.query。此时一级缓存开始工作
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
//创建key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;//查询栈,入栈
//先从缓存中取值
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//没有命中缓存,从数据库中取值
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
//从数据库中查询,并把结果缓存到一级缓存中,
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;
}
流程总结:
- 首先根据条件创建CacheKey
- 根据CacheKey 去一级缓存中取值,命中,返回缓存中数据
- 未命中,去数据库中查询,并以CacheKey 为K将查询结果缓存起来。
清空缓存
一级缓存比较简单,没有所谓的过期清除,更新操作。这样意味着,我们在使用一级缓存时,要把一级缓存可能造成的数据不一致情况考虑进来。
一级缓存内数据存活期比较短暂。
清空时机:
1.同一个sqlsesion执行更新操作时,
public int update(MappedStatement ms, Object parameter) throws SQLException {
//清空一级缓存。
clearLocalCache();
return doUpdate(ms, parameter);
}
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}
2.当我在代码中主动调用sqlsesion#clearCache方法清空
public void clearCache() {
executor.clearLocalCache();
}
3.当调用sqlseesion#close方法时, 此时直接将 一级缓存对象释放了。
public void close() {
try {
executor.close(isCommitOrRollbackRequired(false));
closeCursors();
dirty = false;
} finally {
ErrorContext.instance().reset();
}
}
public void close(boolean forceRollback) {
try {
...
} catch (SQLException e) {
} finally {
transaction = null;
deferredLoads = null;
localCache = null;//直接把localCache置空
localOutputParameterCache = null;
closed = true;
}
}
4.sqlsession 被回收,BaseExecutor ,一级缓存一并回收
禁用一级缓存
如果我们不想启用一级缓存怎么弄?
禁用一级缓存的奥秘就在BaseExecutor#query 方法中这两行中。
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
...
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
1.SQL级别
ms.isFlushCacheRequired==true 对应的配置文件中
<select id="queryTrastatusDateMax" resultType="String" flushCache="true">
2.全局级别
configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT 对应配置文件中
<setting name="localCacheScope" value="STATEMENT" />
总结
一级缓存相比与二级缓存比较简单。
总结起来:
- 一级缓存实现类PerpetualCache ,本质是HashMap的一次封装
- 一级缓存的作用范围SqlSession
- 更新操作会清空一级缓存
如果本文任何错误,请批评指教,不胜感激 !
微信公众号:源码行动
享学源码,行动起来,来源码行动