MyBatis的一二级缓存
一级缓存
一级缓存默认是开启的,生命周期和SqlSession相同。一个会话中每次执行一个查询操作时,会先查询二级缓存,如果二级缓存没查到或者二级缓存未开启就会从一级缓存中查询,如果一级缓存也未查到就从数据库中查询
一级缓存使用条件
- 必须是相同的SQL语句
- 必须是相同的参数
- 必须是同一个会话
- 必须是同一个namespace即相同的mapper接口
- 必须是同一个mapper接口中的同一个方法
- 查询之前没有增删改操作(不管操作是否成功,只要进行了增删改操作就会清空一级缓存)
- 查询之前没有执行sqlSession.clearCache()方法
二级缓存
二级缓存需要我们手动开启,生命周期和SqlSessionFactory相同。可以适用于多个session之间共享数据
开启二级缓存
-
在mapper接口上面写@CacheNamespace注解,需要注意的是如果采用了注解方式,那么写SQL语句也需要使用注解,否则二级缓存不会生效
@CacheNamespace(
implementation = PerpetualCache.class, // 缓存实现 Cache接口 实现类
eviction = LruCache.class,// 缓存算法
flushInterval = 60000, // 刷新间隔时间 毫秒
size = 1024, // 最大缓存引用对象
readWrite = true, // 是否可写
blocking = false // 是否阻塞
)
-
在xxxMapper.xml文件中定义cache标签
二级缓存使用条件
- 当会话提交或者关闭时才会填充数据到二级缓存中
- 必须是在同一个命名空间下
- 必须是相同的statment,即同一个mapper接口的同一个方法
- 必须是相同的SQL语句和参数
- 如果readWrite=true ,实体类必须实现Serializable 接口
二级缓存清空条件
- 任何一种增删改操作 都会清空整个namespace 中的缓存
- 只有修改会话提交之后 才会执行清空操作
调用链
-
顶层api调用
PersonMapper mapper = session.getMapper(PersonMapper.class); Person person1 = mapper.selectPersonById(1);
-
不管是调用select方法还是update方法,都会进入DefaultSqlSession类中,并调用对应方法
/* DefaultSqlSession > selectList() getMappedStatement(statement) 方法,statement即为namespace + selectId。通过configuration获取mapperedStatement,MapperedStatement封装了id、sqlSource、resultMap、paramType等。 通过executor调用下一层的executor执行器执行 */ @Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
-
然后进入CacheExecutor中尝试获取二级缓存
/* CacheExecutor > query() 获取二级缓存的key值 */ @Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
/* CacheExecutor > query() 尝试获取二级缓存的值 */ @Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); // 获取二级缓存 if (cache != null) { //如果cache为空,说明未开启二级缓存 flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); //如果未从二级缓存获取到对应的值,就走一级缓存 if (list == null) { list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); //将值存入二级缓存中 tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } //如果没有获取二级缓存,就尝试去获取一级缓存 return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
- 如果二级缓存没有开启或者未从二级缓存中获取到值,就走一级缓存
/* BaseExecutor > query() */ ...... 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--; } ......从缓存中获取值
-
不管是二级缓存,还是一级缓存都是从perpetualCache类中的HashMap中进行取值和赋值等操作
/* PerpetualCache > getObject() */ @Override public Object getObject(Object key) { return cache.get(key); }