一 mybatis二级缓存
1.1 简介
Mybatis提供查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性能。
Mybatis的查询缓存总共有两级,我们称之为一级缓存和二级缓存,如图:

一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
二级缓存是Mapper(namespace)级别的缓存。多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。
1.2 设置步骤
1.2.1 设置开关
在工程application.properties设置开关
mybatis.configuration.cache-enabled=true
1.2.2 实现cache接口
package com.module.demo.dal.cache;import org.apache.ibatis.cache.Cache;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.data.redis.core.RedisCallback;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.ValueOperations;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock;/*** @className: RedisCache* @description: RedisCache 描述* @author: kangzeng.ckz* @date: 2021/12/14**/public class RedisCache implements Cache {private static final Logger logger = LoggerFactory.getLogger(RedisCache.class);private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();// cache instance idprivate final String id;private RedisTemplate redisTemplate;// redis过期时间,10分钟private static final long EXPIRE_TIME_IN_SECONDS = 600;public RedisCache(String id) {if (id == null) {throw new IllegalArgumentException("Cache instances require an ID");}System.out.println("id:"+id);this.id = id;}/*** @description: 方法定义* @param:* @return: java.lang.String* @author: kangzeng.ckz* @date: 2021/12/13*/@Overridepublic String getId() {System.out.println("id:"+id);return id;}/*** @description: 方法定义* @param: key* @param: value* @return: void* @author: kangzeng.ckz* @date: 2021/12/13*/@Override@SuppressWarnings("unchecked")public void putObject(Object key, Object value) {try {RedisTemplate redisTemplate = getRedisTemplate();ValueOperations opsForValue = redisTemplate.opsForValue();opsForValue.set(key, value, EXPIRE_TIME_IN_SECONDS, TimeUnit.SECONDS);logger.info("Put query result to redis");}catch (Throwable t) {logger.error("Redis put failed", t);}}/*** @description: 方法定义* @param: key* @return: java.lang.Object* @author: kangzeng.ckz* @date: 2021/12/13*/@Overridepublic Object getObject(Object key) {try {RedisTemplate redisTemplate = getRedisTemplate();ValueOperations opsForValue = redisTemplate.opsForValue();logger.info("Get cached query result from redis");return opsForValue.get(key);}catch (Throwable t) {logger.error("Redis get failed, fail over to db", t);return null;}}/*** @description: 方法定义* @param: key* @return: java.lang.Object* @author: kangzeng.ckz* @date: 2021/12/13*/@Override@SuppressWarnings("unchecked")public Object removeObject(Object key) {try {RedisTemplate redisTemplate = getRedisTemplate();redisTemplate.delete(key);logger.debug("Remove cached query result from redis");}catch (Throwable t) {logger.error("Redis remove failed", t);}return null;}/*** @description: 方法定义* @param:* @return: void* @author: kangzeng.ckz* @date: 2021/12/13*/@Overridepublic void clear() {RedisTemplate redisTemplate = getRedisTemplate();redisTemplate.execute((RedisCallback) connection -> {connection.flushDb();return null;});logger.debug("Clear all the cached query result from redis");}/*** This method is not used** @return*/@Overridepublic int getSize() {return 0;}/*** @description: 方法定义* @param:* @return: java.util.concurrent.locks.ReadWriteLock* @author: kangzeng.ckz* @date: 2021/12/13*/@Overridepublic ReadWriteLock getReadWriteLock() {return readWriteLock;}private RedisTemplate getRedisTemplate() {if (redisTemplate == null) {redisTemplate = ApplicationContextHolder.getBean("redisTemplate");}return redisTemplate;}}
package com.module.demo.dal.cache;import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;/*** @className: ApplicationContextHolder* @description: ApplicationContextHolder 描述* @author: kangzeng.ckz* @date: 2021/12/14**/@Componentpublic class ApplicationContextHolder implements ApplicationContextAware {private static ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext ctx) throws BeansException {applicationContext = ctx;}/*** Get application context from everywhere** @return*/public static ApplicationContext getApplicationContext() {return applicationContext;}/*** Get bean by class** @param clazz* @param <T>* @return*/public static <T> T getBean(Class<T> clazz) {return applicationContext.getBean(clazz);}/*** Get bean by class name** @param name* @param <T>* @return*/@SuppressWarnings("unchecked")public static <T> T getBean(String name) {return (T) applicationContext.getBean(name);}}
1.2.3 在mapper文件设置cache
1.2.3.1 cache标签
在MyBatis的 Mapper XML 中加入 cache 或者 cache-ref 标签。
- cache标签用于声明这个namespace需要使用二级缓存,并且可以自定义配置。
- type:cache使用的类型,默认是PerpetualCache,这在一级缓存中提到过。
- eviction: 定义回收的策略,常见的有FIFO,LRU。
- flushInterval: 配置一定时间自动刷新缓存,单位是毫秒。
- size: 最多缓存对象的个数。
- readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。
- blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
<!-- 添加二级缓存--><cache type="com.module.demo.dal.cache.RedisCache"/>
cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache。
<cache-ref namespace="mapper.StudentMapper"/>
1.2.3.2 设置flushCache
flushCache表示是否更新缓存,在insert,update,delete添加flushCache=”true”,每次数据变更则会自动更新cache(不一定要设置,根据业务需要灵活变动)。
<delete id="deleteByExample" flushCache="true" parameterType="com.module.demo.dal.model.EmployeeExample">
delete from employee employee
<if test="_parameter != null">
<include refid="Example_Where_Clause"/>
</if>
</delete>
1.3 Note
- 最好只在单表操作的表上(确切是一个mapper文件)使用缓存,当然你得看你具体的业务需求,灵活设置cache空间和flushCache时机。
- 不只是要保证这个表在整个系统中只有单表操作,而且和该表有关的全部操作必须全部在一个namespace下。缓存是以namespace为单位的,最好一个mapper使用一个namespace,不同namespace下的操作互不影响。
- 一般情况下(具体业务场景具体分析),保证查询远远大于insert,update,delete操作的情况下使用缓存。
使用
二 Spring缓存
- Cache:缓存抽象的规范接口,缓存实现有:RedisCache、EhCacheCache、ConcurrentMapCache等
CacheManager:缓存管理器,管理Cache的生命周期
2.1 JSR107核心接口
Java Caching(JSR-107)定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry和 Expiry。
- CachingProvider:创建、配置、获取、管理和控制多个CacheManager
- CacheManager:创建、配置、获取、管理和控制多个唯一命名的Cache,Cache存在于CacheManager的上下文中。一个CacheManager仅对应一个CachingProvider
- Cache:是由CacheManager管理的,CacheManager管理Cache的生命周期,Cache存在于CacheManager的上下文中,是一个类似map的数据结构,并临时存储以key为索引的值。一个Cache仅被一个CacheManager所拥有
- Entry:是一个存储在Cache中的key-value对
- Expiry:每一个存储在Cache中的条目都有一个定义的有效期。一旦超过这个时间,条目就自动过期,过期后,条目将不可以访问、更新和删除操作。缓存有效期可以通过ExpiryPolicy设置
2.2 缓存注解
Spring提供的重要缓存注解
- @Cacheable:针对方法配置,能够根据方法的请求参数对其结果进行缓存
- @CacheEvict:清空缓存
- @CachePut:既调用方法,又更新缓存数据
- @EnableCaching:开启基于注解的缓存
- @Caching:定义复杂的缓存规则
- @CacheConfig 一个类中可能会有多个缓存操作,而这些缓存操作可能是重复的。这个时候可以使用
举例@Cacheable使用,首先设置keyGenerator,keyGenerator是生成redis缓存的key。
@Configuration
public class CacheConfig {
@Bean(value = {"customKeyGenerator"})
public KeyGenerator keyGenerator(){
KeyGenerator keyGenerator = new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName() + "[" + Arrays.asList(params).toString() + "]";
}
};
return keyGenerator;
}
}
缓存使用,直接在方法(类)上添加注解,注解字段和值参考:https://www.cnblogs.com/coding-one/p/12401630.html
@Override
@Cacheable(value = {"emp"}, keyGenerator = "customKeyGenerator")
public ListEmployeeResponse listEmployee(ListEmployeeRequest request) throws BaseException {
ParamChecker.checkAllNotNull("request",request,request.getName());
ListEmployeeResponse listEmployeeResponse = employeeManager.listEmployeeByName(request.getName(), request.getPageNumberWithDefault(), request.getPageSizeWithDefault());
return listEmployeeResponse;
}
redis运行后redis上会生成缓存,如
5) "emp::listEmployee[[ListEmployeeRequest(name=kang)]]"
三 redistemplate
https://juejin.cn/post/6881541490649071624
