1. 前言
一个系统在于数据库交互的过程中,内存的速度远远快于硬盘速度,当我们重复地获取相同数据时,我们一次又一次地请求数据库或远程服务,者无疑时性能上地浪费(这会导致大量时间被浪费在数据库查询或者远程方法调用上致使程序性能恶化),于是有了“缓存”。 本文将介绍在spring boot项目开发中怎样使用spring提供的Spring Cache 与最近很火的 Redis 数据库来实现数据的缓存。
2. Spring Cache简介
Spring Cache是Spring框架提供的对缓存使用的抽象类,支持多种缓存,比如Redis、EHCache等,集成很方便。同时提供了多种注解来简化缓存的使用,可对方法进行缓存。
2.1 Spring Cache 的注解
-
@EnableCaching:开启缓存。
-
@CacheConfig:这个注解是用于在同一个类中共享一些基础的cache配置的一个类级别的注解,允许共享缓存的名称、KeyGenerator、CacheManager
和CacheResolver。 该操作会被覆盖 -
@Cacheable:标记在一个方法上,也可以标记在一个类上。主要是缓存标注对象的返回结果,标注在方法上缓存该方法的返回值,标注在类上,缓存该类所有的方法返回值。
参数: value缓存名、 key缓存键值、 condition满足缓存条件、unless否决缓存条件。
-
@CachePut:方法支持缓存功能。与@Cacheable不同的是使用
-
@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
-
@CacheEvict:从缓存中移除相应数据。
2.2 SpEL上下文数据
Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:
3. 实践—SpringCache和Redis集成
- 3.1 步骤
我们要把一个查询函数加入缓存功能,大致需要三步。
一、在函数执行前,我们需要先检查缓存中是否存在数据,如果存在则返回缓存数据。
*
二、如果不存在,就需要在数据库的数据查询出来。
*
三、最后把数据存放在缓存中,当下次调用此函数时,就可以直接使用缓存数据,减轻了数据库压力。
本实例没有存入MySQL数据库,主要是为了方便实践,实际使用中大家可以把service层中的方法改为数据库操作代码即可。
3.2 具体操作添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
配置SpringCache,Redis连接等信息
redis配置 - application.yml
server:
port: 6379
spring:
redis配置
redis:
host: your ip
port: 6379
password: lzw
database: 0 #选择储存库
application:
name: redis
SpringCache配置 - RedisConfig.java
package com.lzw.redis.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.util.StringUtils;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
/**
* 描述:
*
* @author liaozw
* @version 1.0.0
* @pachage com.lzw.redis.config
* @create 2020/1/18
**/
@Configuration
public class RedisConfig {
@Value("${spring.application.name}")
private String name;
@Autowired
private RedisConnectionFactory connectionFactory;
/**
* 带前缀key
*
* @return
*/
public RedisSerializer<String> keySerializer() {
String appName = StringUtils.isEmpty(name) ? "" : name.concat(":");//缓存前缀加上应用名称
RedisSerializer<String> redisSerializer = new KeyPrefixRedisSerializer(appName);
return redisSerializer;
}
/**
* 序列化value
* @return
*/
public GenericJackson2JsonRedisSerializer valueSerializer() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
return new GenericJackson2JsonRedisSerializer(objectMapper);
}
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String,Object> redis=new RedisTemplate<>();
redis.setConnectionFactory(redisConnectionFactory);
// 生成key的策略
redis.setKeySerializer(keySerializer());
redis.setEnableDefaultSerializer(false);
redis.setHashKeySerializer(keySerializer());
redis.setHashValueSerializer(valueSerializer());
redis.setValueSerializer(valueSerializer());
redis.afterPropertiesSet();
return redis;
}
/**
* Cache管理
* @return
*/
@Bean
public CacheManager cacheManager() {
//初始化一个RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
//设置CacheManager的值序列化方式为JdkSerializationRedisSerializer,但其实RedisCacheConfiguration默认就是使用StringRedisSerializer序列化key,JdkSerializationRedisSerializer序列化value,所以以下注释代码为默认实现
//ClassLoader loader = this.getClass().getClassLoader();
//JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer(loader);
//RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jdkSerializer);
//RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
RedisCacheConfiguration defaultCacheConfig = getRedisCacheConfiguration();
//设置默认超过期时间是30秒
//defaultCacheConfig.entryTtl(Duration.ofSeconds(30));
//初始化RedisCacheManager
String appName = StringUtils.isEmpty(name) ? "" : name.concat(":");//缓存前缀加上应用名称
RedisCacheManager cacheManager =new RedisCacheManager(redisCacheWriter,defaultCacheConfig);
return cacheManager;
}
private RedisCacheConfiguration getRedisCacheConfiguration() {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
RedisSerializationContext
.SerializationPair
.fromSerializer(valueSerializer()));
redisCacheConfiguration.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()));
//redisCacheConfiguration.computePrefixWith(cacheName -> CacheNameConstant.CACHE_PREFIX.concat(cacheName));
return redisCacheConfiguration;
}
}
编写实体类
@Data
@Accessors(chain = true)
@ToString
public class User implements Serializable {
private String id;
private String userName;
private String passWord;
}
编写缓存操作类
package com.lzw.redis.cache;
import com.lzw.redis.entity.User;
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.Component;
@CacheConfig(cacheNames = "user")
@Component
public class UserCache {
/**
* 新增
* @param user
* @return
*/
@Cacheable(key = "#user.id")
public User getUser(User user) {
System.out.println("缓存:"+user.toString());
return user;
}
/**
* 更新
* @param user
* @return
*/
@CachePut(key = "#user.id")
public User updateUser(User user) {
return user;
}
/**
* 删除
* @param id
* @return
*/
@CacheEvict(key = "#id")
public Boolean deleteUser(String id) {
return true;
}
}
测试
@Test
public void cacheTest() {
// 缓存
User user = new User().setId(UUID.randomUUID().toString()).setUserName("lzw").setPassWord("qq群1037465137");
System.out.println(userCache.getUser(user).toString());
}
/*输出结果*/
缓存:User(id=8a5ce06c-c273-45da-a746-7808b4914c5e, userName=lzw, passWord=qq群1037465137)
User(id=8a5ce06c-c273-45da-a746-7808b4914c5e, userName=lzw, passWord=qq群1037465137)
@Test
public void cacheTest() {
// 更新缓存
User user = new User().setId("8a5ce06c-c273-45da-a746-7808b4914c5e");
System.out.println(userCache.updateUser(user).toString());
System.out.println(userCache.getUser(user).toString());
/*输出结果*/
User(id=8a5ce06c-c273-45da-a746-7808b4914c5e, userName=null, passWord=null)
User(id=8a5ce06c-c273-45da-a746-7808b4914c5e, userName=null, passWord=null)
}
@Test
public void cacheTest() {
// 删除缓存
User user = new User().setId(UUID.randomUUID().toString()).setUserName("lzw1").setPassWord("qq群1037465137");
System.out.println(userCache.deleteUser("aa905945-ef9b-418c-91e2-0d114b9c09c6"));
System.out.println(userCache.getUser(user).toString());
/*输出结果*/
true
缓存:User(id=282e49f3-c96a-48d8-b4d1-32ab19d716b4, userName=lzw1, passWord=qq群1037465137)
User(id=282e49f3-c96a-48d8-b4d1-32ab19d716b4, userName=lzw1, passWord=qq群1037465137)
}