关于java:mybatis体系结构终于最后一篇了

3次阅读

共计 7582 个字符,预计需要花费 19 分钟才能阅读完成。

二级缓存

次要内容:

二级缓存构建在一级缓存之上,在收到查问申请时,MyBatis 首先会查问二级缓存。若二级缓存未命中,再去查问一级缓存。与一级缓存不同,二级缓存和具体的命名空间绑定,一级缓存则是和 SqlSession 绑定。

在依照 MyBatis 标准应用 SqlSession 的状况下,一级缓存不存在并发问题。二级缓存则不然,二级缓存可在多个命名空间间共享。这种状况下,会存在并发问题,因而须要针对性去解决。除了并发问题,二级缓存还存在事务问题。

二级缓存如何开启?

配置项

`<configuration>
  <settings>
    <setting name=”cacheEnabled” value=”true|false” />
  </settings>
</configuration>
`

cacheEnabled=true 示意二级缓存可用,然而要开启话,须要在 Mapper.xml 内配置。

`<cache eviction=”FIFO” flushInterval=”60000″ size=”512″ readOnly=”true”/>
或者 简略形式
<cache/>
`

对配置项属性阐明:

  • flushInterval=”60000″,距离 60 秒清空缓存,这个距离 60 秒,是被动触发的,而不是定时器轮询的。
  • size=512,示意队列最大 512 个长度,大于则移除队列最后面的元素,这里的长度指的是 CacheKey 的个数,默认为 1024。
  • readOnly=”true”,示意任何获取对象的操作,都将返回同一实例对象。如果 readOnly=”false”,则每次返回该对象的拷贝对象,简略说就是序列化复制一份返回。
  • eviction:缓存会应用默认的 Least Recently Used(LRU,最近起码应用的)算法来发出。FIFO:First In First Out 先进先出队列。

在 Configuration 类的 newExecutor 办法中是否开启二级缓存

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

二级缓存通过 CachingExecutor 来实现的,原理是缓存里存在,就返回,不存在就调用 Executor,如果一级缓存未敞开,则先查一级缓存,不存在,再到数据库中查问。

上面应用一张图来示意:

上面是源码:

`@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 取得 BoundSql 对象
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 创立 CacheKey 对象
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    // 查问
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
            throws SQLException {
    // 调用 MappedStatement#getCache() 办法,取得 Cache 对象,
    // 即以后 MappedStatement 对象的二级缓存。
    Cache cache = ms.getCache();
    if (cache != null) {// <2>
        // 如果须要清空缓存,则进行清空
        flushCacheIfRequired(ms);
        // 当 MappedStatement#isUseCache() 办法,返回 true 时,才应用二级缓存。默认开启。
        // 可通过 @Options(useCache = false) 或 <select useCache=”false”> 办法,敞开。
        if (ms.isUseCache() && resultHandler == null) {// <2.2>
            // 临时疏忽,存储过程相干
            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);
}
`

二级缓存 key 是如何生成的?

也是应用的是 BaseExecutor 类中的 createCacheKey 办法生成的,所以二级缓存 key 和一级缓存生成规定是一样的。

二级缓存范畴

二级缓存有一个十分重要的空间划分策略:

`namespace=”com.tian.mybatis.mappers.UserMapper”
namespace=”com.tian.mybatis.mappers.RoleMapper”
`

即,依照 namespace 划分,同一个 namespace,同一个 Cache 空间,不同的 namespace,不同的 Cache 空间。

比方:

在这个 namespace 下的二级缓存是同一个。

二级缓存什么时候会被清空?

每当执行 insert、update、delete,flushCache=true 时,二级缓存都会被清空。

事务不提交,二级缓存不失效?

`SqlSession sqlSession = sqlSessionFactory.openSession();
System.out.println(“ 第一次查问 ”); 
User user = sqlSession.selectOne(“com.tian.mybatis.mapper.UserMapper.selectById”, 1);
System.out.println(user);
    
//sqlSession.commit();
                
SqlSession  sqlSession1 = sqlSessionFactory.openSession();
System.out.println(“ 第二次查问 ”);
User  user2 = sqlSession1.selectOne(“com.tian.mybatis.mapper.UserMapper.selectById”, 1);
System.out.println(user2);
`

因为二级缓存应用的是 TransactionalCaheManager(tcm) 来治理的,最初又调用了 TranscatinalCache 的 getObject()、putObject()、commit 办法。

TransactionalCache 外面又持有真正的 Cache 对象,比方:通过层层装璜的 PrepetualCache。

在 putObject 的时候,只是增加到 entriesToAddOnCommit 外面。

`//TransactionalCache 类中
@Override
public void putObject(Object key, Object object) {
    // 暂存 KV 到 entriesToAddOnCommit 中
    entriesToAddOnCommit.put(key, object);
}
`

