一、集成方法
1、pom添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring cache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2、配置redis连接
配置文件加上:
#redis
spring.data.redis.host=127.0.0.1
spring.data.redis.port=6379
spring.data.redis.password=
spring.data.redis.lettuce.pool.max-active=8
spring.data.redis.lettuce.pool.max-wait=-1
spring.data.redis.lettuce.pool.max-idle=8
spring.data.redis.lettuce.pool.min-idle=0
spring.data.redis.lettuce.pool.enabled=true
spring.data.redis.lettuce.pool.time-between-eviction-runs=30s
3、配置cache
(1)配置文件指定cache类型
#cache
#类型指定redis
spring.cache.type=redis
#一个小时,以毫秒为单位
spring.cache.redis.time-to-live=3600000
#给缓存的建都起一个前缀。 如果指定了前缀就用我们指定的,如果没有就默认使用缓存的名字作为前缀,一般不指定
#spring.cache.redis.key-prefix=CACHE_
#指定是否使用前缀
spring.cache.redis.use-key-prefix=true
#是否缓存空值,防止缓存穿透
spring.cache.redis.cache-null-values=true
(2)配置开启cache
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
@EnableConfigurationProperties(RedisProperties.class)//开启属性绑定配置的功能
public class MyCacheConfig {
}
4、使用
一般在mapper层使用。
类头加@CacheConfig注解,指明cacheNames;
方法上加@Cacheable、@CacheEvict注解直接使用。
4.1、查询
(1)无参
可以使用方法名作为key
@Cacheable(key = "#root.methodName")
public List<ParamDTO> listParams() {
LambdaQueryWrapper<ParamDTO> queryWrapper = new LambdaQueryWrapper<>();
return paramMapper.selectList(queryWrapper);
}
(2) 单参(非数组)查询
@Cacheable(key = "{#p0}")
public List<ParamDTO> listParamsByKey(String key) {
LambdaQueryWrapper<ParamDTO> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ParamDTO::getParamKey,key);
return paramMapper.selectList(queryWrapper);
}
@CacheEvict(key = "{#p0}")
public void deleteByKey(String key) {
LambdaQueryWrapper<ParamDTO> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ParamDTO::getParamKey,key);
paramMapper.delete(queryWrapper);
}
(3)不建议使用缓存的场景:
范围查询请勿添加缓存,如入参含有数组,Set,List
// Do not add cache
public List<UserPresence> getByUserIds(List<String> userIds) {
return userStatusMapper.selectBatchIds(userIds);
}
关联查询请勿添加缓存,即当前的返回结果由多个表联合查询得出;
分页查询请勿添加缓存;
基于动态组合的条件查询请勿添加缓存,如入参是Map,其value的值存在性是可变的,常见于前端多选条件查询。
4.2、增删改
假设我之前查询已经缓存了UserDTO的两个key,一个key是id,一个key是accountId+Id。关于这两个key的增删改需要删除缓存。
(1)参数为单个对象(可以有多个参数)且包含所有key的所有字段值:
可以直接取出对应的值来对所有的key进行缓存清除。
如这里入参包含了这两个Key的所有字段(id、accountId),所以直接取出来删除:
@Caching(evict = {
@CacheEvict(key = "{#p0.id}"),
@CacheEvict(key = "{#p0.accountId, #p0.id}")
})
public void insert(UserDTO userDTO)
(2)参数含有List对象且包含所有key的所有字段值:
Spring Cache默认只支持String类参数作为cache key,对于Collection类型的参数,无法直接将其作为cache key,此类情况需要通过构造自定义的key解析规则来实现,我们将key拼装后的结果通过包装为一个自定义的CollectionWrapper对象来作为cache key进行解析,此例中调用了del.batchKeys(Collection<String>, Collection<String>)以及del.batchKeys(Collection<String>)方法,其返回结果将会是一个CollectionWrapper对象(里面存放了批量的key1和key2,实际上是通过重写RedisCache的evict()方法进行cache key的判断,如果cache key是CollectionWrapper类型对象,将会走自定义的逻辑)。
@Caching(evict = {
@CacheEvict(key = "@delKey.batchKeys(#p0?.![#this.accountId], #p0?.![#this.id])"),
@CacheEvict(key = "@delKey.batchKeys(#p0?.![#this.id])")
})
public void insert(List<UserDTO> users)
(3)参数为单个对象(可以有多个参数)且不包含所有key的所有字段值
不建议这样做,应当在入参中包含key所需要的所有字段值。如入参只有id或者accountId,这样对应不上缓存。
(4)参数含有List对象且不包含所有key的所有字段值
不建议这样做,应当在入参中包含key所需要的所有字段值。
5、缓存一致问题
为了避免多线程缓存一致性问题,beforeInvocation还是默认为false。
为了保证缓存正常被删除,处理方式:
方式1: 要求上层传递参数中包含全字段的entity list。
方式2: 或者在repository中增加一个公共的方法deleteList,这个方法里传参是List<Entity>, 由这个方法统一进行缓存的清理,注意: 同repository其他方法先查到list,然后再调用这个统一删除方法,调用时候要保证aop生效(public 方法,注入自身)
二、demo
1、pom
2、配置文件
3、config
4、controller
5、service
6、dao
7、dto
8、测试:
8.1、缓存与删除缓存:
(1)第一次访问localhost:1111/plusDemo/param/listParams控制台输出:
2024-04-25T17:14:33.330+08:00 INFO 36136 --- [nio-1111-exec-2] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@644769222 wrapping com.mysql.cj.jdbc.ConnectionImpl@5ee5b9f5] will not be managed by Spring
==> Preparing: SELECT id,param_key,param_value FROM t_param
==> Parameters:
<== Columns: id, param_key, param_value
<== Row: 1, a, a_1
<== Row: 2, a, a_2
<== Total: 2
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@14369ce6]
再次访问,返回数据和第一次一样,但是控制台无输出,断点可以看到在serveimImpl直接返回了,并没有进入ParamRepository。
(2)删除缓存
多次访问localhost:1111/plusDemo/param/listParamsByKey?key=a后,访问localhost:1111/plusDemo/param/deleteById?key=a,这时再访问listParamsByKey发现又走db层了
==> Preparing: DELETE FROM t_param WHERE (param_key = ?)
==> Parameters: a(String)
<== Updates: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@341e0500]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@287ed822] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@222873874 wrapping com.mysql.cj.jdbc.ConnectionImpl@4cad2112] will not be managed by Spring
==> Preparing: SELECT id,param_key,param_value FROM t_param WHERE (param_key = ?)
==> Parameters: a(String)
<== Total: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@287ed822]
8.2、删除缓存加入异常
@CacheEvict(key = "{#p0}",beforeInvocation = true)
public void deleteByKey(String key) {
LambdaQueryWrapper<ParamDTO> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ParamDTO::getParamKey,key);
paramMapper.delete(queryWrapper);
int a = 1/0;
}
访问localhost:1111/plusDemo/param/listParamsByKey?key=a后再访问localhost:1111/plusDemo/param/listParamsByKey?key=a可以看到缓存已经清除了,重新走db查询