Featured image of post Spring Boot 整合 Redis(基础篇)

Spring Boot 整合 Redis(基础篇)

Redis 在 Spring Boot 中的基本使用

Spring Boot 整合 Redis(基础篇)

1. 有关依赖的添加

1. 添加 Redis 依赖

1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2. 连接器依赖

Spring Data Redis在org.springframework.data.redis.connection包中提供了接口 RedisConnection 和 RedisConnectionFactory,用于连接 Redis 服务器。Spring Data Redis 提供了多种连接器,如 Jedis、Lettuce、Redis 等。

Spring 默认使用 Lettuce 作为连接器,如果想使用 Jedis 作为连接器,需要添加 Jedis 依赖。

2.1. 添加 Jedis 依赖

1
2
3
4
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

注意:Spring Boot 2.0 之后默认使用 Lettuce 作为连接器,如果想使用 Jedis 作为连接器,需要排除 Lettuce 依赖。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.lettuce.core</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

2.2. 添加 Lettuce 依赖

1
2
3
4
<dependency>
    <groupId>io.lettuce.core</groupId>
    <artifactId>lettuce-core</artifactId>
</dependency>

2. 配置 Redis

1. application.properties 配置

1
2
3
4
5
6
7
# application.properties
spring.data.redis.host=127.0.0.1
spring.data.redis.port=6379
spring.data.redis.password=${REDIS_PASSWORD:} # 环境变量优先
spring.data.redis.database=0
spring.data.redis.lettuce.pool.max-active=8   # 连接池配置
spring.data.redis.timeout=5000               # 超时配置

3. RedisTemplate 与数据操作类的使用

3.1 数据操作类的使用

使用Spring 容器注入 RedisTemplate 对象,通过 RedisTemplate 对象操作 Redis 数据。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package cn.programcx.springbootinit.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.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

@Configuration
public class Config {
    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(new RedisStandaloneConfiguration("localhost", 6379));
    }

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }
}

然后在使用时,通过 RedisTemplate 对象操作 Redis 数据。

3.2 序列化器的使用

StringRedisTemplate 继承 RedisTemplate,并指定了序列化器为StringRedisSerializer以及编码集为UTF-8。这样一来,使用StringRedisTemplate对象操作 Redis 数据时,数据会以字符串形式存储,而不是以字节数组形式存储,不需要翻译。

上面创建RedisTemplate对象时,没有指定序列化器,那么Spring会使用默认的序列化器,即JdkSerializationRedisSerializer。这样一来,文本前面会出现“\xac\xed\x00\x05t\x00\”这样的字符,不方便阅读。

解决这个问题有两种方法:

  1. 通过编码为RedisTemplate对象指定序列化器。 示例:
1
2
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
  1. 注入使用SpringRedisTemplate,首先在容器中注入StringRedisTemplate对象,然后使用StringRedisTemplate对象操作 Redis 数据。
1
2
3
4
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory){
    return new StringRedisTemplate(redisConnectionFactory);
}

在注入ValueOperations的位置指定stringRedisTemplate对象,然后使用stringRedisTemplate对象操作 Redis 数据:

1
2
@Resource(name = "stringRedisTemplate")
private ValueOperations valueOperations;

补充: 如果我们要定义字符串、列表、集合、散列、有序集合等数据类型的操作。

我们可以使用GenericJackson2JsonRedisSerializer作为序列化器,这样我们可以直接存储对象。 在此之前,我们要添加依赖:

1
2
3
4
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

配置类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);
    
    GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
    
    template.setKeySerializer(new StringRedisSerializer());
    template.setValueSerializer(serializer);
    template.setHashKeySerializer(new StringRedisSerializer()); // Hash key序列化
    template.setHashValueSerializer(serializer);
    
    return template;
}

3.3 RedisCallback、SessionCallback 接口和 Redis 事务的使用

RedisCallback 接口和 SessionCallback 接口是 Spring Data Redis 提供的两个回调接口,用于执行 Redis 命令。 RedisCallback 接口用于执行 Redis 命令,SessionCallback 接口用于执行 Redis 事务。

RedisCallback 接口代码如下:

1
2
3
public interface RedisCallback<T> {
    T doInRedis(RedisConnection connection) throws DataAccessException;
}

比如,我们要获取Redis查询的结果,Redis查询完成后我们怎么获取结果呢,这时候就需要用到 RedisCallback 接口。

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Autowired
    StringRedisTemplate stringRedisTemplate;
    @Test
    void testRedisCallback() {
        String value = stringRedisTemplate.execute( (RedisCallback<String>)(connection) ->{
            byte[] valueBytes = connection.get("redis-template".getBytes());
            try {
                return new String(valueBytes, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }
        });
        assert value.equals("Hello, World!");
    }

SessionCallback 主要用于对 Redis 事务的支持。

SessionCallback 接口代码如下:

1
<K, V> T execute(RedisOperations<K, V> operations) throws DataAccessException;

在此之前,我们需要开启 Redis 事务支持,在 RedisTemplate 对象中设置开启事务支持:

1
2
3
4
5
6
7
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
    RedisTemplate redisTemplate = new RedisTemplate();
    redisTemplate.setConnectionFactory(redisConnectionFactory);
    redisTemplate.setEnableTransactionSupport(true);
    return redisTemplate;
}

写: 示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    @Test
    void testSessionCallback() {
        List<Object> results = stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
            @Override
            public  List<Object> execute(RedisOperations operations) throws DataAccessException {
                operations.multi();
                operations.opsForValue().set("key1", "value1");
                operations.opsForValue().set("key2", "value2");
                return operations.exec();
            }
        });
    }