只有 conmit 办法被调用的时候,才会调用 flushPendingEntries 办法,真正写入到缓存里。DefaultSqlSession 调用 commit 办法的时候就会调到这个 commit 办法。

`//TransactionalCache 类中
public void commit() {
    // 如果 clearOnCommit 为 true,则清空 delegate 缓存
    if (clearOnCommit) {
      delegate.clear();
    }
    // 将 entriesToAddOnCommit、entriesMissedInCache 刷入 delegate 中
    flushPendingEntries();
    // 重置
    reset();
  }
private void flushPendingEntries() {
    // 将 entriesToAddOnCommit 刷入 delegate 中
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    // 将 entriesMissedInCache 刷入 delegate 中
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
}
private void reset() {
    // 重置 clearOnCommit 为 false
    clearOnCommit = false;
    // 清空 entriesToAddOnCommit、entriesMissedInCache
    entriesToAddOnCommit.clear();
    entriesMissedInCache.clear();
}
`

为什么增删该操作会清空二级缓存呢?

因为在 CachingExecutor 的 update 办法中

`@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
  flushCacheIfRequired(ms);
  return delegate.update(ms, parameterObject);
}
private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    // 是否须要清空缓存
    // 通过 @Options(flushCache = Options.FlushCachePolicy.TRUE) 或 <select flushCache=”true”> 形式,
    // 开启须要清空缓存。
    if (cache != null && ms.isFlushCacheRequired()) {
        // 调用 TransactionalCache#clear() 办法,清空缓存。
        // 留神,此时清空的仅仅,以后事务中查问数据产生的缓存。
        // 而真正的清空,在事务的提交时。这是为什么呢?
        // 还是因为二级缓存是跨 Session 共享缓存,在事务尚未完结时,不能对二级缓存做任何批改。
        tcm.clear(cache);
    }
}
`

如何实现多个 namespace 的缓存共享?

对于多个 namespace 的缓存共享的问题,能够应用来解决。

比方:

`<cache-ref namespace=”com.tian.mybatis.mapper.RoleMapper”
`

cache-ref 代表援用别名的命名空间的 Cache 配置,两个命名空间的操作应用的是同一个 Cache。在关联的表比拟少或者依照业务能够对表进行分组的时候能够应用。

「留神」:在这种状况下,多个 mapper 的操作都会引起缓存刷新,所以这里的缓存的意义曾经不是很大了。

如果将第三方缓存作为二级缓存?

Mybatis 除了自带的二级换以外,咱们还能够通过是想 Cache 接口来自定义二级缓存。

增加依赖

`<dependency>
         <groupId>org.mybatis.caches</groupId>
         <artifactId>mybatis-redis</artifactId>
         <version>1.0.0-beta2</version>
    </dependency>`

redis 根底配置项

`host=127.0.0.1

port=6379
connectionTimeOut=5000
soTimeout=5000
datebase=0`

在咱们的 UserMapper.xml 中增加

`<cache type=”org.mybatis.caches.redis.RedisCache”
       eviction=”FIFO” flushInterval=”60000″ size=”512″ readOnly=”true”/>
`

RedisCache 类图,Cache 就是 Mybatis 中缓存的顶层接口。

二级缓存利用场景

对于拜访多的查问申请且用户对查问后果实时性要求不高,此时可采纳 mybatis 二级缓存技术升高数据库访问量,进步访问速度,业务场景比方:耗时较高的统计分析 sql、电话账单查问 sql 等。

缓存查问程序

先查二级缓存,不存在则保持一级缓存是否敞开,没敞开,则再查一级缓存,还不存在,最初查询数据库。

二级缓存总结

二级缓存开启形式有两步:

第一步:在全局配置中增加配置

`<settings>
    <setting name=”cacheEnabled” value=”true”/>
</settings>
`

第二步,在 Mapper 中增加配置

`<cache type=”org.mybatis.caches.redis.RedisCache”
           eviction=”FIFO” flushInterval=”60000″ size=”512″ readOnly=”true”/>
`

二级换是默认开启的,然而针对每一个 Mapper 的二级缓存是须要手动开启的。

二级缓存的 key 和一级缓存的 key 是一样的。

每当执行 insert、update、delete,flushCache=true 时,二级缓存都会被清空。

咱们能够继承第三方缓存来作为 Mybatis 的二级缓存。

总结

本文先从整体剖析了 Mybatis 的缓存体系结构,而后就是对每个缓存实现类的源码进行剖析,有具体的讲述一级缓存和二级缓存,如何开启敞开,缓存的范畴的阐明,缓存 key 是如何生成的,对应缓存是什么时候会被清空,先走二级缓存在走以及缓存,二级缓存应用第三方缓存。

参考:http://www.tianxiaobo.com/201…

举荐浏览

把握 Mybatis 动静映射,我可是下了功夫的

《写给大忙人看的 JAVA 核心技术》.pdf

正文完
 0