1. 介绍
Spring Boot 提供了对 Redis 集成的组件包:spring-boot-starter-data-redis
,spring-boot-starter-data-redis
依赖于spring-data-redis
和 lettuce
。Spring Boot 1.0 默认使用的是 Jedis 客户端,2.0 替换成 Lettuce,但如果你从 Spring Boot 1.5.X 切换过来,几乎感受不大差异,这是因为 spring-boot-starter-data-redis
为我们隔离了其中的差异性。
Lettuce 是一个可伸缩线程安全的 Redis 客户端,多个线程可以共享同一个 RedisConnection,它利用优秀 netty NIO 框架来高效地管理多个连接。
2. 添加依赖
修改pom.xml
<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>
|
3. 添加配置
修改application.yml
server: port: 8080 spring: datasource: ... redis: database: 0 host: 127.0.0.1 port: 6379 timeout: 3000 lettuce: pool: max-active: 8 max-wait: -1 max-idle: 8 min-idle: 0
|
4. 自定义 RedisTemplate
默认情况下的模板只能支持 RedisTemplate<String,String>
,只能存入字符串,很多时候,我们需要自定义 RedisTemplate ,设置序列化器,这样我们可以很方便的操作实例对象。
创建: LettuceRedisConfig.java
package com.hui.javalearn.config;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.io.Serializable;
@Configuration @AutoConfigureAfter(RedisAutoConfiguration.class) public class LettuceRedisConfig {
@Bean public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory){ RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>(); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(connectionFactory); return redisTemplate; } }
|
5. 使用
package com.hui.javalearn;
import com.hui.javalearn.model.UserModel; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations;
import java.io.Serializable;
@SpringBootTest public class TestRedis {
@Autowired private StringRedisTemplate stringRedisTemplate;
@Autowired private RedisTemplate<String, Serializable> serializableRedisTemplate;
@Test void testString(){ String key = "java-str"; String value = "hello-word"; stringRedisTemplate.opsForValue().set(key,value); String s = stringRedisTemplate.opsForValue().get(key); System.out.println(s); }
@Test void testSerializable(){ String key = "java-obj"; ValueOperations<String, Serializable> stringSerializableValueOperations = serializableRedisTemplate.opsForValue(); UserModel userModel = new UserModel(); userModel.setPhone("17600000000"); userModel.setNickName("张三"); stringSerializableValueOperations.set(key,userModel); UserModel cacheUserModel = (UserModel)stringSerializableValueOperations.get(key); if (cacheUserModel != null){ System.out.println("nickName:" + cacheUserModel.getNickName() + " phone:" + cacheUserModel.getPhone()); } } }
|
6. 使用Spring Cache 集成Redis
Spring Cache
具备相当的好的灵活性,不仅能够使用 SpEL(Spring Expression Language)
来定义缓存的 key
和各种 condition
,还提供开箱即用的缓存临时存储方案,也支持和主流的专业缓存例如 EHCache
、Redis
、Guava
的集成。
我使用的Springboot版本为2.3.3,不需要再单独添加依赖spring-boot-starter-cache
,直接使用相关注解就行。
6.1 添加缓存管理器
修改: LettuceRedisConfig.java
package com.hui.javalearn.config;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; 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.connection.lettuce.LettuceConnectionFactory; 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.StringRedisSerializer;
import java.io.Serializable; import java.time.Duration;
@Configuration @AutoConfigureAfter(RedisAutoConfiguration.class) @EnableCaching public class LettuceRedisConfig {
@Bean public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory){ RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig(); RedisCacheConfiguration defaultCacheConfig = redisCacheConfiguration .entryTtl(Duration.ofHours(1)) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) .disableCachingNullValues();
return RedisCacheManager.builder(redisConnectionFactory) .cacheDefaults(defaultCacheConfig) .build(); }
@Bean public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory){ RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>(); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setConnectionFactory(connectionFactory); return redisTemplate; } }
|
6.2 使用
步骤一: 在SerivceImpl使用相关注解
我的UserServiceImpl.java
部分代码
....
@Override @Cacheable(cacheNames = "userService-cache",key = "'phone_'+#phone",unless = "#result == null") public UserModel searchUserByPhone(String phone) { log.info("没有走缓存-->" + phone); return userDao.selectByPhone(phone); }
@Override @Cacheable(cacheNames = "userService-cache",key = "'str'+#string") public String returnString(String string) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyy-MM-dd HH:mm:ss"); String format = simpleDateFormat.format(new Date()); return "string==> " + format; } ....
|
步骤二: 在单元测试中使用
编写单元测试: TestRedis.java
package com.hui.javalearn;
import com.hui.javalearn.service.UserService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest public class TestRedis {
@Autowired private UserService userService;
@Test void testCacheMethod() { userService.searchUserByPhone("176000000"); userService.searchUserByPhone("176000000"); userService.searchUserByPhone("17600113418"); userService.searchUserByPhone("17600113418");
System.out.println(userService.returnString("a")); System.out.println(userService.returnString("a")); System.out.println(userService.returnString("b")); System.out.println(userService.returnString("b")); } }
|
redis缓存情况
127.0.0.1:6379> keys * 1) "userService-cache::strb" 2) "userService-cache::stra" 3) "userService-cache::phone_17600113418"
|
6.3 Spring Cache 注解
注解 |
作用 |
@Cacheable |
将方法的结果缓存起来,下一次方法执行参数相同时,将不执行方法,返回缓存中的结果 |
@CacheEvict |
移除指定缓存 |
@CachePut |
标记该注解的方法总会执行,根据注解的配置将结果缓存 |
@Caching |
可以指定相同类型的多个缓存注解,例如根据不同的条件 |
@CacheConfig |
类级别注解,可以设置一些共通的配置,@CacheConfig(cacheNames=“user”), 代表该类下的方法均使用这个cacheN |
7.踩坑
7.1 @Cacheable不起作用
为了图测试方便,我直接在当前类里面定义一个方法:cacheMethod
,发现 @Cacheable不起作用,后来查资料发现不能这么用。
下面是我当时的错误使用代码:
package com.hui.javalearn;
import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.EnableCaching;
import java.text.SimpleDateFormat; import java.util.Date;
@SpringBootTest public class TestRedis {
@Test void testCacheMethod(){ System.out.println(cacheMethod("a")); System.out.println(cacheMethod("b")); }
@Cacheable(cacheNames = "cache-name", key = "'KEY_'+#str") public String cacheMethod(String str){ SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String format = simpleDateFormat.format(new Date()); return "cache: " +str + " Time: " + format; } }
|
原因: 因为@Cacheable 是使用AOP 代理实现的 ,通过创建内部类来代理缓存方法,这样就会导致一个问题:类内部的方法调用类内部的缓存方法不会走代理,而不走代理,就不能正常创建缓存。
7.2 set后,出现很多\x00
代码如下
package com.hui.javalearn;
import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import java.util.concurrent.TimeUnit;
@SpringBootTest public class TestRedis {
@Autowired private StringRedisTemplate stringRedisTemplate;
@Test void testString() { String key = "java-str-"; String value = "hello-word"; stringRedisTemplate.opsForValue().set(key, value); stringRedisTemplate.opsForValue().set(key+"error", value,3600); stringRedisTemplate.opsForValue().set(key+"normal", value,3600, TimeUnit.SECONDS); System.out.println("o"); } }
|
存储结果