Redis是最常用的KV数据库,Spring 通过模板方式(RedisTemplate)提供了对Redis的数据查询和操作功能。本文主要介绍基于RedisTemplate + Jedis方式对Redis进行查询和操作的案例。
Jedis是Redis的Java客户端,在SpringBoot 1.x版本中也是默认的客户端。在SpringBoot 2.x版本中默认客户端是Luttuce。
Spring中的Template和RedisTemplate
Spring 通过模板方式(RedisTemplate)提供了对Redis的数据查询和操作功能。
什么是模板模式?
模板方法模式(Template pattern): 在一个方法中定义一个算法的骨架, 而将一些步骤延迟到子类中. 模板方法使得子类可以在不改变算法结构的情况下, 重新定义算法中的某些步骤。
RedisTemplate对于Redis5种基础类型的操作?
redisTemplate.opsForValue(); // 操作字符串
redisTemplate.opsForHash(); // 操作hash
redisTemplate.opsForList(); // 操作list
redisTemplate.opsForSet(); // 操作set
redisTemplate.opsForZSet(); // 操作zset
对HyperLogLogs(基数统计)类型的操作?
redisTemplate.opsForHyperLogLog();
对geospatial (地理位置)类型的操作?
redisTemplate.opsForGeo();
对于BitMap的操作?也是在opsForValue()方法返回类型ValueOperations中
Boolean setBit(K key, long offset, boolean value);
Boolean getBit(K key, long offset);
对于Stream的操作?
redisTemplate.opsForStream();
什么是Lettuce?
Lettuce 是一个可伸缩线程安全的 Redis 客户端。多个线程可以共享同一个 RedisConnection。它利用优秀 netty NIO 框架来高效地管理多个连接。
Lettuce的特性:
- 支持 同步、异步、响应式 的方式
- 支持 Redis Sentinel
- 支持 Redis Cluster
- 支持 SSL 和 Unix Domain Socket 连接
- 支持 Streaming API 支持 CDI 和 Spring 的集成
- 支持 Command Interfaces
- 兼容 Java 8+ 以上版本
为何SpringBoot2.x中Lettuce会成为默认的客户端?
除了上述特性的支持性之外,最为重要的是Lettuce中使用了Netty框架,使其具备线程共享和异步的支持性。
- 线程共享
Jedis 是直连模式,在多个线程间共享一个 Jedis 实例时是线程不安全的,如果想要在多线程环境下使用 Jedis,需要使用连接池,每个线程都去拿自己的 Jedis 实例,当连接数量增多时,物理连接成本就较高了。
Lettuce 是基于 netty 的,连接实例可以在多个线程间共享,所以,一个多线程的应用可以使用一个连接实例,而不用担心并发线程的数量。 - 异步和反应式
-Lettuce 从一开始就按照非阻塞式 IO 进行设计,是一个纯异步客户端,对异步和反应式 API 的支持都很全面。
即使是同步命令,底层的通信过程仍然是异步模型,只是通过阻塞调用线程来模拟出同步效果而已。
PS: Jedis和Lettuce的对比如下,
Lettuce的基本的API方式
依赖POM包
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>x.y.z.BUILD-SNAPSHOT</version>
</dependency>
基础用法
RedisClient client = RedisClient.create("redis://localhost");
StatefulRedisConnection<String, String> connection = client.connect();
RedisStringCommands sync = connection.sync();
String value = sync.get("key");
异步方式
StatefulRedisConnection<String, String> connection = client.connect();
RedisStringAsyncCommands<String, String> async = connection.async();
RedisFuture<String> set = async.set("key", "value")
RedisFuture<String> get = async.get("key")
async.awaitAll(set, get) == true
set.get() == "OK"
get.get() == "value"
响应式
StatefulRedisConnection<String, String> connection = client.connect();
RedisStringReactiveCommands<String, String> reactive = connection.reactive();
Mono<String> set = reactive.set("key", "value");
Mono<String> get = reactive.get("key");
set.subscribe();
get.block() == "value"
实现案例
包依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.9.0</version>
</dependency>
yml配置
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
password: test
lettuce:
pool:
min-idle: 0
max-active: 8
max-idle: 8
max-wait: -1ms
connect-timeout: 30000ms
RedisConfig配置
通过@Bean的方式配置RedisTemplate,主要是设置RedisConnectionFactory以及各种类型数据的Serializer。
package tech.pdai.springboot.redis.lettuce.config;
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis configuration.
*
* @author pdai
*/
@Configuration
public class RedisConfig {
/**
* redis template.
*
* @param factory factory
* @return RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
或
@Configuration
public class RedisConfiguration {
@Bean
public RedisTemplate<String, Serializable> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//建立一个Redis模型, 用来调用方法,设置属性
RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);//设置Redis连接的工厂类
redisTemplate.setKeySerializer(RedisSerializer.string());//设置key的值
redisTemplate.setValueSerializer(RedisSerializer.json());//设置value的值
//设置完之后传进去的参数不管是啥, 都会给你转换为String,json,然后存进去.
return redisTemplate;
}
}
RedisService封装
RedisService接口类
package tech.pdai.springboot.redis.lettuce.enclosure.service;
import org.springframework.data.redis.core.RedisCallback;
import java.util.Collection;
import java.util.Set;
/**
* Redis Service.
*
* @author pdai
*/
public interface IRedisService<T> {
void set(String key, T value);
void set(String key, T value, long time);
T get(String key);
void delete(String key);
void delete(Collection<String> keys);
boolean expire(String key, long time);
Long getExpire(String key);
boolean hasKey(String key);
Long increment(String key, long delta);
Long decrement(String key, long delta);
void addSet(String key, T value);
Set<T> getSet(String key);
void deleteSet(String key, T value);
T execute(RedisCallback<T> redisCallback);
}
RedisService的实现类
package tech.pdai.springboot.redis.lettuce.enclosure.service.impl;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import tech.pdai.springboot.redis.lettuce.enclosure.service.IRedisService;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @author pdai
*/
@Service
public class RedisServiceImpl<T> implements IRedisService<T> {
@Resource
private RedisTemplate<String, T> redisTemplate;
@Override
public void set(String key, T value, long time) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
}
@Override
public void set(String key, T value) {
redisTemplate.opsForValue().set(key, value);
}
@Override
public T get(String key) {
return redisTemplate.opsForValue().get(key);
}
@Override
public void delete(String key) {
redisTemplate.delete(key);
}
@Override
public void delete(Collection<String> keys) {
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 increment(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
@Override
public Long decrement(String key, long delta) {
return redisTemplate.opsForValue().increment(key, -delta);
}
@Override
public void addSet(String key, T value) {
redisTemplate.opsForSet().add(key, value);
}
@Override
public Set<T> getSet(String key) {
return redisTemplate.opsForSet().members(key);
}
@Override
public void deleteSet(String key, T value) {
redisTemplate.opsForSet().remove(key, value);
}
@Override
public T execute(RedisCallback<T> redisCallback) {
return redisTemplate.execute(redisCallback);
}
}
RedisService的调用
UserController类中调用RedisService
package tech.pdai.springboot.redis.lettuce.enclosure.controller;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.pdai.springboot.redis.lettuce.enclosure.entity.User;
import tech.pdai.springboot.redis.lettuce.enclosure.entity.response.ResponseResult;
import tech.pdai.springboot.redis.lettuce.enclosure.service.IRedisService;
/**
* @author pdai
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IRedisService<User> redisService;
/**
* @param user user param
* @return user
*/
@ApiOperation("Add")
@PostMapping("add")
public ResponseResult<User> add(User user) {
redisService.set(String.valueOf(user.getId()), user);
return ResponseResult.success(redisService.get(String.valueOf(user.getId())));
}
/**
* @return user list
*/
@ApiOperation("Find")
@GetMapping("find/{userId}")
public ResponseResult<User> edit(@PathVariable("userId") String userId) {
return ResponseResult.success(redisService.get(userId));
}
}
接下来,在测试的根包下创建RedisTests
来测试访问Redis中的数据:
@SpringBootTest
public class RedisTests {
@Autowired
RedisTemplate<String, Serializable> redisTemplate;
@Test
void testSetValue() {
redisTemplate.opsForValue()
.set("name", "liuguobin");
}
@Test
void testSetValueTTL() {
redisTemplate.opsForValue()
.set("name", "fanchuanqi", 60, TimeUnit.SECONDS);
}
@Test
void testSetObjectValue() {
CategoryDetailsVO category = new CategoryDetailsVO();
category.setId(65L);
category.setIsParent(1);
category.setDepth(1);
category.setName("水果");
redisTemplate.opsForValue()
.set("category", category);
//关于Key的使用,通常建议使用冒号区分多层次
//某个id(9527)对应的类别的Key:`categories:item:9527`
//类别列表的Key:`categories:list`或`categories`
}
@Test
void testGetValue() {
// 当key存在时,可获取到有效值
// 当key不存在时,获取到的结果将是null
Serializable name = redisTemplate.opsForValue()
.get("name");
System.out.println("get value --> " + name);
}
@Test
void testGetObjectValue() {
// 当key存在时,可获取到有效值
// 当key不存在时,获取到的结果将是null
Serializable serializable = redisTemplate.opsForValue()
.get("category");
System.out.println("get value --> " + serializable);
if (serializable != null) {
CategoryDetailsVO category = (CategoryDetailsVO) serializable;
System.out.println("get value --> " + category);
}
}
@Test
void testDeleteKey() {
// 删除key时,将返回“是否成功删除”
// 当key存在时,将返回true
// 当key不存在时,将返回false
Boolean result = redisTemplate.delete("name");
System.out.println("result --> " + result);
}
@Test
void testRightPushList() {
// 存入List时,需要redisTemplate.opsForList()得到针对List的操作器
// 通过rightPush()可以向Redis中的List追加数据
// 每次调用rightPush()时使用的key必须是同一个,才能把多个数据放到同一个List中
List<CategoryDetailsVO> list = new ArrayList<>();
for (int i = 1; i <= 5; i++) {
CategoryDetailsVO category = new CategoryDetailsVO();
category.setName("类别00" + i);
list.add(category);
}
String key = "categoryList";
for (CategoryDetailsVO category : list) {
redisTemplate.opsForList().rightPush(key, category);
}
}
@Test
void testListSize() {
// 获取List的长度,即List中的元素数量
String key = "categoryList";
Long size = redisTemplate.opsForList().size(key);
System.out.println("size --> " + size);
}
@Test
void testRange() {
// 调用opsForList()后再调用range(String key, long start, long end)方法取出List中的若干个数据,将得到List
// long start:起始下标(结果中将包含)
// long end:结束下标(结果中将包含),如果需要取至最后一个元素,可使用-1作为此参数值
String key = "categoryList";
List<Serializable> range = redisTemplate.opsForList().range(key, 0, -1);
for (Serializable serializable : range) {
System.out.println(serializable);
}
}
@Test
void testKeys() {
// 调用keys()方法可以找出匹配模式的所有key
// 在模式中,可以使用星号作为通配符
Set<String> keys = redisTemplate.keys("*");
for (String key : keys) {
System.out.println(key);
}
}
}
本文由 liyunfei 创作,采用 知识共享署名4.0
国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Jun 29,2022