Mybatis—学习过程—Mybatis缓存
1.Mybatis缓存机制
- Mybatis也提供了缓存,分为一级缓存,二级缓存
- 二级缓存级别比一级缓存要大
- 图解如下:
- 每个sqlSession中有一个一级缓存,所以说一级缓存是sqlSession级别,同构SqlSessionFactory.openSession()生产出来的每个sqlSession对象中存在一个map集合,这个map集合里面存放的就是缓存数据,可以说一级缓存的底层数据结构就是一个map集合(HashMap)
- 每个sqlSession对象中都有一个属于自己的map集合,所以不同的sqlSession中他们的一级缓存是互不影响的
- 从图上看出这里的二级缓存横跨了3个sqlSession,所以说范围肯定是跨sqlSession的
- Mybatis的二级缓存是mapper级别或者说是namespace级别,所以所多个sqlSession共同去操作同一个Mapper的sql语句,这多个sqlSession是可以共享一个二级缓存的
2.Mybatis一级缓存:在Mybatis中一级缓存是默认开启
- 证明一级缓存的存在:在一个sqlSession中,对user表根据id查询两次,查看他们sql发出的情况
实例代码/** * 根据id进行两次sql查询 */ @Test public void firstLevelCache(){ //第一次查询id=1的用户 User user1 = iUserMapper.findUserById(1); //第二次查询id=1的用户 User user2 = iUserMapper.findUserById(1); System.out.println(user1==user2); }
- sqlSession对象中是有一个map集合的,这个map集合是Mybatis一级缓存的数据结构
HashMap
步骤分析
://第一次查询id=1的用户 User user1 = iUserMapper.findUserById(1);
- 首先是去一级缓存中去查询,如果有直接返回。如果没有就查询数据库,同时将查询结果放到一级缓存中。
- 当我们存的时候key和value分别是什么?
value值
是查询出来的User对象。key应该叫cacheKey
是由statementId,params(当前的参数’1‘)
,boundSql(封装着我们要执行的sql语句)
,rowBounds(分页参数)
组成。 - boundSql:Mybatis底层封装的一个对象,里面封装着当前要执行的sql语句。
- rowBounds:是一个分页对象
//第二次查询id=1的用户 User user2 = iUserMapper.findUserById(1);
- 第二次查询和第一次查询刚开始是一样的
先去一级缓存中去查询,如果有直接返回。如果没有就查询数据库,同时将查询结果放到一级缓存中
。但是第二次查询是可以从一级缓存中拿到数据的,因为第二次查询要执行sql以及参数和第一次查询用的sql和参数完全一样。所以我们可以在一级缓存中根据cacheKey得到User对象 - 所以说当前的user1和user2是同一个User对象,完全一样。
- 这时候我们在第二次查询前加上一段
更新
的代码//更新用户 User user = new User(); user.setId(1); user.setUsername("tom"); iUserMapper.updateUser(user); sqlSession.commit();
- 做
增删改
操作,并进行了事务提交,就是刷新一级缓存,所以之后再有查询操作,还是先去一级缓存中去查询,如果有直接返回。如果没有就查询数据库,同时将查询结果放到一级缓存中
。 - 除了
增删改
操作,进行了事务提交,可以刷新一级缓存,我们还可以手动刷新一级缓存代码是sqlSession对象.clearCache();
3.Mybatis一级缓存总结
4.Mybatis一级缓存原理以及源码
思考问题
-
一级缓存到底是什么?
-
一级缓存什么时候被创建
-
一级缓存的工作流程是什么样的
-
分析源码要找到入口,目前一想到一级缓存就是SqlSession,所以目前的入口是
SqlSession
-
进入
SqlSession
接口中先找关于缓存的方法,找到一个void clearCache();
,所以现在clearCache
作为入口了 -
又因为
SqlSession
是个接口,所以我们要找void clearCache();
的实现类DefaultSqlSession
中的clearCache
方法了 -
发现又调用了
clearLocalCache
方法,所以点击它,之后发现他又在一个接口中,找他的实现类BaseExecutor类中的clearLocalCache
方法,发现this.localCache.clear();
于是点击clear方法进入到PerpetualCache
类中看到clear
方法 -
流程图
-
我们可以看到
PerpetualCache
类中的clear
方法调用了this.cache.clear();
,cache是缓存对象,向上查找会发现一行代码private Map<Object, Object> cache = new HashMap();
,cache就是变量名,机构就是HashMap,所以说this.cache.clear();
也是this.Map.clear();
,每个SqlSession都会存放这么一个Map的引用 -
所以说一级缓存到底是什么,
其实底层就是HashMap
-
接下来看这个缓存
cache
是何时被创建的,应该在Executor
这部分寻找,为什么呢?原因是我们当初在自定义持久层框架的时候,Executor
是一个执行器,他的作用就是执行sql和底层JDBC,又因为之前了解一级缓存的时候说道进行查询操作的时候会先查找缓存,有就返回,没有就查库,所以说应该去Executor
这部分寻找 -
去
Executor
这部分寻找发现CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4);
这个方法,点击进入BaseExecutor
,找到对应的createCacheKey
方法,开始分析public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { if (this.closed) { throw new ExecutorException("Executor was closed."); } else { //再执行这个 createCacheKey 方法之前先new了一个CacheKey对象 CacheKey cacheKey = new CacheKey(); //cacheKey通过update方法设置了一些属性 //ms.getId()就是namespace.id cacheKey.update(ms.getId()); //rowBounds.getOffset() 和 rowBounds.getLimit()是设置分页参数,默认值是0,因为是int类型 cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); //boundSql.getSql()是获取到需要执行的sql cacheKey.update(boundSql.getSql()); //所以由此可见cacheKey就是由statementId,分页参数和底层要执行的sql语句组成的 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); Iterator var8 = parameterMappings.iterator(); while(var8.hasNext()) { ParameterMapping parameterMapping = (ParameterMapping)var8.next(); 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); } } //configuration对应的就是sqlMapConfig.xml配置文件 if (this.configuration.getEnvironment() != null) { //this.configuration.getEnvironment().getId()中的id是什么 //其实就是 <environment id="development"> 这个标签中的id的值 //再然后查看一下update的方法,直接看最后一句 this.updateList.add(object); //这个updateList就是一个ArrayList的集合 //也是在这句代码中 this.updateList.add(object); 完成了cacheKey中的4个值的封装 cacheKey.update(this.configuration.getEnvironment().getId()); } //执行到return cacheKey;的时候就意味着cacheKey已经被创造好了 return cacheKey; } }
-
开始思考这个
createCacheKey
方法是何时被调用的,也是说何时生成cacheKey
的,这时候会向自定义持久层框架的时候,无论调用什么方法,无论是userMapper.findUserById(1)
或者其他,最终执行的都是Executor
执行器中的query
方法,query
方法是最终底层执行的方法,所以说再执行query时被调用了public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //首先在 MappedStatement 中获得了一个我们要执行的sql,在boundSql中封装着 BoundSql boundSql = ms.getBoundSql(parameter); //接下来就调用了 createCacheKey 直接拿到一个 cacheKey,这个cacheKey就是要放到一级缓存中的 CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql); //接下来要走底层的另一个query方法了,就在下面 return this.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 { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (this.closed) { throw new ExecutorException("Executor was closed."); } else { if (this.queryStack == 0 && ms.isFlushCacheRequired()) { this.clearLocalCache(); } List list; try { ++this.queryStack; //根据刚刚生成 CacheKey key 传递过来,从一级缓存进行获取到值 list list = resultHandler == null ? (List)this.localCache.getObject(key) : null; //如果list的值不为空,直接调用handleLocallyCachedOutputParameters方法返回值 if (list != null) { this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { //这时list的值为空,直接调用queryFromDatabase方法,这个方法应该是查询数据库的方法, //查看queryFromDatabase方法,请先下部分代码 list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { --this.queryStack; } if (this.queryStack == 0) { Iterator var8 = this.deferredLoads.iterator(); while(var8.hasNext()) { BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next(); deferredLoad.load(); } this.deferredLoads.clear(); if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { this.clearLocalCache(); } } return list; } }
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); } //这个key就是刚生成的cacheKey值,list是通过doQuery查询数据库获得的结果 this.localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { this.localOutputParameterCache.putObject(key, parameter); } return list; }