SpringBoot 结合 Redis 实现缓存

本文详细介绍了如何在SpringBoot项目中结合Redis实现缓存功能,包括Maven引入依赖、配置Redis参数、设置Spring缓存管理器、使用SpringCache注解以及通过示例项目展示具体操作。同时指出SpringCache的不足,并提供了手动操作Redis缓存的建议。

1、Mavne 引入相关依赖

  • spring-boot-starter-data-redis
  • commons-pool2
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

2、配置 Redis 参数

application 文件中添加连接 Redis 的配置参数

  • Redis 单机配置:
spring:
  redis:
    host: 127.0.0.1       #Redis 地址
    port: 6379            #Redis 端口号
    database: 0           #Redis 索引(0~15,默认为0)
    timeout: 1000         #Redis 连接的超时时间
    password: 123456      #Redis 密码,如果没有就默认不配置此参数
    lettuce:              #使用 lettuce 连接池
      pool:
        max-active: 20    #连接池最大连接数(使用负值表示没有限制)
        max-wait: -1      #连接池最大阻塞等待时间(使用负值表示没有限制)
        min-idle: 0       #连接池中的最大空闲连接
        max-idle: 10      #连接池中的最小空闲连接
  • Redis 哨兵配置:
spring:
  redis:
    sentinel:             #哨兵配置
      master: "my-master"
      nodes: "192.168.2.11:6379,192.168.2.12:6379,192.168.2.13:6379"
    database: 0           #Redis 索引(0~15,默认为0)
    timeout: 1000         #Redis 连接的超时时间
    password: 123456      #Redis 密码,如果没有就默认不配置此参数  
    lettuce:              #使用 lettuce 连接池
      pool:
        max-active: 20    #连接池最大连接数(使用负值表示没有限制)
        max-wait: -1      #连接池最大阻塞等待时间(使用负值表示没有限制)
        min-idle: 0       #连接池中的最大空闲连接
        max-idle: 10      #连接池中的最小空闲连接

  • Redis 集群配置:
spring:
  redis:
    cluster:              #redis 集群配置
      max-redirects: 5    #redis命令执行时最多转发次数
      nodes: "192.168.2.11:6379,192.168.2.12:6379,192.168.2.13:6379" 
    database: 0           #Redis 索引(0~15,默认为0)
    timeout: 1000         #Redis 连接的超时时间
    password: 123456      #Redis 密码,如果没有就默认不配置此参数  
    lettuce:              #使用 lettuce 连接池
      pool:
        max-active: 20    #连接池最大连接数(使用负值表示没有限制)
        max-wait: -1      #连接池最大阻塞等待时间(使用负值表示没有限制)
        min-idle: 0       #连接池中的最大空闲连接
        max-idle: 10      #连接池中的最小空闲连接

3、配置 Spring 缓存管理器

@Configuration
public class RedisConfig {

    /**
     * 配置缓存管理器
     * @param factory Redis 线程安全连接工厂
     * @return 缓存管理器
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        // 生成两套默认配置,通过 Config 对象即可对缓存进行自定义配置
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                // 设置过期时间 10 分钟
                .entryTtl(Duration.ofMinutes(10))
                // 设置缓存前缀
                .prefixKeysWith("cache:user:")
                // 禁止缓存 null 值
                .disableCachingNullValues()
                // 设置 key 序列化
                .serializeKeysWith(keyPair())
                // 设置 value 序列化
                .serializeValuesWith(valuePair());
        // 返回 Redis 缓存管理器
        return RedisCacheManager.builder(factory)
                            .withCacheConfiguration("user", cacheConfig).build();
    }

    /**
     * 配置键序列化
     * @return StringRedisSerializer
     */
    private RedisSerializationContext.SerializationPair<String> keyPair() {
        return RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer());
    }

    /**
     * 配置值序列化,使用 GenericJackson2JsonRedisSerializer 替换默认序列化
     * @return GenericJackson2JsonRedisSerializer
     */
    private RedisSerializationContext.SerializationPair<Object> valuePair() {
        return RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer());
    }

}

4、服务中使用 SpringCache 的注解

@Service
@CacheConfig(cacheNames = "user")
public class UserServiceImpl implements UserService {

    /**
     * 新增用户
     */
    public User addUser(User user) {
        ......
    }

