Spring Data Redis
Kiml Lv5
  • 前言

    Spring Data Redis 是 Spring 框架提供的用于操作 Redis 的方式。涵盖 Redis 的安装、Spring Cache 结合 Redis 的使用、Redis 连接池的使用和 RedisTemplate 的使用等内容。

  • 参考文章
    Spring Data Redis 最佳实践!

  • 更新

1
23.08.29 初始记录

Redis 安装

Linux

  • 下载 Redis5.0 的 Docker 镜像

1
docker pull redis:5.0
  • 使用 Docker 命令启动 Redis 容器

1
2
3
docker run -p 6379:6379 --name redis \
-v /mydata/redis/data:/data \
-d redis:5.0 redis-server --appendonly yes

Windows

  • 下载 Windows 版本的 Redis
    image

  • 下载后直接解压

  • 在当前地址栏输入 cmd 命令,执行对应的 redis 启动命令

1
2
3
4
5
6
7
8
# 注册服务
redis-server --service-install redis.windows.conf
# 启动服务
redis-server --service-start
# 停止服务
redis-server --service-stop
# 删除服务
redis-server --service-uninstall

Spring Cache 操作 Redis

Spring Cache 简介

当 Spring Boot 结合 Redis 来作为缓存使用时,最简单的方式就是使用 Spring Cache 了,使用它我们无需知道 Spring 中对 Redis 的各种操作,仅仅通过它提供的@Cacheable 、@CachePut 、@CacheEvict 、@EnableCaching 等注解就可以实现缓存功能。

常用注解

@EnableCaching

开启缓存功能,一般放在启动类上。

@Cacheable

使用该注解的方法当缓存存在时,会从缓存中获取数据而不执行方法,当缓存不存在时,会执行方法并把返回结果存入缓存中。一般使用在查询方法上,可以设置如下属性:

  • value:缓存名称(必填),指定缓存的命名空间;

  • key:用于设置在命名空间中的缓存 key 值,可以使用 SpEL 表达式定义;

  • unless:条件符合则不缓存;

  • condition:条件符合则缓存。

@CachePut

使用该注解的方法每次执行时都会把返回结果存入缓存中。一般使用在新增方法上,可以设置如下属性:

  • value:缓存名称(必填),指定缓存的命名空间;

  • key:用于设置在命名空间中的缓存 key 值,可以使用 SpEL 表达式定义;

  • unless:条件符合则不缓存;

  • condition:条件符合则缓存。

@CacheEvict

使用该注解的方法执行时会清空指定的缓存。一般使用在更新或删除方法上,可以设置如下属性:

  • value:缓存名称(必填),指定缓存的命名空间;

  • key:用于设置在命名空间中的缓存 key 值,可以使用 SpEL 表达式定义;

  • condition:条件符合则缓存。

使用步骤

基础步骤

  • 在 pom.xml 中添加项目依赖:

1
2
3
4
5
<!--redis依赖配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 修改配置文件 application.yml,添加 Redis 的连接配置

1
2
3
4
5
6
7
spring:
redis:
host: 192.168.6.139 # Redis服务器地址
database: 0 # Redis数据库索引(默认为0)
port: 6379 # Redis服务器连接端口
password: # Redis服务器连接密码(默认为空)
timeout: 1000ms # 连接超时时间
  • 在启动类上添加@EnableCaching 注解启动缓存功能

1
2
3
4
5
6
7
@EnableCaching
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
  • 类中实现缓存功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* PmsBrandService实现类
* Created by macro on 2019/4/19.
*/
@Service
public class PmsBrandServiceImpl implements PmsBrandService {
@Autowired
private PmsBrandMapper brandMapper;

@CacheEvict(value = RedisConfig.REDIS_KEY_DATABASE, key = "'pms:brand:'+#id")
@Override
public int update(Long id, PmsBrand brand) {
brand.setId(id);
return brandMapper.updateByPrimaryKeySelective(brand);
}

@CacheEvict(value = RedisConfig.REDIS_KEY_DATABASE, key = "'pms:brand:'+#id")
@Override
public int delete(Long id) {
return brandMapper.deleteByPrimaryKey(id);
}

@Cacheable(value = RedisConfig.REDIS_KEY_DATABASE, key = "'pms:brand:'+#id", unless = "#result==null")
@Override
public PmsBrand getItem(Long id) {
return brandMapper.selectByPrimaryKey(id);
}

}

