Mybatis源码分析之Cache一级缓存原理(四)「建议收藏」

Mybatis源码分析之Cache一级缓存原理(四)「建议收藏」Mybatis源码分析之Cache一级缓存原理(四)

欢迎大家来到IT世界,在知识的湖畔探索吧!

之前的文章我已经基本讲解到了SqlSessionFactory、SqlSession、Excutor以及Mpper执行SQL过程,下面我来了解下myabtis的缓存,

它的缓存分为一级缓存和二级缓存,本文我们主要分析下一级缓存。

先看一个例子,代码还是之前(第一篇)的的demo

 public static void main(String[] args) throws Exception {
SqlSessionFactory sessionFactory = ;
String resource = "configuration.xml";
sessionFactory = new SqlSessionFactoryBuilder.build(Resources.getResourceAsReader(resource));
SqlSession sqlSession = sessionFactory.openSession;
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
System.out.println(userMapper.findUserById(1));
System.out.println(userMapper.findUserById(1));
}

欢迎大家来到IT世界,在知识的湖畔探索吧!

上面代码我们执行了两次userMapper.findUserById(1),结果如下图

Mybatis源码分析之Cache一级缓存原理(四)「建议收藏」

从执行结果看,DB的查询只有1次,那么第二次的查询结果是在怎么来的呢?

一:什么是一级缓存

每当我们使用MyBatis开启一次和数据库的会话,MyBatis会创建出一个SqlSession对象表示一次数据库会话。 在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。

为了解决这一问题,减少资源的浪费,MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。

基本的流程示意图如下:

Mybatis源码分析之Cache一级缓存原理(四)「建议收藏」

对于会话(Session)级别的数据缓存,我们称之为一级数据缓存,简称一级缓存。

二:如何执行缓存

我们知道mybatis的SQL执行最后是交给了Executor执行器来完成的,我们看下BaseExecutor类的源码

欢迎大家来到IT世界,在知识的湖畔探索吧! @Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}




@SuppressWarnings("unchecked")
@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 == ? (List<E>) localCache.getObject(key) : ;//localCache 本地缓存
if (list != ) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); //如果缓存没有就走DB
}
} 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;
}

通过上面的三个方法我们基本已经看明白了缓存的使用,它的本地缓存使用的是PerpetualCache类,内部其实还是一个HashMap,只是稍微做了封装而已。

我们再看下天的Key是如何生成的

 CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
欢迎大家来到IT世界,在知识的湖畔探索吧! @Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey;
cacheKey.update(ms.getId);
cacheKey.update(Integer.valueOf(rowBounds.getOffset));
cacheKey.update(Integer.valueOf(rowBounds.getLimit));
cacheKey.update(boundSql.getSql);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings;
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration.getTypeHandlerRegistry;
// mimic DefaultParameterHandler logic
for (int i = 0; i < parameterMappings.size; i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty;
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == ) {
value = ;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass)) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment != ) {
// issue #176
cacheKey.update(configuration.getEnvironment.getId);
}
return cacheKey;
}

它是通过statementId,params,rowBounds,BoundSql来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果。

我们用一张图来看清楚一级缓存的基本流程(本图网上早来的,自己懒得画了)

Mybatis源码分析之Cache一级缓存原理(四)「建议收藏」

三:一级缓存生命周期

1. MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,

Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

2. 如果SqlSession调用了close方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。

3. 如果SqlSession调用了clearCache,会清空PerpetualCache对象中的数据,但是该对象仍可使用。

4.SqlSession中执行了任何一个update操作(update、delete、insert) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/18259.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信