    /**
     * 查询用户 
     */
    @Cacheable(key = "#username")
    public User getUserByUsername(String username) {
        ......
    }

    /**
     * 更新用户 
     */
    @CachePut(key = "#user.username")
    public User updateUser(User user) {
        ......
    }
    
    /**
     * 删除用户 
     */
    @CacheEvict(key = "#username")
    public void deleteByUsername(String username) {
        ......
    }

}

注解说明:

  • @CacheConfig: 一般配置在类上,指定缓存名称,这个名称是和上面“置缓存管理器”中缓存名称的一致。
  • @Cacheable: 作用于方法上,用于对于方法返回结果进行缓存,如果已经存在该缓存,则直接从缓存中获取,缓存的key可以从入参中指定,缓存的 value 为方法返回值。
  • @CachePut: 作用于方法上,无论是否存在该缓存,每次都会重新添加缓存,缓存的key可以从入参中指定,缓存的value为方法返回值,常用作于更新。
  • @CacheEvict: 作用于方法上,用于清除缓存
  • @Caching: 作用于方法上,用于一次性设置多个缓存。

上面注解中的常用配置参数:

  • value: 缓存管理器中配置的缓存的名称,这里可以理解为一个组的概念,缓存管理器中可以有多套缓存配置,每套都有一个名称,类似于组名,这个可以配置这个值,选择使用哪个缓存的名称,配置后就会应用那个缓存名称对应的配置。
  • key: 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合。
  • condition: 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存。
  • unless: 不缓存的条件,和 condition 一样,也是 SpEL 编写,返回 true 或者 false,为 true 时则不进行缓存。

5、启动类添加开启缓存注解

@EnableCaching
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}
  • @EnableCaching: 作用于类上,用于开启注解功能。

五、SpringCache 操作缓存的不足

使用 Spring Cache 虽然方便,但是也有很多局限性,因为它多是根据请求参数命名 key,根据返回指设置 value,这样很多情况下,我们想方法内部进行命名和操作有一定的限制。如果我们需要灵活设置缓存,可以不用 SpringCache 提供的注解,直接在代码中使用 Spring-data-redis 包提供的方法,手动操作 key 与 value。

  • opsForValue().set(String key, String value);
  • opsForValue().get(String key);
@Autowired
private RedisTemplate<String, Object> redisTemplate;

public void redisBatch(){
    // 设置值
    redisTemplate.opsForValue().set("key", "value");

    // 获取值
    redisTemplate.opsForValue().get("key");
}

还有经常要批量设置、读取缓存,可以使用:

  • opsForValue().multiSet(Map map);
  • opsForValue().multiGet(List list);
@Autowired
private RedisTemplate<String, Object> redisTemplate;

public void redisBatch(){
    // 批量设置值
    Map<String,Object> map = new HashMap<>();
    map.put("test1","value1");
    map.put("test2","value2");
    map.put("test3","value3");
    redisTemplate.opsForValue().multiSet(map);

    // 批量获取值,如果某个 key 不存在,则返回值集合中对于的为 null
    List<String> list = new ArrayList<>();
    list.add("test1");
    list.add("test2");
    list.add("test3");
    List<Object> valueList = redisTemplate.opsForValue().multiGet(list);
}

六、SpringBoot + SpringCache + Redis 示例项目

下面是一个简单的 SpringBoot 项目,用于对用户的增删改查,这里使用 SpringCache 来模拟对数据进行缓存,示例如下:

1、Mavne 引入相关依赖

Maven 中引入 SpringBoot 和 Redis 依赖,因为使用了

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
    </parent>

    <groupId>mydlq.club</groupId>
    <artifactId>springboot-redis-example</artifactId>
    <version>0.0.1</version>
    <name>springboot-redis-example</name>
    <description>Demo project for Spring Boot Redis</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <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>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2、配置连接 Redis 参数

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0      
    timeout: 1000      
    password:         
    lettuce:        
      pool:
        max-active: 20   
        max-wait: -1    
        min-idle: 0      
        max-idle: 10  

3、配置 Spring 缓存管理器

缓存配置类,里面配置缓存管理器,配置缓存的全局过期时间、序列化等参数。

import org.springframework.cache.CacheManager;
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.serializer.*;
import java.time.Duration;