存储 JSON 格式数据

给 RedisTemplate 设置 JSON 格式的序列化器,并通过配置 RedisCacheConfiguration 设置超时时间来实现以上需求,此时还需要去除启动类上的@EnableCaching 注解,具体配置类 RedisConfig 代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* Redis配置类
* Created by macro on 2020/3/2.
*/
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

/**
* redis数据库自定义key
*/
public static final String REDIS_KEY_DATABASE="mall";

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisSerializer<Object> serializer = redisSerializer();
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(serializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}

@Bean
public RedisSerializer<Object> redisSerializer() {
//创建JSON序列化器
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(objectMapper);
return serializer;
}

@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
//设置Redis缓存有效期为1天
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer())).entryTtl(Duration.ofDays(1));
return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
}
}

使用 Redis 连接池

SpringBoot 1.5.x 版本 Redis 客户端默认是 Jedis 实现的,SpringBoot 2.x 版本中默认客户端是用 Lettuce 实现的,我们先来了解下 Jedis 和 Lettuce 客户端。

Jedis vs Lettuce

Jedis 在实现上是直连 Redis 服务,多线程环境下非线程安全,除非使用连接池,为每个 RedisConnection 实例增加物理连接。

Lettuce 是一种可伸缩,线程安全,完全非阻塞的 Redis 客户端,多个线程可以共享一个 RedisConnection,它利用 Netty NIO 框架来高效地管理多个连接,从而提供了异步和同步数据访问方式,用于构建非阻塞的反应性应用程序。

使用步骤
  • 修改 application.yml 添加 Lettuce 连接池配置,用于配置线程数量和阻塞等待时间

1
2
3
4
5
6
7
8
spring:
redis:
lettuce:
pool:
max-active: 8 # 连接池最大连接数
max-idle: 8 # 连接池最大空闲连接数
min-idle: 0 # 连接池最小空闲连接数
max-wait: -1ms # 连接池最大阻塞等待时间,负值表示没有限制
  • 由于 SpringBoot 2.x 中默认并没有使用 Redis 连接池,所以需要在 pom.xml 中添加 commons-pool2 的依赖

1
2
3
4
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
  • 不添加依赖则报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Caused by: java.lang.NoClassDefFoundError: org/apache/commons/pool2/impl/GenericObjectPoolConfig
at org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration$LettucePoolingClientConfiguYrRUIQWOPrationBuilder.<init>(LettucePoolingClientConfiguration.java:84) ~[spring-data-redis-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration.builder(LettucePoolingClientConfiguration.java:48) ~[spring-data-redis-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration$PoolBuilderFactory.createBuilder(LettuceConnectionConfiguration.java:149) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
at org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration.createBuilder(LettuceConnectionConfiguration.java:107) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
at org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration.getLettuceClientConfiguration(LettuceConnectionConfiguration.java:93) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
at org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration.redisConnectionFactory(LettuceConnectionConfiguration.java:74) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
at org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration$$EnhancerBySpringCGLIB$$5caa7e47.CGLIB$redisConnectionFactory$0(<generated>) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
at org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration$$EnhancerBySpringCGLIB$$5caa7e47$$FastClassBySpringCGLIB$$b8ae2813.invoke(<generated>) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363) ~[spring-context-5.1.5.RELEASE.jar:5.1.5.RELEASE]
at org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration$$EnhancerBySpringCGLIB$$5caa7e47.redisConnectionFactory(<generated>) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_91]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_91]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_91]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_91]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.1.5.RELEASE.jar:5.1.5.RELEASE]
... 111 common frames omitted

自由操作 Redis

使用 RedisTemplate 自由缓存方法中产生的中间值

