LD is tigger forever,CG are not brothers forever, throw the pot and shine forever.
Modesty is not false, solid is not naive, treacherous but not deceitful, stay with good people, and stay away from poor people.
talk is cheap, show others the code,Keep progress,make a better result.
Survive during the day and develop at night。
目录
概 述
每当我们使用 MyBatis 开启一次和数据库的会话,MyBatis 会创建出一个 SqlSession 对象表示一次数据库会话。
在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。
为了解决这一问题,减少资源的浪费,MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。
mybatis的缓存有一级缓存和二级缓存。
一级缓存
一级缓存是默认开启的,作用域是session级别的,缓存的key格式如下:
cache key: id + sql + limit + offset
在commit之前,第一次查询结果换以key value的形式存起来,如果有相同的key进来,直接返回value,这样有助于减轻数据的压力。
org.apache.ibatis.executor.BaseExecutor#createCacheKey
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
for(int i = 0; i < parameterMappings.size(); ++i) {
ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
String propertyName = parameterMapping.getProperty();
Object value;
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 = this.configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (this.configuration.getEnvironment() != null) {
cacheKey.update(this.configuration.getEnvironment().getId());
}
return cacheKey;
}
}
查询数据库并存入一级缓存的语句
org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
List list;
try {
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
this.localCache.removeObject(key);
}
//将查询出来的结果存入一级缓存
this.localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
//如果是存储过程把参数存入localOutputParameterCache
this.localOutputParameterCache.putObject(key, parameter);
}
return list;
}
并且当commit或者rollback的时候会清除缓存,并且当执行insert、update、delete的时候也会清除缓存。
删除缓存:
org.apache.ibatis.executor.BaseExecutor#update
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
//删除一级缓存
this.clearLocalCache();
return this.doUpdate(ms, parameter);
}
}
org.apache.ibatis.executor.BaseExecutor#commit
public void commit(boolean required) throws SQLException {
if (this.closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
} else {
//删除一级缓存
this.clearLocalCache();
this.flushStatements();
if (required) {
this.transaction.commit();
}
}
}
org.apache.ibatis.executor.BaseExecutor#rollback
public void rollback(boolean required) throws SQLException {
if (!this.closed) {
try {
//删除一级缓存
this.clearLocalCache();
this.flushStatements(true);
} finally {
if (required) {
this.transaction.rollback();
}
}
}
}
二级缓存
二级缓存是手动开启的,作用域为sessionfactory(也可以说MapperStatement级缓存,也就是一个namespace就会有一个缓存),因为二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的,也就是要求实现Serializable接口,如果存储在内存中的话,实测不序列化也可以的。
如果开启了二级缓存的话,你的Executor将会被装饰成CachingExecutor,缓存是通过CachingExecutor来操作的,查询出来的结果会存在statement中的cache中,若有更新,删除类的操作默认就会清空该MapperStatement的cache(也可以通过修改xml中的属性,让它不执行),不会影响其他的MapperStatement。
在这里插入代码片
org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? this.defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Object 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);
}
//是否开启缓存,传入的参数为SimpleExecutor
if (this.cacheEnabled) {
executor = new CachingExecutor((Executor)executor);
}
//责任链模式拦截器
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
return executor;
}
总结:
一级缓存是自动开启的,sqlSession级别的缓存,查询结果存放在BaseExecutor中的localCache中。
果第一次做完查询,接着做一次update | insert | delete | commit | rollback操作,则会清除缓存,第二次查询则继续走数据库。
对于一级缓存不同的sqlSession之间的缓存是互相不影响的。
二级缓存是手动开启的,作用域为sessionfactory,也可以说MapperStatement级缓存,也就是一个namespace(mapper.xml)就会有一个缓存,不同的sqlSession之间的缓存是共享的
因为二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的,也就是要求实现Serializable接口,如果存储在内存中的话,实测不序列化也可以的。
一般为了避免出现脏数据,所以我们可以在每一次的insert | update | delete操作后都进行缓存刷新,也就是在Statement配置中配置flushCache属性,如下:
<!--刷新二级缓存 flushCache="true"-->
<update id="updateByPrimaryKey" parameterType="com.demo.mybatis.pojo.User" flushCache="true">
update user
set name = #{name,jdbcType=VARCHAR},
age = #{age,jdbcType=INTEGER}
where id = #{id,jdbcType=INTEGER}
</update>
相关工具如下:
分析:
小结:
主要讲述了接下来Mybatis缓存源码分析 CachingExecutor 源码详解
,请大家指正~
参考资料和推荐阅读
1.链接: 参考资料.