/**
 * Redis 配置类
 */
@Configuration
public class RedisConfig {

    /**
     * 配置缓存管理器
     * @param factory Redis 线程安全连接工厂
     * @return 缓存管理器
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        // 生成两套默认配置,通过 Config 对象即可对缓存进行自定义配置
        RedisCacheConfiguration cacheConfig1 = RedisCacheConfiguration.defaultCacheConfig()
                // 设置过期时间 10 分钟
                .entryTtl(Duration.ofMinutes(10))
                // 设置缓存前缀
                .prefixKeysWith("cache:user:")
                // 禁止缓存 null 值
                .disableCachingNullValues()
                // 设置 key 序列化
                .serializeKeysWith(keyPair())
                // 设置 value 序列化
                .serializeValuesWith(valuePair());
        RedisCacheConfiguration cacheConfig2 = RedisCacheConfiguration.defaultCacheConfig()
                // 设置过期时间 30 秒
                .entryTtl(Duration.ofSeconds(30))
                .prefixKeysWith("cache:user_info:")
                .disableCachingNullValues()
                .serializeKeysWith(keyPair())
                .serializeValuesWith(valuePair());
        // 返回 Redis 缓存管理器
        return RedisCacheManager.builder(factory)
                .withCacheConfiguration("user", cacheConfig1)
                .withCacheConfiguration("userInfo", cacheConfig2)
                .build();
    }

    /**
     * 配置键序列化
     * @return StringRedisSerializer
     */
    private RedisSerializationContext.SerializationPair<String> keyPair() {
        return RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer());
    }

    /**
     * 配置值序列化,使用 GenericJackson2JsonRedisSerializer 替换默认序列化
     * @return GenericJackson2JsonRedisSerializer
     */
    private RedisSerializationContext.SerializationPair<Object> valuePair() {
        return RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer());
    }

}

4、定义实体类

用户实体类

User

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

/**
 * 用户实体
 */
@Data
public class User {
    private String username;
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private String password;
    private String role;
}

用户信息实体类

UserInfo

import lombok.Data;

/**
 * 用户信息实体
 */
@Data
public class UserInfo {
    private String name;
    private String sex;
    private Integer age;
}

5、定义服务接口

UserService

import mydlq.club.example.entity.User;

/**
 * 用户业务接口
 */
public interface UserService {

    /**
     * 增加账户
     *
     * @param user 账户
     */
    void addUser(User user);

    /**
     * 获取账户
     *
     * @param username 用户名
     * @return 用户信息
     */
    User getUserByUsername(String username);

    /**
     * 修改账户
     *
     * @param user 用户信息
     * @return 用户信息
     */
    User updateUser(User user);

    /**
     * 删除账户
     * @param username 用户名
     */
    void deleteByUsername(String username);

}

UserInfoService

import mydlq.club.example.entity.UserInfo;

/**
 * 用户信息业务接口
 */
public interface UserInfoService {

    /**
     * 增加用户信息
     *
     * @param userInfo 用户信息
     */
    void addUserInfo(UserInfo userInfo);

    /**
     * 获取用户信息
     *
     * @param name 姓名
     * @return 用户信息
     */
    UserInfo getByName(String name);

    /**
     * 修改用户信息
     *
     * @param userInfo 用户信息
     * @return 用户信息
     */
    UserInfo updateUserInfo(UserInfo userInfo);

    /**
     * 删除用户信息
     * @param name 姓名
     */
    void deleteByName(String name);

}

6、实现服务类

实现 UserService 与 UserInfoService 接口中的方法,里面使用 @Cacheable@CachePut@CacheEvict 三个注解完成对用户与用户信息数据的缓存。

UserServiceImpl(用户业务实现类)

注意,为了演示方便,没有连接数据库,临时创建了个成员变量 userMap 来模拟数据库存储。

import mydlq.club.example.entity.User;
import mydlq.club.example.service.UserService;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.HashMap;

@Service
@CacheConfig(cacheNames = "user")
public class UserServiceImpl implements UserService {

    private HashMap<String, User> userMap = new HashMap<>();

    @Override
    public void addUser(User user) {
        userMap.put(user.getUsername(), user);
    }

