mybatis源码解析( 二级缓存)
一、二级缓存介绍
1、一级缓存
Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存,一级缓存只是相对于同一个SqlSession而言,属于会话级缓存,使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。会将对象缓存起来,如果修改对象,缓存中对象也会被修改。
一级缓存的使用条件:
- 必须是相同的SQL和参数
- 必须是相同的会话
- 必须是相同的namespace 即同一个mapper
- 必须是相同的statement 即同一个mapper 接口中的同一个方法
- 查询语句中间没有执行session.clearCache() 方法
- 查询语句中间没有执行 insert update delete 方法(无论变动记录是否与 缓存数据有无关系)
2、二级缓存
二级缓存默认是不开启的,需要手动开启二级缓存,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。缓存中存储的是序列化之后的,所以不同的会话操作对象不会改变缓存
二级缓存使用条件:
- 当会话提交或关闭之后才会填充二级缓存
- 必须是在同一个命名空间之下
- 必须是相同的statement 即同一个mapper 接口中的同一个方法
- 必须是相同的SQL语句和参数
- 如果readWrite=true ,实体对像必须实现Serializable 接
- 任何一种增删改操作 都会清空整个namespace 中的缓存
- 多表操作不要使用二级缓存,因为多表操作进行更新操作,一定会产生脏数据。
二级缓存开启
<settings> <setting name = "cacheEnabled" value = "true" /> </settings>
还需要在 Mapper 的xml 配置文件中加入 <cache>
标签
cache 标签有多个属性,一起来看一些这些属性分别代表什么意义
-
eviction
: 缓存回收策略,有这几种回收策略- LRU - 最近最少回收,移除最长时间不被使用的对象
- FIFO - 先进先出,按照缓存进入的顺序来移除它们
- SOFT - 软引用,移除基于垃圾回收器状态和软引用规则的对象
- WEAK - 弱引用,更积极的移除基于垃圾收集器和弱引用规则的对象
-
flushinterval
缓存刷新间隔,缓存多长时间刷新一次,默认不清空,设置一个毫秒值 -
readOnly
: 是否只读;true 只读,MyBatis 认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。MyBatis 为了加快获取数据,直接就会将数据在缓存中的引用交给用户。不安全,速度快。读写(默认):MyBatis 觉得数据可能会被修改 -
size
: 缓存存放多少个元素 -
type
: 指定自定义缓存的全类名(实现Cache 接口即可) -
blocking
: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
二、源码解析
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) {
//insert方法 case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; }
//更新 case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; }
//删除 case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; }
//查询 case SELECT:
//不同返回类型的处理 if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; }
public <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List<T> list = this.<T>selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; } } public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); } public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try {
//获取MappedStatement 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(); } }
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取sql BoundSql boundSql = ms.getBoundSql(parameterObject);
//获取一级缓存二级缓存的key 例:-609361858:2327171568:com.pjf.mybatis.dao.UserMapper.selectById:0:2147483647:select * from user where id=?:1:SqlSessionFactoryBean CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//获取mybatis的二级缓存配置<cache> Cache cache = ms.getCache();
//如果配置了二级缓存 if (cache != null) {
//是否要刷新缓存,是否手动设置了需要清空缓存 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.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//放入缓存,但是这里其他会话还拿不到,只是当前会话能查到 tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.<E> query(ms, parameterObject, 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 (closed) { throw new ExecutorException("Executor was closed."); } if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } 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--; } 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; }
再来看下更新操作,什么时候清空缓存的
public int update(String statement, Object parameter) { try { dirty = true; MappedStatement ms = configuration.getMappedStatement(statement); return executor.update(ms, wrapCollection(parameter)); } catch (Exception e) { throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
//清空二级缓存 flushCacheIfRequired(ms); return delegate.update(ms, parameterObject); }
public int update(MappedStatement ms, Object parameter) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); }
//在执行更新之前,清空一级缓存 clearLocalCache(); return doUpdate(ms, parameter); }