一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情。
REmote DIctionary Server(Redis) 是一个由 Salvatore Sanfilippo 写的 key-value 存储系统,是跨平台的非关系型数据库。
Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API。
Redis 通常被称为数据结构服务器,因为值(value)可以是字符串(String)、哈希(Hash)、列表(list)、集合(sets)和有序集合(sorted sets)等类型。 相比于其他数据库类型,Redis具备的特点是:
- C/S通讯模型
- 单进程单线程模型
- 丰富的数据类型
- 操作具有原子性
- 持久化
- 高并发读写
- 支持lua脚本
3.快速集成Redis
3.1 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.2 配置Redis数据库
spring: redis: # redis库 database: 3 # redis 服务器地址 host: 127.0.0.1 # redis 端口号 port: 6379 # redis 密码 password: # 连接超时时间(毫秒) timeout: 1000 lettuce: pool: # 连接池最大链接数(负数表示没有限制) max-active: 8 # 连接池最大阻塞等待时间(负数表示没有限制) max-wait: -1 # 连接池最大空闲连接数 max-idle: 8 # 连接池最小空闲连接数 min-idle: 0
连接池参数要选择lettuce的(默认),不要配成jedis的,源码中jedis很多类不存在,因此是不生效的。
3.3 缓存操作
(1)缓存:直接使用SpringBoot的缓存注解即可;
(2)操作:使用 RedisTemplate 或 RedisUtils(个人封装工具) 操作数据
需要注意的是:实体类要实现Serializable(序列化)才能存入redis, 不然会报错。
public class Table implements Serializable {}
3.4 Redis配置类
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.cache.annotation.EnableCaching;
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.*;
import java.time.Duration;
/**
* Redis 配置类,开启缓存
*/
@Configuration
@EnableCaching
public class RedisConfig {
/**
* 配置缓存管理器
* @param factory Redis 线程安全连接工厂
* @return 缓存管理器
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
// 生成两套默认配置,通过 Config 对象即可对缓存进行自定义配置
RedisCacheConfiguration cacheConfig1 = RedisCacheConfiguration.defaultCacheConfig()
// 设置过期时间 10 分钟
.entryTtl(Duration.ofMinutes(10))
// 设置缓存前缀
.prefixKeysWith("cache:user:")
// 禁止缓存 null 值
.disableCachingNullValues()
// 设置 key 序列化
.serializeKeysWith(keyPair())
// 设置 value 序列化
.serializeValuesWith(valuePair());
RedisCacheConfiguration cacheConfig2 = RedisCacheConfiguration.defaultCacheConfig()
// 设置过期时间 30 秒
.entryTtl(Duration.ofSeconds(30))
.prefixKeysWith("cache:admin:")
.disableCachingNullValues()
.serializeKeysWith(keyPair())
.serializeValuesWith(valuePair());
// 返回 Redis 缓存管理器
return RedisCacheManager.builder(factory)
.withCacheConfiguration("user", cacheConfig1)
.withCacheConfiguration("admin", cacheConfig2)
.build();
}
/**
* 配置键序列化
* @return StringRedisSerializer
*/
private RedisSerializationContext.SerializationPair<String> keyPair() {
return RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer());
}
/**
* 配置值序列化,使用 GenericJackson2JsonRedisSerializer 替换默认序列化
* @return GenericJackson2JsonRedisSerializer
*/
private RedisSerializationContext.SerializationPair<Object> valuePair() {
return RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer());
}
}
3.5 操作redis
SpringBoot提供了两个bean来操作redis,分别是RedisTemplate 和 StringRedisTemplate,这两者的主要区别如下:
-
RedisTemplate使用的是JdkSerializationRedisSerializer ,存入数据会将数据先序列化成字节数组然后在存入Redis数据库;
-
StringRedisTemplate使用的是StringRedisSerializer;
@RestController
public class UserController {
@Autowired
private UserService userServer;
@Autowired
StringRedisTemplate stringRedisTemplate;
/**
* 查询所有课程
*/
@RequestMapping("/allCourses")
public String findAll() {
List<Courses> courses = userServer.findAll();
// 将查询结果写入redis缓存
stringRedisTemplate.opsForValue().set("hot", String.valueOf(courses));
// 读取redis缓存
System.out.println(stringRedisTemplate.opsForValue().get("courses"));
return "ok";
}
}
4.我配置的Redis
4.1 我的POM文件
<?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.6.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hanpang</groupId>
<artifactId>springboot-redis-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-redis-01</name>
<description>springboot-redis-01</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</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>
4.2 我的Redis配置类
package com.hanpang.springbootredis01.configuration;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
/**
* Redis 开启声明缓存支持
* @author: 胖先森
* === 面朝大海,春暖花开 ===
* 参考文档:https://segmentfault.com/a/1190000040280884
* 2022-04-05 11:54
**/
@Configuration
@EnableCaching
public class RedisConfiguration {
/**
* Redis 缓存管理器
* @param redisTemplate 模板
* @return 返回 Redis 缓存管理器
*/
@Primary
@Bean
public RedisCacheManager redisCacheManager(RedisTemplate<?, ?> redisTemplate){
// 从 RedisTemplate 中获取连接
RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
// 检查 RedisConnectionFactory 是否为 null
RedisConnectionFactory redisConnectionFactory = Objects.requireNonNull(connectionFactory);
// 检查 RedisConnectionFactory 是否为 null
// 创建新的无锁 RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
// 获取 RedisTemplate 的序列化
RedisSerializer<?> valueSerializer = redisTemplate.getValueSerializer();
// 序列化对
RedisSerializationContext.SerializationPair<?> serializationPair = RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer);
// 获取默认缓存配置
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
// 设置过期时间
// redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(serializationPair).entryTtl(Duration.ofSeconds(60));
// 设置序列化
RedisCacheConfiguration redisCacheConfigurationSerialize = redisCacheConfiguration.serializeValuesWith(serializationPair);
// 创建并返回 Redis 缓存管理
return new TTLRedisCacheManager(redisCacheWriter, redisCacheConfigurationSerialize);
}
/**
* 注意:如果要使用注解 {@link Autowired} 管理 {@link RedisTemplate},
* 则需要将 {@link RedisTemplate} 的 {@link Bean} 缺省泛型
*
* @param redisConnectionFactory Redis 连接工厂
* @return 返回 Redis 模板
*/
@Bean
public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// Helper类简化了 Redis 数据访问代码
RedisTemplate<?, ?> template = new RedisTemplate<>();
// 设置连接工厂。
template.setConnectionFactory(redisConnectionFactory);
// 可以使用读写JSON:使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
// ObjectMapper 提供了从基本 POJO(普通旧Java对象)或从通用 JSON 树模型({@link JsonNode})读取和写入 JSON 的功能,
// 以及执行转换的相关功能。
ObjectMapper objectMapper = new ObjectMapper();
// 枚举,定义影响Java对象序列化方式的简单开/关功能。
// 默认情况下启用功能,因此默认情况下日期/时间序列化为时间戳。
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 如果启用,上下文<code> TimeZone </ code>将基本上覆盖任何其他TimeZone信息;如果禁用,则仅在值本身不包含任何TimeZone信息时使用。
objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
// 注册使用Jackson核心序列化{@code java.time}对象的功能的类。
JavaTimeModule javaTimeModule = new JavaTimeModule();
// 添加序列化
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
// 添加反序列化
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
// 用于注册可以扩展该映射器提供的功能的模块的方法; 例如,通过添加自定义序列化程序和反序列化程序的提供程序。
objectMapper.registerModule(javaTimeModule);
SimpleModule module = new SimpleModule();
module.addSerializer(Double.class, new JsonSerializer<Double>() {
@Override
public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
BigDecimal d = new BigDecimal(value.toString());
gen.writeNumber(d.stripTrailingZeros().toPlainString());
}
});
module.addSerializer(double.class, new JsonSerializer<Double>() {
@Override
public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
BigDecimal d = new BigDecimal(value.toString());
gen.writeNumber(d.stripTrailingZeros().toPlainString());
}
});
module.addDeserializer(Boolean.class, new JsonDeserializer<Boolean>() {
@Override
public Boolean deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return BooleanUtils.toBoolean(jsonParser.getText());
}
});
module.addSerializer(boolean.class, new JsonSerializer<Boolean>() {
@Override
public void serialize(Boolean value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeNumber(BooleanUtils.toInteger(value));
}
});
objectMapper.registerModules(module);
objectMapper.registerModule(new ParameterNamesModule());
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 序列化时带全限定名
objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 1
template.setValueSerializer(jackson2JsonRedisSerializer);
// 2
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
4.3 自定义失效时间扩展
- value中,等号左边的为cacheName, 等号右边的为失效时间
package com.hanpang.springbootredis01.configuration;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.util.StringUtils;
import java.time.Duration;
/**
* @author: 胖先森
* === 面朝大海,春暖花开 ===
* 2022-04-05 12:07
**/
public class TTLRedisCacheManager extends RedisCacheManager {
public TTLRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
String[] cells = StringUtils.delimitedListToStringArray(name, "=");
name = cells[0];
if (cells.length > 1) {
long ttl = Long.parseLong(cells[1]);
// 根据传参设置缓存失效时间
cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(ttl));
}
return super.createRedisCache(name, cacheConfig);
}
}
public class UserServiceImpl implements UserService {
// 有效时间120秒
@Cacheable(value = "user=120", key = "#userId")
@Override
public User get(Long userId) {
log.info("模拟查询数据库中的用户:{}", userId);
User user = new User();
user.setUserId(userId);
user.setUserName("胖先森");
user.setSex("男");
user.setBirthday(LocalDate.now());
user.setCreateDateTime(LocalDateTime.now());
log.info("模拟查询结果:{}", user);
return user;
}
}