Spring引入了对Cache的支持
Spring引入了对Cache的支持。其使用方法和原理都类似于Spring对事务管理的支持。Spring Cache是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存放在缓存中,等到下次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。所以在使用Spring Cache的时候我们要保证我们缓存的方法对于相同的方法参数要有相同的返回结果。
使用Spring Cache需要我们做两方面的事:
1.声明某些方法使用缓存
2. 配置Spring对Cache的支持
spring-data-redis针对jedis提供了如下功能:
1.连接池自动管理,提供了一个高度封装的“RedisTemplate”类
2.针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口
-
ValueOperations:简单K-V操作
-
SetOperations:set类型数据操作
-
ZSetOperations:zset类型数据操作
-
HashOperations:针对map类型的数据操作
-
ListOperations:针对list类型的数据操作
-
注解缓存的使用
@Cacheable:在方法执行前Spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;没有则调用方法并将方法返回值放进缓存。
-
参数 解释 example value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如:
@Cacheable(value=”mycache”)
@Cacheable(value={”cache1”,”cache2”}key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 @Cacheable(value=”testcache”,key=”#userName”) condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 @Cacheable(value=”testcache”,condition=”#userName.length()>2”) 除了上述使用方法参数作为key之外,Spring还为我们提供了一个root对象可以用来生成key。通过该root对象我们可以获取到以下信息。
属性名称
描述
示例
methodName
当前方法名
#root.methodName
method
当前方法
#root.method.name
target
当前被调用的对象
#root.target
targetClass
当前被调用的对象的class
#root.targetClass
args
当前方法参数组成的数组
#root.args[0]
caches
当前被调用的方法使用的Cache
#root.caches[0].name
-
在支持Spring Cache的环境下,对于使用@Cacheable标注的方法,Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。@CachePut也可以声明一个方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
@CacheEvict:删除缓存中的数据。
-
@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。
-
allEntries是boolean类型,表示是否需要清除缓存中的所有元素。
实战操作
构建Springboot引入pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.14</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
添加基础参数
spring.redis.database=0
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.timeout=5000
配置RedisConfig
package com.example.redis.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Configuration
@EnableCaching
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig extends CachingConfigurerSupport {
/**
* 设置 redis 数据默认过期时间,默认2小时
* 设置@cacheable 序列化方式
*/
@Bean
public RedisCacheConfiguration redisCacheConfiguration(){
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofHours(2));
return configuration;
}
@SuppressWarnings("all")
@Bean(name = "redisTemplate")
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
//序列化
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
//org.springframework.data.redis.serializer.StringRedisSerializer s=new org.springframework.data.redis.serializer.StringRedisSerializer();
// value值的序列化采用fastJsonRedisSerializer
template.setValueSerializer(fastJsonRedisSerializer);
template.setHashValueSerializer(fastJsonRedisSerializer);
// 全局开启AutoType,这里方便开发,使用全局的方式
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
// 建议使用这种方式,小范围指定白名单
// ParserConfig.getGlobalInstance().addAccept("com.example.entity");
// key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
/**
* 没有指定key的时候( @CachePut(key="#user.id"))也就是 直接使用@CachePut,默认
* 自定义缓存key生成策略,默认将使用该策略
*/
@Bean
@Override
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
Map<String,Object> container = new HashMap<>(3);
Class<?> targetClassClass = target.getClass();
// 类地址
container.put("class",targetClassClass.toGenericString());
// 方法名称
container.put("methodName",method.getName());
// 包名称
container.put("package",targetClassClass.getPackage());
// 参数列表
for (int i = 0; i < params.length; i++) {
container.put(String.valueOf(i),params[i]);
}
// 转为JSON字符串
String jsonString = JSON.toJSONString(container);
// 做SHA256 Hash计算,得到一个SHA256摘要作为Key
log.info("jsonString:"+jsonString);
String s = DigestUtils.sha256Hex(jsonString);
log.info("DigestUtils.sha256Hex::"+s);
return s;
};
}
@Bean
@Override
public CacheErrorHandler errorHandler() {
// 异常处理,当Redis发生异常时,打印日志,但是程序正常走
log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
return new CacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
}
@Override
public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
}
@Override
public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
}
@Override
public void handleCacheClearError(RuntimeException e, Cache cache) {
log.error("Redis occur handleCacheClearError:", e);
}
};
}
}
/**
* Value 序列化
*
* @author /
* @param <T>
*/
class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
private Class<T> clazz;
FastJsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) {
if (t == null) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(StandardCharsets.UTF_8);
}
@Override
public T deserialize(byte[] bytes) {
if (bytes == null || bytes.length <= 0) {
return null;
}
String str = new String(bytes, StandardCharsets.UTF_8);
return JSON.parseObject(str, clazz);
}
}
/**
* 重写序列化器
*和data-redis的StringRedisSerializer区别:可以使用Object类型作为key
*/
class StringRedisSerializer implements RedisSerializer<Object> {
private final Charset charset;
StringRedisSerializer() {
this(StandardCharsets.UTF_8);
}
private StringRedisSerializer(Charset charset) {
//Assert.notNull(charset, "Charset must not be null!");
this.charset = charset;
}
@Override
public String deserialize(byte[] bytes) {
return (bytes == null ? null : new String(bytes, charset));
}
@Override
public byte[] serialize(Object object) {
String string = JSON.toJSONString(object);
if (StringUtils.isBlank(string)) {
return null;
}
string = string.replace("\"", "");
return string.getBytes(charset);
}
}
创建实体类User
package com.example.redis.entity;
import lombok.Data;
/**
* @program: myblog-test
* @description:
* @author: Mr.liu
* @create: 2020-08-21 16:30
**/
@Data
public class User {
private String id;
private String username;
private String password;
}
设置dao层
package com.example.redis.dao;
import com.example.redis.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
/**
* @program: myblog-test
* @description:
* @author: Mr.liu
* @create: 2020-08-21 16:29
**/
@Slf4j
@Service
@CacheConfig(cacheNames = "TEST.USER")
public class UserDao {
/*
* 注解缓存的使用
@Cacheable:在方法执行前Spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;没有则调用方法并将方法返回值放进缓存。
@CachePut:将方法的返回值放到缓存中。
@CacheEvict:删除缓存中的数据。
*
* */
@CachePut(key="#user.id")
public User set(User user){
log.info("执行set:"+user.getId());
return user;
}
@Cacheable(key="#user.id")
public User get(User user){
log.info("执行get:"+user.getId());
return user;
}
@CacheEvict(key="#user.id")
//@CacheEvict(allEntries = true)清理全部
public void del(User user){
log.info("执行del:"+user.getId());
}
}
package com.example.redis.dao;
import com.example.redis.config.RedisConfig;
import com.example.redis.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
/**
* @program: myblog-test
* @description:
* @author: Mr.liu
* @create: 2020-08-21 16:57
**/
@Slf4j
@Service
@CacheConfig(cacheNames = {"x","x1"})
public class NoneDao {
@CachePut
public User set(User user){
log.info("执行set:"+user.getId());
return user;
}
@Cacheable
public User get(User user){
log.info("执行get:"+user.getId());
return user;
}
@CacheEvict
//@CacheEvict(allEntries = true)清理全部
public void del(User user){
log.info("执行del:"+user.getId());
}
}
设置Controller进行接口测试
package com.example.redis.controller;
import com.example.redis.dao.NoneDao;
import com.example.redis.dao.UserDao;
import com.example.redis.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @program: myblog-test
* @description:
* @author: Mr.liu
* @create: 2020-08-21 16:29
**/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserDao userDao;
@Autowired
private NoneDao noneDao;
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("/set/{id}")
public String set(@PathVariable String id){
User user = new User();
user.setId(id);
user.setUsername("jack");
user.setPassword(id);
userDao.set(user);
return "success";
}
@GetMapping("/get/{id}")
public Object get(@PathVariable String id){
User user = new User();
user.setId(id);
User user1 = userDao.get(user);
return user1;
}
@GetMapping("/del/{id}")
public Object del(@PathVariable String id){
User user = new User();
user.setId(id);
userDao.del(user);
return "success";
}
@GetMapping("/redis/set/{id}")
public String set1(@PathVariable String id){
User user = new User();
user.setId(id);
user.setUsername("xxxx");
user.setPassword(id);
redisTemplate.opsForValue().set(user,user);
return "success";
}
/*
* 不指定缓存域名
* */
@GetMapping("/setn/{id}")
public String setn(@PathVariable String id){
User user = new User();
user.setId(id);
user.setUsername("jack");
user.setPassword(id);
noneDao.set(user);
return "success";
}
@GetMapping("/getn/{id}")
public Object getn(@PathVariable String id){
User user = new User();
user.setId(id);
User user1 = noneDao.get(user);
return user1;
}
@GetMapping("/deln/{id}")
public Object deln(@PathVariable String id){
User user = new User();
user.setId(id);
noneDao.del(user);
return "success";
}
}
测试截图
@GetMapping("/redis/set/{id}")
redisTemplate.opsForValue().set(user,user);
重写序列化器和data-redis的StringRedisSerializer区别:可以使用Object类型作为key
@CacheConfig(cacheNames = {"x","x1"})&&DigestUtils.sha256Hex(jsonString);