RedisService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/**
* redis操作Service
* Created by macro on 2020/3/3.
*/
public interface RedisService {

/**
* 保存属性
*/
void set(String key, Object value, long time);

/**
* 保存属性
*/
void set(String key, Object value);

/**
* 获取属性
*/
Object get(String key);

/**
* 删除属性
*/
Boolean del(String key);

/**
* 批量删除属性
*/
Long del(List<String> keys);

/**
* 设置过期时间
*/
Boolean expire(String key, long time);

/**
* 获取过期时间
*/
Long getExpire(String key);

/**
* 判断是否有该属性
*/
Boolean hasKey(String key);

/**
* 按delta递增
*/
Long incr(String key, long delta);

/**
* 按delta递减
*/
Long decr(String key, long delta);

/**
* 获取Hash结构中的属性
*/
Object hGet(String key, String hashKey);

/**
* 向Hash结构中放入一个属性
*/
Boolean hSet(String key, String hashKey, Object value, long time);

/**
* 向Hash结构中放入一个属性
*/
void hSet(String key, String hashKey, Object value);

/**
* 直接获取整个Hash结构
*/
Map<Object, Object> hGetAll(String key);

/**
* 直接设置整个Hash结构
*/
Boolean hSetAll(String key, Map<String, Object> map, long time);

/**
* 直接设置整个Hash结构
*/
void hSetAll(String key, Map<String, Object> map);

/**
* 删除Hash结构中的属性
*/
void hDel(String key, Object... hashKey);

/**
* 判断Hash结构中是否有该属性
*/
Boolean hHasKey(String key, String hashKey);

/**
* Hash结构中属性递增
*/
Long hIncr(String key, String hashKey, Long delta);

/**
* Hash结构中属性递减
*/
Long hDecr(String key, String hashKey, Long delta);

/**
* 获取Set结构
*/
Set<Object> sMembers(String key);

/**
* 向Set结构中添加属性
*/
Long sAdd(String key, Object... values);

/**
* 向Set结构中添加属性
*/
Long sAdd(String key, long time, Object... values);

/**
* 是否为Set中的属性
*/
Boolean sIsMember(String key, Object value);

/**
* 获取Set结构的长度
*/
Long sSize(String key);

/**
* 删除Set结构中的属性
*/
Long sRemove(String key, Object... values);

/**
* 获取List结构中的属性
*/
List<Object> lRange(String key, long start, long end);

/**
* 获取List结构的长度
*/
Long lSize(String key);

/**
* 根据索引获取List中的属性
*/
Object lIndex(String key, long index);

/**
* 向List结构中添加属性
*/
Long lPush(String key, Object value);

/**
* 向List结构中添加属性
*/
Long lPush(String key, Object value, long time);

/**
* 向List结构中批量添加属性
*/
Long lPushAll(String key, Object... values);

/**
* 向List结构中批量添加属性
*/
Long lPushAll(String key, Long time, Object... values);

/**
* 从List结构中移除属性
*/
Long lRemove(String key, long count, Object value);
}
RedisServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/**
* redis操作实现类
* Created by macro on 2020/3/3.
*/
@Service
public class RedisServiceImpl implements RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Override
public void set(String key, Object value, long time) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
}

@Override
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}

@Override
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}

@Override
public Boolean del(String key) {
return redisTemplate.delete(key);
}

@Override
public Long del(List<String> keys) {
return redisTemplate.delete(keys);
}

@Override
public Boolean expire(String key, long time) {
return redisTemplate.expire(key, time, TimeUnit.SECONDS);
}

@Override
public Long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}

@Override
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}

@Override
public Long incr(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}

@Override
public Long decr(String key, long delta) {
return redisTemplate.opsForValue().increment(key, -delta);
}

@Override
public Object hGet(String key, String hashKey) {
return redisTemplate.opsForHash().get(key, hashKey);
}

@Override
public Boolean hSet(String key, String hashKey, Object value, long time) {
redisTemplate.opsForHash().put(key, hashKey, value);
return expire(key, time);
}

@Override
public void hSet(String key, String hashKey, Object value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}

@Override
public Map<Object, Object> hGetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}

@Override
public Boolean hSetAll(String key, Map<String, Object> map, long time) {
redisTemplate.opsForHash().putAll(key, map);
return expire(key, time);
}

@Override
public void hSetAll(String key, Map<String, Object> map) {
redisTemplate.opsForHash().putAll(key, map);
}

@Override
public void hDel(String key, Object... hashKey) {
redisTemplate.opsForHash().delete(key, hashKey);
}

@Override
public Boolean hHasKey(String key, String hashKey) {
return redisTemplate.opsForHash().hasKey(key, hashKey);
}

@Override
public Long hIncr(String key, String hashKey, Long delta) {
return redisTemplate.opsForHash().increment(key, hashKey, delta);
}

@Override
public Long hDecr(String key, String hashKey, Long delta) {
return redisTemplate.opsForHash().increment(key, hashKey, -delta);
}

