目录
2.2mybatis-config.xml 仍然打开二级缓存
2.4在 XxxMapper.xml 中启用 EhCache , 当然原来 MyBatis 自带的缓存配置就注销了
缓存
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
基本上就是这样。这个简单语句的效果如下:
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
这些属性可以通过 cache 元素的属性来修改。比如:
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
可用的清除策略有:
LRU– 最近最少使用:移除最长时间不被使用的对象。FIFO– 先进先出:按对象进入缓存的顺序来移除它们。SOFT– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。默认的清除策略是 LRU。
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
提示 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。

图的解析:
客户端/浏览器发出一个请求的时候,控制器会先去Executor中去查询Local Cache去查有没有,如果有就会直接返回给客户端/浏览器,没有就会去数据库中去拿,同时也会放到 Local Cache中,下一次在查询这个数据的时候就会直接从Local Cache中获取。
- //测试一级缓存,失效
- //关闭sqlSession会话后 , 一级缓存失效
- @Test
- public void level1CacheTest2() {
-
- //查询id=3的monster
- Monster monster = monsterMapper.getMonsterById(3);
- System.out.println("monster=" + monster);
-
- //关闭sqlSession, 一级缓存失效
- if (sqlSession != null) {
- sqlSession.close();
- }
-
- //因为关闭了sqlSession,所以需要重新初始化sqlSession和 monsterMapper
- sqlSession = MyBatisUtils.getSqlSession();
- monsterMapper = sqlSession.getMapper(MonsterMapper.class);
- //再次查询id=3的monster
- System.out.println("--如果你关闭了sqlSession,当你再次查询相同的id时, 仍然会发出sql----");
- Monster monster2 = monsterMapper.getMonsterById(3);
- System.out.println("monster2=" + monster2);
-
- if (sqlSession != null) {
- sqlSession.close();
- }
- }
- //测试一级缓存,失效
- //如果执行sqlSession.clearCache() , 会导致一级缓存失效
- @Test
- public void level1CacheTest3() {
-
- //查询id=3的monster
- Monster monster = monsterMapper.getMonsterById(3);
- System.out.println("monster=" + monster);
-
-
- //执行clearCache
- /**
- * @Override
- * public void clearCache() {
- * executor.clearLocalCache();
- * }
- */
- sqlSession.clearCache();
-
- //再次查询id=3的monster
- System.out.println("--如果你执行sqlSession.clearCache(),当你再次查询相同的id时, 仍然会发出sql----");
- Monster monster2 = monsterMapper.getMonsterById(3);
- System.out.println("monster2=" + monster2);
-
-
- if (sqlSession != null) {
- sqlSession.close();
- }
- }
- //测试一级缓存,失效
- //如果修改了同一个对象 , 会导致一级缓存[对象数据]失效
- @Test
- public void level1CacheTest4() {
-
- //查询id=3的monster
- Monster monster = monsterMapper.getMonsterById(3);
- System.out.println("monster=" + monster);
-
-
- //如果修改了同一个对象 , 会导致一级缓存[对象数据]失效
- monster.setName("蚂蚱精");
- monsterMapper.updateMonster(monster);
-
- //再次查询id=3的monster
- System.out.println("--如果你修改了同一个对象,当你再次查询相同的id时, 仍然会发出sql----");
- Monster monster2 = monsterMapper.getMonsterById(3);
- System.out.println("monster2=" + monster2);
-
-
- if (sqlSession != null) {
- sqlSession.commit();//这里需要commit
- sqlSession.close();
- }
- }

客户端发出了一次请求或者说一次会话,这个时候假如你配置了二级缓存,会在CachingExecytor中会先有一个获取二级缓存的动作,在这个二级缓存中看有没有数据(有自带二级缓存和第三方缓存库),查到了直接返回,没有查到就会继续往下走,到我们的一级缓存中去查找,在一次里面找到了也可以直接返回给客户端,没有找到继续去数据库查找。
1. mybatis-config.xml 配置中开启二级缓存
- <configuration>
-
- <properties resource="jdbc.properties"/>
- <settings>
-
- <setting name="cacheEnabled" value="true"/>
- settings>
2.使用二级缓存时 entity 类实现序列化接口 (serializable),因为二级缓存可能使用到序

