目录
前言:spring+redis集成已完成的前提下编辑Spring注解式缓存
1.2 配置自定义Key生成器CacheKeyGenerator 缓存的Java对象一定要重写hashCode和eqauls
2.1 @CacheConfig 它是一个类级别的注解,允许共享缓存的名称、KeyGenerator、CacheManager和CacheResolver
Spring注解式缓存
Redis是key-value存储的非关系型数据库。Spring Data Redis包含了多个模板实现,用来完成Redis数据库的数据存取功能
1. spring注解式缓存使用步骤
<bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
<constructor-arg name="redisOperations" ref="redisTemplate" />
<!--redis缓存数据过期时间单位秒-->
<property name="defaultExpiration" value="${redis.expiration}" />
<property name="usePrefix" value="true"/>
<property name="cachePrefix">
<bean class="org.springframework.data.redis.cache.DefaultRedisCachePrefix">
<constructor-arg index="0" value="-cache-"/>
</bean>
</property>
</bean>
<bean id="cacheKeyGenerator" class="com.zking.ssm.redis.CacheKeyGenerator"></bean>
<cache:annotation-driven cache-manager="redisCacheManager" key-generator="cacheKeyGenerator"/>
value:缓存位置的一段名称,不能为空 key:缓存的key,默认为空,表示使用方法的参数类型及参数值作为key,支持SpEL
配置在方法或类上,作用:本方法执行后,先去缓存看有没有数据,如果没有,从数据库中查找出来,给缓存中存一份,返回结果, 下次本方法执行,在缓存未过期情况下,先在缓存中查找,有的话直接返回,没有的话从数据库查找 value:缓存位置的一段名称,不能为空 key:缓存的key,默认为空,表示使用方法的参数类型及参数值作为key,支持SpEL keyGenerator:指定key的生成策略 condition:触发条件,满足条件就加入缓存,默认为空,表示全部都加入缓存,支持SpEL 注1:condition是在方法执行前评估, unless是在方法执行后评估.
类似于更新操作,即每次不管缓存中有没有结果,都从数据库查找结果,并将结果更新到缓存,并返回结果 value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存
用来清除用在本方法或者类上的缓存数据(用在哪里清除哪里) value:缓存位置的一段名称,不能为空 key:缓存的key,默认为空,表示使用方法的参数类型及参数值作为key,支持SpEL condition:触发条件,满足条件就加入缓存,默认为空,表示全部都加入缓存,支持SpEL allEntries:true表示清除value中的全部缓存,默认为false
@Cacheable(value="cacheName", key="#id")
public ResultDTO method(int id);
注1:Spring Cacheable注解不缓存null值 用Cacheable注解时,发现空值,也会被缓存下来。下次另一个系统如果更新了值,这边从缓存取,还是空值,会有问题。 解决方案:
@Cacheable(value = "service", key = "#service.serviceId.toString()", unless = "#result == null")
@Cacheable(value = "service", keyGenerator = RedisKeys.KEY_GENERATOR, unless = "#result.size() == 0")
@Cacheable(value="cacheName", key="T(String).valueOf(#name).concat('-').concat(#password))
public ResultDTO method(int name, String password);
@Cacheable(value="cacheName", key="#user.id)
public ResultDTO method(User user);
注1:以上三种配置方式中,使用了spEL表达式
@Cacheable(value="gomeo2oCache", keyGenerator = "keyGenerator")
public ResultDTO method(User user);
spring注解式缓存中的巨坑~~~~~~~ 没有指定key,默认情况下spirng会使用SimpleKeyGenerator生成key, 而Spring默认的SimpleKeyGenerator是不会将函数名组合进key中的,举个例子:
@Component
public class CacheTestImpl implements CacheTest {
@Cacheable("databaseCache")
public Long test1()
{ return 1L; }
@Cacheable("databaseCache")
public Long test2()
{ return 2L; }
@Cacheable("databaseCache")
public Long test3()
{ return 3L; }
@Cacheable("databaseCache")
public String test4()
{ return "4"; }//注意返回的是字符串“4”
}
我们期望的输出是: 1 2 3 4 而实际上的输出是: 1 1 1 此外,原子类型的数组,直接作为key使用也是不会生效的,为了解决上述2个问题,只能通过自定义KeyGenerator解决
自定义Key生成器CacheKeyGenerator:源码见资料“CacheKeyGenerator.java”,另外此类使用非加密哈希算法MurmurHash
CacheKeyGenerator.java:
package com.zking.ssm.redis;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.util.ClassUtils;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
@Slf4j
public class CacheKeyGenerator implements KeyGenerator {
// custom cache key
public static final int NO_PARAM_KEY = 0;
public static final int NULL_PARAM_KEY = 53;
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder key = new StringBuilder();
key.append(target.getClass().getSimpleName()).append(".").append(method.getName()).append(":");
if (params.length == 0) {
key.append(NO_PARAM_KEY);
} else {
int count = 0;
for (Object param : params) {
if (0 != count) {//参数之间用,进行分隔
key.append(',');
}
if (param == null) {
key.append(NULL_PARAM_KEY);
} else if (ClassUtils.isPrimitiveArray(param.getClass())) {
int length = Array.getLength(param);
for (int i = 0; i < length; i++) {
key.append(Array.get(param, i));
key.append(',');
}
} else if (ClassUtils.isPrimitiveOrWrapper(param.getClass()) || param instanceof String) {
key.append(param);
} else {//Java一定要重写hashCode和eqauls
key.append(param.hashCode());
}
count++;
}
}
String finalKey = key.toString();
log.debug("using cache key={}", finalKey);
return finalKey;
}
}
(源码46行: Hashing.murmur3_128().hashString),需要引入google guava项目,其pom如下:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.0.1-jre</version>
</dependency>
只应将热数据放到缓存中 所有缓存信息都应设置过期时间 缓存过期时间应当分散以避免集中过期 缓存key应具备可读性 应避免不同业务出现同名缓存key 可对key进行适当的缩写以节省内存空间 选择合适的数据结构 确保写入缓存中的数据是完整且正确的 避免使用耗时较长的操作命令,如:keys * Redis默认配置中操作耗时超过10ms即视为慢查询 一个key对应的数据不应过大
对于string类型,一个key对应的value大小应控制在10K以内,1K左右更优hash类型,不应超过5000行 避免缓存穿透 数据库中未查询到的数据,可在Redis中设置特殊标识,以避免因缓存中无数据而导致每次请求均达到数据库缓存层不应抛出异常
缓存应有降级处理方案,缓存出了问题要能回源到数据库进行处理
可以进行适当的缓存预热 对于上线后可能会有大量读请求的应用,在上线之前可预先将数据写入缓存中读的顺序是先缓存,后数据库;写的顺序是先数据库,后缓存
数据一致性问题 数据源发生变更时可能导致缓存中数据与数据源中数据不一致,应根据实际业务需求来选择适当的缓存更新策略:
主动更新:在数据源发生变更时同步更新缓存数据或将缓存数据过期。一致性高,维护成本较高。 被动删除:根据缓存设置的过期时间有Redis负责数据的过期删除。一致性较低,维护成本较低。
@Transactional(readOnly = true)
@Cacheable(value = "service+'By'+service.userId", unless = "#result.size() == 0")
List<Service> listByUserId(Service service, PageBean pageBean);