SpringBoot集成Redis - 基于RedisTemplate+Luttuce的数据操作
in JavaDevelop with 0 comment

SpringBoot集成Redis - 基于RedisTemplate+Luttuce的数据操作

in JavaDevelop with 0 comment

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): 在一个方法中定义一个算法的骨架, 而将一些步骤延迟到子类中. 模板方法使得子类可以在不改变算法结构的情况下, 重新定义算法中的某些步骤。

image-1655190973539

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的特性:

为何SpringBoot2.x中Lettuce会成为默认的客户端?

除了上述特性的支持性之外,最为重要的是Lettuce中使用了Netty框架,使其具备线程共享和异步的支持性。

PS: Jedis和Lettuce的对比如下,

image-1655191799071

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);
        }
    }

}