创建SpringBoot项目
运行docker,测试连接
docker run -itd --name dockerRedis -p 6379:6379 redis
docker exec -it dockerRedis redis-cli
// 查看所有容器
docker ps -a
// 查看所有容器ID
docker ps -a -q
// stop停止所有容器
docker stop $(docker ps -a -q)
//remove删除所有容器
docker rm $(docker ps -a -q)
pom.xml文件中增加连接池
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
完整pom.xml文件为:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.1</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>org.redis.learning.it</groupId>
<artifactId>SpringBootRedisDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringBootRedisDemo</name>
<description>Demo project for Spring Boot With Redis</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
配置文件application.properties:
server.port=8080
server.servlet.context-path=/redis
############################################################
# REDIS 配置
############################################################
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.min-idle=2
spring.redis.timeout=100
spring.cache.cache-names=myCache
启动类上添加如下代码,表示开启缓存:
package org.redis.learning.it;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
// 开启缓存
@EnableCaching
@SpringBootApplication
public class SpringBootRedisDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootRedisDemoApplication.class, args);
}
}
准备User Bean文件:其中LocalDate序列化与反序列化配置
package org.redis.learning.it.bean;
import java.io.Serializable;
import java.time.LocalDate;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import lombok.Data;
@Data
public class User implements Serializable {
private static final long serialVersionUID = 7043831680764669930L;
private int userId;
private String userName;
private int age;
// LocalDate序列化与反序列化配置
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonSerialize(using = LocalDateSerializer.class)
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate birthday;
}
备注:LocalDate和LocalDateTime序列化&反序列化示例
package com.lwy.it.book.vo;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.Data;
@Data
public class BookVO implements Serializable {
private static final long serialVersionUID = 683534413370900389L;
private int id;
private String bookName;
private double bookPrice;
// LocalDate序列化与反序列化配置
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonSerialize(using = LocalDateSerializer.class)
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate bookPublishDate;
// LocalDateTime序列化与反序列化配置
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime bookProduceTime;
}
SpringBoot 整合 Redis,否则在Service中@Resource导入失败
package org.redis.learning.it.configuration;
import javax.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfiguration {
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 配置连接工厂
template.setConnectionFactory(factory);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
StringRedisSerializer keySerializer = new StringRedisSerializer();
template.setKeySerializer(keySerializer);
template.setHashKeySerializer(keySerializer);
// 使用GenericJackson2JsonRedisSerializer来序列化和反序列化Redis的Value/HashValue值(默认使用JDK的序列化方式)
GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer();
template.setValueSerializer(valueSerializer);
template.setHashValueSerializer(valueSerializer);
return template;
}
@Bean
public RedisOperations<String, Object> redisOperations() {
RedisOperations<String, Object> redisOperations = redisTemplate;
return redisOperations;
}
}
更新文件,重写public CacheErrorHandler errorHandler()方法:
使用GenericJackson2JsonRedisSerializer序列化和反序列化Value:
package org.redis.learning.it.configuration;
import javax.annotation.Resource;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfiguration extends CachingConfigurerSupport {
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 配置连接工厂
template.setConnectionFactory(factory);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
StringRedisSerializer keySerializer = new StringRedisSerializer();
template.setKeySerializer(keySerializer);
template.setHashKeySerializer(keySerializer);
// 使用GenericJackson2JsonRedisSerializer来序列化和反序列化Redis的Value/HashValue值(默认使用JDK的序列化方式)
GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer();
template.setValueSerializer(valueSerializer);
template.setHashValueSerializer(valueSerializer);
return template;
}
@Bean
public RedisOperations<String, Object> redisOperations() {
RedisOperations<String, Object> redisOperations = redisTemplate;
return redisOperations;
}
@Override
public CacheErrorHandler errorHandler() {
return new RedisCacheErrorHandler();
}
}
使用Jackson2JsonRedisSerializer序列化和反序列化Value:
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfiguration extends CachingConfigurerSupport {
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 配置连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 使用Jackson2JsonRedisSerializer来序列化和反序列化Redis的value值
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// 指定药序列化的域,field,get和set,以及修饰符范围
// ANY任何级别的字段都可以自动识别
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会抛出异常
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// value采用Json序列化
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// 使用StringRedisSerializer来序列化和反序列化Redis的key
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 使用StringRedisSerializer来序列化和反序列化Redis的Hash key
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// Hash value采用Json序列化
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
return redisTemplate;
}
@Override
public CacheErrorHandler errorHandler() {
return new RedisCacheErrorHandler();
}
}
Spring CacheManager 配置
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
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.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheManager redisCacheManager =
// 配置Redis连接工厂
RedisCacheManager.builder(redisConnectionFactory)
// 设置默认配置
.cacheDefaults(defaultRedisCacheConfiguration(1200L))
// 不同Key的自定义配置
.withInitialCacheConfigurations(this.initialCacheConfiguration())
.transactionAware()
.build();
return redisCacheManager;
}
private RedisCacheConfiguration defaultRedisCacheConfiguration(Long second) {
// 使用Jackson2JsonRedisSerializer来序列化和反序列化Redis的value值
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// 指定药序列化的域,field,get和set,以及修饰符范围
// ANY任何级别的字段都可以自动识别
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会抛出异常
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
RedisCacheConfiguration redisCacheConfiguration =
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(second))
.serializeKeysWith(RedisSerializationContext
.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext
.SerializationPair
.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
return redisCacheConfiguration;
}
private Map<String, RedisCacheConfiguration> initialCacheConfiguration() {
Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>();
// 设置redisCache 120s过期,同时设置序列化方式
configurationMap.put("redisCache", this.defaultRedisCacheConfiguration(120L));
// testCache 240s过期,同时设置序列化方式
configurationMap.put("testCache", this.defaultRedisCacheConfiguration(240L));
return configurationMap;
}
}
新增RedisCacheErrorHandler处理Cache Exception
package org.redis.learning.it.configuration;
import org.springframework.cache.Cache;
import org.springframework.cache.interceptor.CacheErrorHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class RedisCacheErrorHandler implements CacheErrorHandler {
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
log.error(exception.getMessage(), exception);
}
@Override
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
log.error(exception.getMessage(), exception);
}
@Override
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
log.error(exception.getMessage(), exception);
}
@Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
log.error(exception.getMessage(), exception);
}
}
新增Redis操作Util:RedisOperateUtil,使得Redis宕机不影响业务逻辑:
package org.redis.learning.it.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.QueryTimeoutException;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
public class RedisOperateUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public boolean exists(String key) {
boolean result = false;
try {
result = redisTemplate.hasKey(key);
} catch (RedisConnectionFailureException exception) {
log.error(exception.getMessage(), exception);
} catch (QueryTimeoutException exception) {
log.error(exception.getMessage(), exception);
}
return result;
}
public Object get(Object key) {
Object object = null;
try {
object = redisTemplate.opsForValue().get(key);
} catch (RedisConnectionFailureException exception) {
log.error(exception.getMessage(), exception);
} catch (QueryTimeoutException exception) {
log.error(exception.getMessage(), exception);
}
return object;
}
public void set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
} catch (Exception exception) {
log.error(exception.getMessage(), exception);
}
}
}
RedisOperations及子类RedisTemplate使用:
package org.redis.learning.it.service;
import javax.annotation.Resource;
import org.redis.learning.it.bean.User;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
@Service
public class UserService {
@Resource
private RedisOperations<String, User> redisOperations;
@Resource
private RedisTemplate<String, User> redisTemplate;
public User findUserById(int id) {
User user = (User)redisOperations.opsForValue().get(md5(id));
return user;
}
public void saveUser(User user) {
redisTemplate.opsForValue().set(md5(user.getUserId()), user);
}
// 通过MD5自定义Key
private String md5(int id) {
String salt = "SpringBootWithRedis";
String key = String.valueOf(id);
String md5 = DigestUtils.md5DigestAsHex(key.concat(salt).getBytes());
return md5;
}
}
更新UserService:
package org.redis.learning.it.service;
import java.time.LocalDate;
import org.redis.learning.it.bean.User;
import org.redis.learning.it.util.RedisOperateUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
@Service
public class UserService {
@Autowired
private RedisOperateUtil redisOperateUtil;
public User findUserById(int id) {
User user = (User)redisOperateUtil.get(md5(id));
if (user == null) {
// 模拟数据库查询操作等
user = new User();
user.setUserId(1);
user.setUserName("张三");
user.setAge(18);
user.setBirthday(LocalDate.of(2000, 10, 10));
}
return user;
}
public void saveUser(User user) {
redisOperateUtil.set(md5(user.getUserId()), user);
}
// 通过MD5自定义Key
private String md5(int id) {
String salt = "SpringBootWithRedis";
String key = String.valueOf(id);
String md5 = DigestUtils.md5DigestAsHex(key.concat(salt).getBytes());
return md5;
}
}
接口Controller使用@Cache缓存配置
package org.redis.learning.it.controller;
import java.time.LocalDate;
import org.redis.learning.it.bean.User;
import org.redis.learning.it.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@CacheConfig(cacheNames = "myCache")
public class RedisController {
@Autowired
private UserService userService;
@GetMapping("/saveUser")
public String saveUser() {
User user = new User();
user.setUserId(1);
user.setUserName("张三");
user.setBirthday(LocalDate.of(1990, 01, 01));
user.setAge(30);
userService.saveUser(user);
return "SUCCESS";
}
@GetMapping("/findUser")
@Cacheable(key = "#id")
public User findUserById(Integer id) {
if (id != null) {
User user = userService.findUserById(id);
return user;
}
return new User();
}
}
源码分析:RedisCacheManager,相关的配置是在org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration 类中完成的。部分源码如下:
@Configuration
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory,
ResourceLoader resourceLoader) {
RedisCacheManagerBuilder builder = RedisCacheManager
.builder(redisConnectionFactory)
.cacheDefaults(determineConfiguration(resourceLoader.getClassLoader()));
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
}
return this.customizerInvoker.customize(builder.build());
}
}
看类上的注解,发现在万事俱备的情况下,系统会自动提供一个 RedisCacheManager 的 Bean,这个 RedisCacheManager 间接实现了 Spring 中的 Cache 接口,有了这个 Bean,我们就可以直接使用 Spring 中的缓存注解和接口了,而缓存数据则会被自动存储到 Redis 上。
缓存使用
@CacheConfig
这个注解在类上使用,用来描述该类中所有方法使用的缓存名称,当然也可以不使用该注解,直接在具体的缓存注解上配置名称。
@Cacheable
这个注解一般加在查询方法上,表示将一个方法的返回值缓存起来,默认情况下,缓存的 key 就是方法的参数,缓存的 value 就是方法的返回值。
@CachePut
这个注解一般加在更新方法上,当数据库中的数据更新后,缓存中的数据也要跟着更新,使用该注解,可以将方法的返回值自动更新到已经存在的 key 上。
@CacheEvict
这个注解一般加在删除方法上,当数据库中的数据删除后,相关的缓存数据也要自动清除,该注解在使用的时候也可以配置按照某种条件删除(condition 属性)或者或者配置清除所有缓存(allEntries 属性)
原理:
CacheManager===Cache 缓存组件来实际给缓存中存取数据
1)引入redis的starter,容器中保存的是 RedisCacheManager;
2)RedisCacheManager 帮我们创建 RedisCache 来作为缓存组件;RedisCache通过操作redis缓存数据的
3)默认保存数据 k-v 都是Object;利用序列化保存;如何保存为json
1.引入了redis的starter,cacheManager变为 RedisCacheManager;
2.默认创建的 RedisCacheManager 操作redis的时候使用的是 RedisTemplate<Object, Object>
3.RedisTemplate<Object, Object> 是 默认使用jdk的序列化机制
4)自定义CacheManager;
Spring Cache :Spring Cache学习_liwenyang1992的专栏-CSDN博客