续写Mybatis二级缓存
上周由于临时有约,导致文章没写完,那么今天抽时间将这篇文章完善。废话不多说开整,如果想回顾上一篇文章内容 ---------->传送门:Mybatis二级缓存-1
已经讲了一级缓存产生的过程,那么我们是不是得了解下,它是如何失效的呢?毕竟不能长时间存在
那么我们得看下更新,删除相关操作了,执行新增或更新或删除操作,一级缓存就会被清除。
MyBatis处理新增或删除的时候,最终都是调用update方法,也就是说新增或者删除操作在MyBatis眼里都是一个更新操作。
让我们来看下 update方法的
不用说,看过我上一篇文章的就知道,这里执行器调用的update方法一定是 调用的cachingExecutor执行器中的 update 方法
这里的flushCacheIfRequired方法清除的是二级缓存,我们之后会分析。 CachingExecutor委托给了(之前已经分析过)SimpleExecutor的update方法,SimpleExecutor没有Override父类BaseExecutor的update方法,因此我们看BaseExecutor的update方法:
看这个方法 clearLocalCache();
看到当前,相信大家就对于一级缓存已经有一定的了解了,那么耐心下去接着看二级缓存呗,二级缓存就相比较于一级缓存 就是作用域范围变大了,一级缓存找不到的情况去看二级,二级缓存跨mapper的。
二级缓存
二级缓存的作用域是全局的,二级缓存在SqlSession关闭或提交之后才会生效。
研究二级缓存,得先带大家看下MappedStatement这个类
这个类存的就是 mybatis解析 select,update, insert 这些标签,以及返回类型等,对xml中各种信息数据解析的封装集合。
二级缓存跟一级缓存不同,一级缓默认开启。 二级缓存需要配置
- mybatis全局配置文件中的setting中的cacheEnabled需要为true(默认为true,不设置也行)
- mapper配置文件中需要加入节点
- mapper配置文件中的select节点需要加上属性useCache需要为true(默认为true,不设置也行)
一般在mapper文件上加上这句配置即可:节点
测试
@Test
public void testCache2() {
SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
try {
String sql = "org.format.mybatis.cache.UserMapper.getById";
User user = (User)sqlSession.selectOne(sql, 1);
log.debug(user);
// 注意,这里一定要提交。 不提交还是会查询两次数据库
sqlSession.commit();
User user2 = (User)sqlSession2.selectOne(sql, 1);
log.debug(user2);
} finally {
sqlSession.close();
sqlSession2.close();
}
}
MyBatis仅进行了一次数据库查询:
==> Preparing: select * from USERS WHERE ID = ?
==> Parameters: 1(Integer)
<== Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
不同SqlSession,查询相同语句,第一次查询之后close SqlSession:
@Test
public void testCache2() {
SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
try {
String sql = "org.format.mybatis.cache.UserMapper.getById";
User user = (User)sqlSession.selectOne(sql, 1);
log.debug(user);
sqlSession.close();
User user2 = (User)sqlSession2.selectOne(sql, 1);
log.debug(user2);
} finally {
sqlSession2.close();
}
}
MyBatis仅进行了一次数据库查询:
==> Preparing: select * from USERS WHERE ID = ?
==> Parameters: 1(Integer)
<== Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
不同SqlSesson,查询相同语句。 第一次查询之后SqlSession不提交:
@Test
public void testCache2() {
SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
try {
String sql = "org.format.mybatis.cache.UserMapper.getById";
User user = (User)sqlSession.selectOne(sql, 1);
log.debug(user);
User user2 = (User)sqlSession2.selectOne(sql, 1);
log.debug(user2);
} finally {
sqlSession.close();
sqlSession2.close();
}
}
MyBatis执行了两次数据库查询:
==> Preparing: select * from USERS WHERE ID = ?
==> Parameters: 1(Integer)
<== Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
==> Preparing: select * from USERS WHERE ID = ?
==> Parameters: 1(Integer)
<== Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
解析mapper文件 主要就是通过如下方法
解析完cache标签之后会使用builderAssistant的userNewCache方法,这里的builderAssistant是一个MapperBuilderAssistant类型的帮助类,每个XMLMappedBuilder构造的时候都会实例化这个属性,MapperBuilderAssistant类内部有个Cache类型的currentCache属性,这个属性也就是mapper配置文件中cache节点所代表的值
public Cache useNewCache(Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval, Integer size, boolean readWrite, boolean blocking, Properties props) {
typeClass = (Class)this.valueOrDefault(typeClass, PerpetualCache.class);
evictionClass = (Class)this.valueOrDefault(evictionClass, LruCache.class);
Cache cache = (new CacheBuilder(this.currentNamespace)).implementation(typeClass).addDecorator(evictionClass).clearInterval(flushInterval).size(size).readWrite(readWrite).blocking(blocking).properties(props).build();
this.configuration.addCache(cache);
this.currentCache = cache;
return cache;
}
public void parseStatementNode() {
String id = context.getStringAttribute(“id”);
String databaseId = context.getStringAttribute(“databaseId”);
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
这段代码前面都是解析一些标签的属性,我们看到了最后一行使用builderAssistant添加MappedStatement,其中builderAssistant属性是构造XMLStatementBuilder的时候通过XMLMappedBuilder传入的,我们继续看builderAssistant的addMappedStatement方法
private void setStatementCache(boolean isSelect, boolean flushCache, boolean useCache, Cache cache, org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder) {
flushCache = (Boolean)this.valueOrDefault(flushCache, !isSelect);
useCache = (Boolean)this.valueOrDefault(useCache, isSelect);
statementBuilder.flushCacheRequired(flushCache);
statementBuilder.useCache(useCache);
statementBuilder.cache(cache);
}
最终mapper配置文件中的被设置到了XMLMapperBuilder的builderAssistant属性中,XMLMapperBuilder中使用XMLStatementBuilder遍历CRUD节点,遍历CRUD节点的时候将这个cache节点设置到这些CRUD节点中,这个cache就是所谓的二级缓存!
接下来我们回到最开始讲的
最终结论:使用二级缓存之后:查询数据的话,先从二级缓存中拿数据,如果没有的话,去一级缓存中拿,一级缓存也没有的话再查询数据库
接下来我们来验证为什么SqlSession commit或close之后,二级缓存才会生效这个问题。
CachingExecutor的commit方法:
一步一步进入 最终
缓存接口的实现类
未完待续--------------------------------------------- 下一次讲 mybatis 责任链+handler