@Override
public Set<Object> sMembers(String key) {
return redisTemplate.opsForSet().members(key);
}

@Override
public Long sAdd(String key, Object... values) {
return redisTemplate.opsForSet().add(key, values);
}

@Override
public Long sAdd(String key, long time, Object... values) {
Long count = redisTemplate.opsForSet().add(key, values);
expire(key, time);
return count;
}

@Override
public Boolean sIsMember(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}

@Override
public Long sSize(String key) {
return redisTemplate.opsForSet().size(key);
}

@Override
public Long sRemove(String key, Object... values) {
return redisTemplate.opsForSet().remove(key, values);
}

@Override
public List<Object> lRange(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}

@Override
public Long lSize(String key) {
return redisTemplate.opsForList().size(key);
}

@Override
public Object lIndex(String key, long index) {
return redisTemplate.opsForList().index(key, index);
}

@Override
public Long lPush(String key, Object value) {
return redisTemplate.opsForList().rightPush(key, value);
}

@Override
public Long lPush(String key, Object value, long time) {
Long index = redisTemplate.opsForList().rightPush(key, value);
expire(key, time);
return index;
}

@Override
public Long lPushAll(String key, Object... values) {
return redisTemplate.opsForList().rightPushAll(key, values);
}

@Override
public Long lPushAll(String key, Long time, Object... values) {
Long count = redisTemplate.opsForList().rightPushAll(key, values);
expire(key, time);
return count;
}

@Override
public Long lRemove(String key, long count, Object value) {
return redisTemplate.opsForList().remove(key, count, value);
}
}
RedisController

简单测试在 Controller 中进行操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* Redis测试Controller
* Created by macro on 2020/3/3.
*/
@Api(tags = "RedisController", description = "Redis测试")
@Controller
@RequestMapping("/redis")
public class RedisController {
@Autowired
private RedisService redisService;
@Autowired
private PmsBrandService brandService;

@ApiOperation("测试简单缓存")
@RequestMapping(value = "/simpleTest", method = RequestMethod.GET)
@ResponseBody
public CommonResult<PmsBrand> simpleTest() {
List<PmsBrand> brandList = brandService.list(1, 5);
PmsBrand brand = brandList.get(0);
String key = "redis:simple:" + brand.getId();
redisService.set(key, brand);
PmsBrand cacheBrand = (PmsBrand) redisService.get(key);
return CommonResult.success(cacheBrand);
}

@ApiOperation("测试Hash结构的缓存")
@RequestMapping(value = "/hashTest", method = RequestMethod.GET)
@ResponseBody
public CommonResult<PmsBrand> hashTest() {
List<PmsBrand> brandList = brandService.list(1, 5);
PmsBrand brand = brandList.get(0);
String key = "redis:hash:" + brand.getId();
Map<String, Object> value = BeanUtil.beanToMap(brand);
redisService.hSetAll(key, value);
Map<Object, Object> cacheValue = redisService.hGetAll(key);
PmsBrand cacheBrand = BeanUtil.mapToBean(cacheValue, PmsBrand.class, true);
return CommonResult.success(cacheBrand);
}

@ApiOperation("测试Set结构的缓存")
@RequestMapping(value = "/setTest", method = RequestMethod.GET)
@ResponseBody
public CommonResult<Set<Object>> setTest() {
List<PmsBrand> brandList = brandService.list(1, 5);
String key = "redis:set:all";
redisService.sAdd(key, (Object[]) ArrayUtil.toArray(brandList, PmsBrand.class));
redisService.sRemove(key, brandList.get(0));
Set<Object> cachedBrandList = redisService.sMembers(key);
return CommonResult.success(cachedBrandList);
}

@ApiOperation("测试List结构的缓存")
@RequestMapping(value = "/listTest", method = RequestMethod.GET)
@ResponseBody
public CommonResult<List<Object>> listTest() {
List<PmsBrand> brandList = brandService.list(1, 5);
String key = "redis:list:all";
redisService.lPushAll(key, (Object[]) ArrayUtil.toArray(brandList, PmsBrand.class));
redisService.lRemove(key, 1, brandList.get(0));
List<Object> cachedBrandList = redisService.lRange(key, 0, 3);
return CommonResult.success(cachedBrandList);
}
}
 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
访客数 访问量