3. 在对应的 XxxMapper.xml 中设置二级缓存的策略
- <mapper namespace="com.hong.mapper.MonsterMapper">
-
-
- <cache eviction="FIFO" flushInterval="60000"
- size="512" readOnly="true"/>
4.测试
- //测试二级缓存的使用
- @Test
- public void level2CacheTest() {
-
- //查询id=3的monster
- Monster monster = monsterMapper.getMonsterById(3);
- System.out.println("monster=" + monster);
-
-
- //这里老师关闭sqlSession
- if (sqlSession != null) {
- sqlSession.close();
- }
-
- //重新获取sqlSession
- sqlSession = MyBatisUtils.getSqlSession();
- //重新获取了monsterMapper
- monsterMapper = sqlSession.getMapper(MonsterMapper.class);
- //再次查询id=3的monster
- System.out.println("--虽然前面关闭了sqlSession,因为配置二级缓存, " +
- "当你再次查询相同的id时, 依然不会再发出sql, 而是从二级缓存获取数据----");
- Monster monster2 = monsterMapper.getMonsterById(3);
- System.out.println("monster2=" + monster2);
-
- Monster monster3 = monsterMapper.getMonsterById(3);
- System.out.println("monster3=" + monster3);
-
- if (sqlSession != null) {
- sqlSession.close();
- }
- }
- <settings>
- <setting name="logImpl" value="STDOUT_LOGGING"/>
-
- <setting name="cacheEnabled" value="false"/>
- settings>
3.2MonsterMapper.xml
3.3或者更加细粒度的, 在配置方法上指定

注意:一般我们不需要去修改,使用默认的即可
4. mybatis 刷新二级缓存的设置
- <update id="updateMonster" parameterType="Monster" flushCache="true">
- UPDATE mybatis_monster SET NAME=#{name},age=#{age} WHERE id=#{id}
- update>
一句话:缓存执行顺序是:二级缓存-->一级缓存-->数据库
当我们关闭一级缓存的时候,如果你配置二级缓存,那么一级缓存的数据,会放入到二级缓存
2.运行效果 , 可以看到,在一级缓存存在的情况下,依然是先查询二级缓存,但是因为 二级缓存,没有数据, 所以命中率都是 0.0 ,

可以通过一个接口找到第三方缓存库。
- <dependencies>
-
- <dependency>
- <groupId>net.sf.ehcachegroupId>
- <artifactId>ehcache-coreartifactId>
- <version>2.6.11version>
- dependency>
-
- <dependency>
- <groupId>org.slf4jgroupId>
- <artifactId>slf4j-apiartifactId>
- <version>1.7.25version>
- dependency>
-
- <dependency>
- <groupId>org.mybatis.cachesgroupId>
- <artifactId>mybatis-ehcacheartifactId>
- <version>1.2.1version>
- dependency>
- dependencies>
- <settings>
-
- <setting name="cacheEnabled" value="true"/>
- settings>
- "1.0" encoding="UTF-8"?>
- <ehcache>
-
- <diskStore path="java.io.tmpdir/Tmp_EhCache"/>
-
-
- <defaultCache
- eternal="false"
- maxElementsInMemory="10000"
- overflowToDisk="false"
- diskPersistent="false"
- timeToIdleSeconds="1800"
- timeToLiveSeconds="259200"
- memoryStoreEvictionPolicy="LRU"/>
-
- ehcache>
- <mapper namespace="com.hong.mapper.MonsterMapper">
-
-
-
-
- <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
- //测试ehCache级缓存
- @Test
- public void ehCacheTest() {
-
- //查询id=3的monster
- Monster monster = monsterMapper.getMonsterById(3);
- //会发出SQL, 到db查询
- System.out.println("monster=" + monster);
-
- //这里老师关闭sqlSession, 一级缓存[数据]失效.=> 将数据放入到二级缓存 (ehcache)
- if (sqlSession != null) {
- sqlSession.close();
- }
-
- //重新获取sqlSession
- sqlSession = MyBatisUtils.getSqlSession();
- //重新获取了monsterMapper
- monsterMapper = sqlSession.getMapper(MonsterMapper.class);
- //再次查询id=3的monster
- System.out.println("--虽然前面关闭了sqlSession,因为配置二级缓存(ehcache), " +
- "当你再次查询相同的id时, 不会再发出sql, 而是从二级缓存(ehcache)获取数据----");
- Monster monster2 = monsterMapper.getMonsterById(3);
- System.out.println("monster2=" + monster2);
-
- //再次查询id=3的monster, 仍然到二级缓存(ehcache), 获取数据, 不会发出sql
- Monster monster3 = monsterMapper.getMonsterById(3);
- System.out.println("monster3=" + monster3);
-
- if (sqlSession != null) {
- sqlSession.close();
- }

4. 当我们使用了 Ehcahce 后,就是 EhcacheCache 类实现 Cache 接口的,是核心类.

5. 我们看一下源码,发现缓存的本质就是 Map
