ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

mybatis缓存

2022-07-07 20:01:30  阅读:130  来源: 互联网

标签:缓存 sqlSession delegate executor mybatis new public


1.一级缓存

mybatis的一级缓存存在于BaseExecutor(localCache)

BatchExecutor,ReuseExecutor,SimpleExecutor三个处理器都继承了BaseExecutor,会调用父类的构造方法

查看代码
#BaseExecutor
protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue<>();
    // PerpetualCache实现缓存本质上是依赖一个HashMap来存储数据的
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }


#DefaultSqlSessionFactory
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // (若与spring事务关联 则会创建一个由spring管理的事务连接 SpringManagedTransaction)
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  

#Configuration
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

一级缓存会关联SqlSession,每次创建一个SqlSession,都会关联一个新的excutor,调用BaseExecutor的构造方法实例化一个新的本地缓存。

简单来说,一级缓存作用于一个SqlSession,因此在不同线程间一级缓存是无法生效的,同一个线程有不同的SqlSession一级缓存也是无法公用的。

 <select id="getStudentById" resultMap="StudentBaseMap">
    select s.id ,s.name ,
    s.age , s.is_adult, s.test_date,
    g.user_id, g.chinese, g.math, g.english
    from student s
    left join grade g on s.id = g.user_id
   where s.id = #{id}
 </select>
 
 // 注入
@Resource
private SqlSessionFactory sqlSessionFactory;

public void testCache1() {
    SqlSession sqlSession = null;
    try {
      // 开启一个sqlSession
      sqlSession = sqlSessionFactory.openSession();
      StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
      Student student1 = studentMapper.getStudentById(1);
      Student student2 = studentMapper.getStudentById(1);
      log.info("student1 == student2, result:" + (student1 == student2));

      log.info("开启一个新的SqlSession 此时之前的一级缓存应该已经失效");
      sqlSession = sqlSessionFactory.openSession();
      studentMapper = sqlSession.getMapper(StudentMapper.class);
      Student student3 = studentMapper.getStudentById(1);
      Student student4 = studentMapper.getStudentById(1);
      log.info("student1 == student3, result:" + (student1 == student3));
      log.info("student2 == student3, result:" + (student2 == student3));
      log.info("student3 == student4, result:" + (student3 == student4));
    } finally {
      sqlSession.close();
    }
  }

结果输出,只查询了两次

缓存清除配置 

另外BaseExecutor的query()方法中有这样一段清除本地一级缓存的代码
主要是根据ms中的flushCacheRequired属性是否为true,若为true则每次查询之前都会清除一级缓存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
 }
 

  <select id="getStudentById" resultMap="StudentBaseMap" flushCache="true">
    select s.id ,s.name ,
    s.age , s.is_adult, s.test_date,
    g.user_id, g.chinese, g.math, g.english
    from student s
    left join grade g on s.id = g.user_id
   where s.id = #{id}
  </select>

加上flushCache="true"后,再次运行结果如下

通过上述结果可以看出总共查询了四次,每个对象都不相等,说明清除一级缓存的配置生效

 

2.二级缓存

mybatis的二级缓存默认开启,但真正使用需要在mapper文件中添加相应的缓存配置

二级缓存存在于SqlSessionFactory生命周期中, 每个二级缓存对同一个mapper文件中的SELECT操作有效

#Configuration
protected boolean cacheEnabled = true;

// 执行器默认会传入到CachingExecutor进行一层包装
if (ExecutorType.BATCH == executorType) {
    executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
    executor = new ReuseExecutor(this, transaction);
} else {
    executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
    executor = new CachingExecutor(executor);
}


#CacheExecutor
// delegate委托对象 为上述传入的一种具体的执行器
// cacheExecutor的具体查询更新操作都是通过委托对象来进行操作的
private final Executor delegate; 
// 缓存管理器
private final TransactionalCacheManager tcm = new TransactionalCacheManager();

public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
}


// 具体分析下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 != null && ms.isFlushCacheRequired() 刷新二级缓存
        // 若select标签中没有配置 flushCache="true"则不会刷新
        flushCacheIfRequired(ms);
        // 是否将查询结果进行二级缓存 select标签默认是true 也可用useCache="false"配置不进行缓存
        if (ms.isUseCache() && resultHandler == null) {
            // 存储过程相关校验
            ensureNoOutParams(ms, boundSql);
            @SuppressWarnings("unchecked")
            // 根据cacheKey从缓存中取数据
            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;
        }
    }
    // 若没有配置二级缓存 实际上就直接执行BaseExecutor中的query方法
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// 上述tcm.putObject(cache, key, list); 
实际上调用了TransactionalCache的putObject方法,将结果放进entriesToAddOnCommit这个map中,并没有缓存到delegate中 
只有调用sqlSession的commit或close方法 才会将结果存进缓存(通过flushPendingEntries方法)
public class TransactionalCache implements Cache {

