mybatis源码解析( 二级缓存)

一、二级缓存介绍

1、一级缓存

  Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存,一级缓存只是相对于同一个SqlSession而言,属于会话级缓存,使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。会将对象缓存起来,如果修改对象,缓存中对象也会被修改。

一级缓存的使用条件:

  1. 必须是相同的SQL和参数
  2. 必须是相同的会话
  3. 必须是相同的namespace 即同一个mapper
  4. 必须是相同的statement 即同一个mapper 接口中的同一个方法
  5. 查询语句中间没有执行session.clearCache() 方法
  6. 查询语句中间没有执行 insert update delete 方法(无论变动记录是否与 缓存数据有无关系)

2、二级缓存

二级缓存默认是不开启的,需要手动开启二级缓存,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。缓存中存储的是序列化之后的,所以不同的会话操作对象不会改变缓存

二级缓存使用条件:

  1. 当会话提交或关闭之后才会填充二级缓存
  2. 必须是在同一个命名空间之下
  3. 必须是相同的statement 即同一个mapper 接口中的同一个方法
  4. 必须是相同的SQL语句和参数
  5. 如果readWrite=true ,实体对像必须实现Serializable 接
  6. 任何一种增删改操作 都会清空整个namespace 中的缓存
  7. 多表操作不要使用二级缓存,因为多表操作进行更新操作,一定会产生脏数据。

二级缓存开启

<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); }