    @Override
    @Cacheable(key = "#username",unless = "#result==null ")
    public User getUserByUsername(String username) {
        if (!userMap.containsKey(username)) {
            return null;
        }
        return userMap.get(username);
    }

    @Override
    @CachePut(key = "#user.username")
    public User updateUser(User user) {
        if (!userMap.containsKey(user.getUsername())){
            throw new RuntimeException("不存在该用户");
        }
        // 获取存储的对象
        User newUser = userMap.get(user.getUsername());
        // 复制要更新的数据到新对象,因为不能更改用户名信息,所以忽略
        BeanUtils.copyProperties(user, newUser, "username");
        // 将新的对象存储,更新旧对象信息
        userMap.put(newUser.getUsername(), newUser);
        // 返回新对象信息
        return newUser;
    }

    @Override
    @CacheEvict(key = "#username")
    public void deleteByUsername(String username) {
        userMap.remove(username);
    }

}

UserInfoServiceImpl(用户信息业务实现)

注意,为了演示方便,没有连接数据库,临时创建了个成员变量 userInfoMap 来模拟数据库存储。

import mydlq.club.example.entity.UserInfo;
import mydlq.club.example.service.UserInfoService;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.HashMap;

@Service
@CacheConfig(cacheNames = "userInfo")
public class UserInfoServiceImpl implements UserInfoService {

    private HashMap<String, UserInfo> userInfoMap = new HashMap<>();

    @Override
    public void addUserInfo(UserInfo userInfo) {
        userInfoMap.put(userInfo.getName(), userInfo);
    }

    @Override
    @Cacheable(key = "#name", unless = "#result==null")
    public UserInfo getByName(String name) {
        if (!userInfoMap.containsKey(name)) {
            return null;
        }
        return userInfoMap.get(name);
    }

    @Override
    @CachePut(key = "#userInfo.name")
    public UserInfo updateUserInfo(UserInfo userInfo) {
        if (!userInfoMap.containsKey(userInfo.getName())) {
            throw new RuntimeException("该用户信息没有找到");
        }
        // 获取存储的对象
        UserInfo newUserInfo = userInfoMap.get(userInfo.getName());
        // 复制要更新的数据到新对象,因为不能更改用户名信息,所以忽略
        BeanUtils.copyProperties(userInfo, newUserInfo, "name");
        // 将新的对象存储,更新旧对象信息
        userInfoMap.put(newUserInfo.getName(), newUserInfo);
        // 返回新对象信息
        return newUserInfo;
    }

    @Override
    @CacheEvict(key = "#name")
    public void deleteByName(String name) {
        userInfoMap.remove(name);
    }

}

7、创建 Controller

UserController

import mydlq.club.example.entity.User;
import mydlq.club.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 用户 Controller
 */
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/user/{username}")
    public User getUser(@PathVariable String username) {
        return userService.getUserByUsername(username);
    }

    @PostMapping("/user")
    public String createUser(@RequestBody User user) {
        userService.addUser(user);
        return "SUCCESS";
    }

    @PutMapping("/user")
    public User updateUser(@RequestBody User user) {
        return userService.updateUser(user);
    }

    @DeleteMapping("/user/{username}")
    public String deleteUser(@PathVariable String username) {
        userService.deleteByUsername(username);
        return "SUCCESS";
    }

}

UserInfoController

import mydlq.club.example.entity.UserInfo;
import mydlq.club.example.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 用户信息 Controller
 */
@RestController
public class UserInfoController {

    @Autowired
    private UserInfoService userInfoService;

    @GetMapping("/userInfo/{name}")
    public UserInfo getUserInfo(@PathVariable String name) {
        return userInfoService.getByName(name);
    }

    @PostMapping("/userInfo")
    public String createUserInfo(@RequestBody UserInfo userInfo) {
        userInfoService.addUserInfo(userInfo);
        return "SUCCESS";
    }

    @PutMapping("/userInfo")
    public UserInfo updateUserInfo(@RequestBody UserInfo userInfo) {
        return userInfoService.updateUserInfo(userInfo);
    }

    @DeleteMapping("/userInfo/{name}")
    public String deleteUserInfo(@PathVariable String name) {
        userInfoService.deleteByName(name);
        return "SUCCESS";
    }

}

8、启动类

启动类中添加 @EnableCaching 注解开启缓存。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@EnableCaching
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值