读: 示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@Test
    void readSessionCallback(){
        List<Object> result = stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
            @Override
            public List<Object> execute(RedisOperations operations) throws DataAccessException {
                operations.multi();
                operations.opsForValue().get("key1");
                operations.opsForValue().get("key2");
                return operations.exec();
            }
        });
        for(Object o: result){
            System.out.println(o);
        }
    }

3.4 RedisTemplate 的使用

RedisTemplate 是 Spring Data Redis 提供的核心类,用于操作 Redis 数据。RedisTemplate 提供了多种数据操作方法,如操作字符串、列表、集合、散列、有序集合等。

RedisTemplate 提供了多种数据操作方法,如:

  • opsForValue():操作字符串类型的数据。

  • opsForList():操作列表类型的数据。

  • opsForSet():操作集合类型的数据。

  • opsForZSet():操作有序集合类型的数据。

  • opsForHash():操作散列类型的数据。

  • execute():执行 Redis 命令。

  • delete():删除 Redis 数据。

  • expire():设置 Redis 数据的过期时间。

如何使用呢?

比如,我们要插入一个列表数据,可以使用opsForList()方法:

1
2
3
stringRedisTemplate.opsForList().leftPush("list", "a");
stringRedisTemplate.opsForList().leftPush("list", "b");
stringRedisTemplate.opsForList().leftPush("list", "c");

然后我们可以使用opsForList()方法获取列表数据:

1
2
3
4
List<String> list = stringRedisTemplate.opsForList().range("list", 0, -1);
for(String s: list){
    System.out.println(s);
}

如果我们要插入一个字符串数据,可以使用opsForValue()方法:

1
2

stringRedisTemplate.opsForValue().set("key", "value");

然后我们可以使用opsForValue()方法获取字符串数据:

1
2
3
4

String value = stringRedisTemplate.opsForValue().get("key");

System.out.println(value);

4. 使用 Spring 缓存注解操作 Redis

4.1 启用缓存和配置缓存管理器

Spring Boot 提供了 @EnableCaching 注解,用于启用缓存功能。在Spring Boot 程序入口处添加 @EnableCaching 注解,启用缓存功能。

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableAsync
@EnableCaching
public class SpringBootInitApplication {
    public static void main(String[] args) {
       SpringApplication.run(SpringBootInitApplication.class, args);
    }
}

4.2 使用缓存注解开发

Spring Boot 提供了多个缓存注解,如 @Cacheable、@CachePut、@CacheEvict、@Caching 和 @CacheConfig。

  • @Cacheable:缓存方法的返回结果。
  • @CachePut:将方法的返回结果放入缓存。
  • @CacheEvict:删除缓存。
  • @Caching:组合多个缓存操作。

4.2.1 配置缓存注解序列化器

Spring Boot 默认使用 JdkSerializationRedisSerializer 作为序列化器,会导致缓存的值前面出现“\xac\xed\x00\x05t\x00\”这样的字符,不方便阅读。所以我们要指定 StringRedisSerializer 作为序列化器。

在配置类中添加以下代码:

1
2
3
4
5
6
7
8
9
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .disableCachingNullValues();

        return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(redisCacheConfiguration).build();
    }

4.2.2 缓存注解的使用

  1. @Cacheable:缓存方法的返回结果。

新建一个 Service类型的类,添加 @Cacheable 注解,指定缓存的名称和缓存的 key。

为什么要在 Service 类里面使用注解?

  1. Service 类是由Spring Bean容器管理的,Redis 缓存注解的实现是依赖AOP的,所以需要在由Spring Bean容器管理的类中使用。否则缓存注解不生效

  2. 在 Service 类中使用缓存注解,可以将缓存的逻辑和业务逻辑分离,提高代码的可读性和可维护性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package cn.programcx.springbootinit.services;
;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class CacheServices {


    @Cacheable(value = "bookname",key = "#id")
    public String findBookNameById(int id) {
        return "RedisCookbook";
    }
}

然后我们在测试类中调用这个方法,第一次调用会执行方法,第二次调用会从缓存中获取结果。

1
2
3
4
5
6
7
    @Autowired
    private CacheServices cacheServices;

    @Test
    void testFindBookNameById(){
        System.out.println(cacheServices.findBookNameById(1));  //返回 RedisCookbook
    }

我们打开redis-cli查看键值存放:

1
keys *

我们可以在结果中找到:

1
13) "bookname::1"

输入以下命令查看键值对:

1
2
127.0.0.1:6379> get bookname::1
"RedisCookbook"
  1. @CachePut:将方法的返回结果放入缓存。
1
2
3
4
@CachePut(value = "bookname",key = "#id")
public String updateBookNameById(int id) {
    return "SpringBootCookbook";
}
  1. @CacheEvict:删除缓存。
1
2
3
4
@CacheEvict(value = "bookname",key = "#id")
public void deleteBookNameById(int id) {
    System.out.println("delete bookname by id");
}

5. Redis 全局异常处理

5.1. Redis 异常处理

在使用 Redis 过程中,可能会出现 Redis 连接异常、Redis 命令执行异常等问题。为了保证程序的正常运行,需要对 Redis 异常进行处理。

5.2. Redis 异常处理示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@ControllerAdvice
public class RedisExceptionHandler {
    
    @ExceptionHandler(RedisConnectionFailureException.class)
    public ResponseEntity<String> handleConnectionError(RedisConnectionFailureException ex) {
        return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
            .body("Redis服务不可用: " + ex.getMessage());
    }

    @ExceptionHandler(DataAccessException.class)
    public ResponseEntity<String> handleDataAccessException(DataAccessException ex) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body("数据访问异常: " + ex.getMessage());
    }
}
Licensed under CC BY-NC-SA 4.0