一级缓存(作用域SqlSession)
- MyBatis的一级查询缓存(也叫作本地缓存)是基于org.apache.ibatis.cache.impl.PerpetualCache 类的 HashMap本地缓存,其作用域是SqlSession
- 在同一个SqlSession中两次执行相同的 sql 查询语句,第一次执行完毕后,会将查询结果写入到缓存中,第二次会从缓存中直接获取数据,而不再到数据库中进行查询,这样就减少了数据库的访问,从而提高查询效率。
- 当一个 SqlSession 结束后,该 SqlSession 中的一级查询缓存也就不存在了。
- myBatis 默认一级查询缓存是开启状态,且不能关闭。
- 增删改会清空缓存,无论是否commit
- spring整合mybatis后,非事务环境下,每次操作数据库都使用新的sqlSession对象。因此mybatis的一级缓存无法使用(一级缓存针对同一个sqlsession有效)
- 在开启事物的情况之下,spring使用threadLocal获取当前资源绑定同一个sqlSession,因此此时一级缓存是有效的
二级缓存(作用域namespace)
- MyBatis的二级缓存是mapper范围级别的
- SqlSession关闭后才会将数据写到二级缓存区域
- 增删改操作,无论是否进行提交commit(),均会清空一级、二级缓存
- 二级缓存会使用 Least Recently Used (LRU,最近最少使用的)算法来收回。
- 根据时间表(如 no Flush Interval ,没有刷新间隔),缓存不会以任何时间顺序来刷新 。
- 缓存会存储集合或对象(无论查询方法返回什么类型的值)的 1024 个引用。
- 缓存会被视为 read/write (可读/可写)的,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改 。
- 设置方式在mapper文件中添加<cache/>
二级缓存该用么?
多表操作一定不要使用二级缓存,因为多表操作进行更新操作,一定会产生脏数据。而且二级缓存是表级缓存,开销大,没有一级缓存直接使用 HashMap 来存储的效率更高,所以二级缓存并不推荐使用。
二级缓存之Ehcache(本机)
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。
优点:
-
快速
-
简单
-
缓存数据有两级:内存和磁盘,因此无需担心容量问题
-
缓存数据会在虚拟机重启的过程中写入磁盘
-
可以通过RMI、可插入API等方式进行分布式缓存
-
具有缓存和缓存管理器的侦听接口
-
支持多缓存管理器实例,以及一个实例的多个缓存区域
-
提供Hibernate的缓存实现
-
多种缓存策略,Ehcache提供了对大数据的内存和硬盘的存储,最近版本允许多实例、保存对象高灵活性、提供LRU、LFU、FIFO淘汰算法,基础属性支持热配置、支持的插件多
<dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.6</version> </dependency> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.1.0</version> </dependency>
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
缺点
-
使用磁盘Cache的时候非常占用磁盘空间:这是因为DiskCache的算法简单,该算法简单也导致Cache的效率非常高。它只是对元素直接追加存储。因此搜索元素的时候非常的快。如果使用DiskCache的,在很频繁的应用中,很快磁盘会满。
-
不能保证数据的安全:当突然kill掉java的时候,可能会产生冲突,EhCache的解决方法是如果文件冲突了,则重建cache。这对于Cache数据需要保存的时候可能不利。当然,Cache只是简单的加速,而不能保证数据的安全。如果想保证数据的存储安全,可以使用Bekeley DB Java Edition版本。这是个嵌入式数据库。可以确保存储安全和空间的利用率。
二级缓存之Redis(分布式适用)
pom依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.7.0</version> </dependency>
redishelper
package com.zqh.cache; import com.zqh.config.ApplicationContextHolder; import org.apache.ibatis.cache.Cache; import org.springframework.data.redis.core.RedisTemplate; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * 1.使用@Component的方式,是spring的方式,会生成一个RedisCache的实例,纳入spring容器中, * 在这个容器中实例,是可以注入RedisTemplate。 * 2.因为在每个mapper中,配置了二级缓存,对应的mapper会重新生成实例,生成的这个实例没有按照spring的规则来生成。 * 所以这个类中加入了 @Resource @AutoWire都是无法获取。 * 3.那么如何获取spring容器中的对象了?就需要先拿到spring容器,然后从容器中去手动取。 */ public class RedisCache implements Cache { //要想使用Mybatis的二级缓存,必须要实现Cache接口 private RedisTemplate<Object, Object> redisTemplate; private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); private String id; // id的作用是将不同的mapper作一个区分 public RedisCache(String id) { this.id = id; } // 获取RedisTemplate private RedisTemplate<Object, Object> getRedisTemplate() { this.redisTemplate = ApplicationContextHolder.getRedisTemplate(); return redisTemplate; } @Override public void putObject(Object key, Object value) { getRedisTemplate().opsForValue().set(key, value); } @Override public Object getObject(Object key) { return getRedisTemplate().opsForValue().get(key); } @Override public Object removeObject(Object key) { return getRedisTemplate().delete(key); } @Override public int getSize() { return 1; } @Override public String getId() { return this.id; } // 不用实现 @Override public void clear() { } @Override public ReadWriteLock getReadWriteLock() { return this.reentrantReadWriteLock; } }
获取ApplicationContext
package com.zqh.config; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; /** * spring提供了很多的接口 XXXXAware, 这一类的接口比较的特殊, 那么spring容器在启动的时候 * 如果检测某个类实现了这一类接口,那么会去调用实现了该接口方法的实现。 */ @Component public class ApplicationContextHolder implements ApplicationContextAware { private static ApplicationContext applicationContext; // 该放方法会自动将spring容器的类 ApplicationContext, 传入给该方法。 @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ApplicationContextHolder.applicationContext = applicationContext; } // 从容器中获取RedisTemplate public static RedisTemplate getRedisTemplate() { return applicationContext.getBean("redisTemplate", RedisTemplate.class); } }
mapper配置
<!-- 在这里配置缓存,那么每个Mapper都会去生成 RedisCache的实现类 --> <!-- flushInterval: 清空缓存的时间间隔,单位为毫秒; 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用更新语句时刷新。 size: 可以被设置为任意正整数, 缓存的数量,默认是1024; evication: LRU 移除最长时间不被使用的对象。 blocking: 默认是false; --> <cache size="1024" type="com.qf.cache.RedisCache"></cache>
Mybatis的二级缓存作用相对有限,特别是在分布式场景及复杂业务场景。建议不开启,直接用在逻辑层自己控制redis。