  private static final Log log = LogFactory.getLog(TransactionalCache.class);

  // 委托对象 真实的缓存 最后结果将存进这个缓存
  private final Cache delegate;
  // 若为true,则commit的时候会将delegate清空
  private boolean clearOnCommit;
  // 存储commit时需要放进缓存的对象
  private final Map<Object, Object> entriesToAddOnCommit;
  // 存储调用getObject()获取不到的key
  private final Set<Object> entriesMissedInCache;

  public TransactionalCache(Cache delegate) {
    this.delegate = delegate;
    this.clearOnCommit = false;
    this.entriesToAddOnCommit = new HashMap<>();
    this.entriesMissedInCache = new HashSet<>();
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public Object getObject(Object key) {
    // issue #116
    Object object = delegate.getObject(key);
    if (object == null) {
      entriesMissedInCache.add(key);
    }
    // issue #146
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }

  @Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }

  @Override
  public Object removeObject(Object key) {
    return null;
  }

  @Override
  public void clear() {
    clearOnCommit = true;
    entriesToAddOnCommit.clear();
  }

  public void commit() {
    if (clearOnCommit) {
      delegate.clear();
    }
    flushPendingEntries();
    reset();
  }

  public void rollback() {
    unlockMissedEntries();
    reset();
  }

  private void reset() {
    clearOnCommit = false;
    entriesToAddOnCommit.clear();
    entriesMissedInCache.clear();
  }

  private void flushPendingEntries() {
    // 遍历entriesToAddOnCommit,将结果缓存
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }

  private void unlockMissedEntries() {
    for (Object entry : entriesMissedInCache) {
      try {
        delegate.removeObject(entry);
      } catch (Exception e) {
        log.warn("Unexpected exception while notifiying a rollback to the cache adapter. "
            + "Consider upgrading your cache adapter to the latest version. Cause: " + e);
      }
    }
  }

}

测试代码

  public void testCache2() {
    SqlSession sqlSession = null;
    try {
      // 开启一个sqlSession
      sqlSession = sqlSessionFactory.openSession();
      StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
      Student student1 = studentMapper.getStudentById(1);
      sqlSession.close();
      log.info("开启新的session");
      sqlSession = sqlSessionFactory.openSession();
      studentMapper = sqlSession.getMapper(StudentMapper.class);
      Student student2 = studentMapper.getStudentById(1);
      Student student3 = studentMapper.getStudentById(1);
      Student student4 = studentMapper.getStudentById(1);
      log.info("student1 == student2, result:" + (student1 == student2));
      log.info("student1 == student3, result:" + (student1 == student3));
      log.info("student1 == student4, result:" + (student1 == student4));
    } finally {
      sqlSession.close();
    }
  }

结果输出

可以看到后三次的查询都命中了缓存,没有再去查找数据库。另外,查询出来的结果都不是同一个对象,事实上缓存的时候SerializedCache这个缓存 ,所有二级缓存的对象需要实现Serializable接口,后面存储的其实是对象的字节数组,这样反序列化出来的时候就不是同一个对象了。

若某个查询不想使用二级缓存,也可以加上flushCache="true"这个配置,同一级缓存一样

#SerializedCache

@Override
public void putObject(Object key, Object object) {
    if (object == null || object instanceof Serializable) {
      delegate.putObject(key, serialize((Serializable) object));
    } else {
      throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);
    }
}

private byte[] serialize(Serializable value) {
    try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos)) {
      oos.writeObject(value);
      oos.flush();
      return bos.toByteArray();
    } catch (Exception e) {
      throw new CacheException("Error serializing object.  Cause: " + e, e);
    }
  }

 

最后

 

无论是一级缓存还是二级缓存再调用update()方法后都会clear清空缓存,使缓存失效。实际开发使用中mybatis缓存的坑还是很多的,需要谨慎小心。

 

标签:缓存,sqlSession,delegate,executor,mybatis,new,public
来源: https://www.cnblogs.com/monianxd/p/16455915.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有