java每日精进 5.25【Redis缓存】

1.Redis 缓存实现

1.1 编程式缓存(Spring Data Redis)

编程式缓存使用 Spring Data Redis 框架,通过 RedisTemplate 操作 Redis,结合 Redisson 客户端实现。本例以缓存 OAuth2 访问令牌(OAuth2AccessTokenDO)为例。

步骤 1:添加依赖

项目引入了 Redisson 的 Spring Boot Starter 依赖,以支持 Redis 操作。

java 复制代码
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
  • 解释:Redisson 是一个功能强大的 Redis 客户端,支持分布式锁、队列、限流等功能。引入此依赖后,Spring Boot 可以通过 Redisson 连接 Redis,并使用 RedisTemplate 进行编程式缓存操作。
步骤 2:配置 Redis 连接

在 application-local.yaml 文件中,通过 spring.redis 配置项设置 Redis 连接参数。

java 复制代码
redis:
  host: 127.0.0.1 # Redis 服务器地址
  port: 6379      # Redis 端口
  database: 0     # Redis 数据库索引
# password: dev   # 密码(生产环境建议启用)
  • 解释:此配置指定了 Redis 服务器的地址、端口和数据库索引。yudao 项目指出 Redisson 的默认配置通常无需额外调优,适用于大多数场景。
步骤 3:配置 RedisTemplate(JSON 序列化)

在 YudaoRedisAutoConfiguration 类中,项目自定义了 RedisTemplate,使用 JSON 序列化存储复杂对象的值。

java 复制代码
@AutoConfiguration(before = RedissonAutoConfiguration.class)
public class YudaoRedisAutoConfiguration {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        // 创建 RedisTemplate
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        // 设置键序列化(String)
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());

        // 设置值序列化(JSON)
        template.setValueSerializer(buildRedisSerializer());
        template.setHashValueSerializer(buildRedisSerializer());
        return template;
    }

    public static RedisSerializer<?> buildRedisSerializer() {
        RedisSerializer<Object> json = RedisSerializer.json();
        // 支持 LocalDateTime 序列化
        ObjectMapper objectMapper = (ObjectMapper) ReflectUtil.getFieldValue(json, "mapper");
        objectMapper.registerModules(new JavaTimeModule());
        return json;
    }
}

解释

  • 目的:定义一个 RedisTemplate bean,用于 Redis 的键值操作。
  • 序列化
    • :使用 StringRedisSerializer,确保键以字符串形式存储(如 oauth2_access_token:token123)。
    • :使用 Jackson2JsonRedisSerializer(通过 RedisSerializer.json()),将复杂对象(如 OAuth2AccessTokenDO)序列化为 JSON。
    • LocalDateTime 支持:通过注册 JavaTimeModule,确保 Jackson 可以正确序列化/反序列化 Java 8 的日期时间类型(如 LocalDateTime)。
  • 优先级:@AutoConfiguration(before = RedissonAutoConfiguration.class) 确保自定义 RedisTemplate 在 Redisson 默认配置之前加载,以覆盖默认设置。
步骤 4:定义数据对象(OAuth2AccessTokenDO)

项目定义了一个数据对象 OAuth2AccessTokenDO,用于表示存储在 Redis 中的访问令牌结构。

java 复制代码
@TableName(value = "system_oauth2_access_token", autoResultMap = true)
@KeySequence("system_oauth2_access_token_seq")
@Data
@EqualsAndHashCode(callSuper = true)
public class OAuth2AccessTokenDO extends TenantBaseDO {

    @TableId
    private Long id; // 主键
    private String accessToken; // 访问令牌
    private String refreshToken; // 刷新令牌
    private Long userId; // 用户编号
    private Integer userType; // 用户类型
    @TableField(typeHandler = JacksonTypeHandler.class)
    private Map<String, String> userInfo; // 用户信息
    private String clientId; // 客户端编号
    @TableField(typeHandler = JacksonTypeHandler.class)
    private List<String> scopes; // 授权范围
    private LocalDateTime expiresTime; // 过期时间
}

解释

  • 作用:OAuth2AccessTokenDO 是一个数据对象(DO),用于映射数据库表 system_oauth2_access_token 和 Redis 中的缓存数据。
  • 注解
    • @TableName:指定 MyBatis-Plus 映射的表名。
    • @KeySequence:支持 Oracle、PostgreSQL 等数据库的主键自增(MySQL 可忽略)。
    • @TableField(typeHandler = JacksonTypeHandler.class):对复杂字段(如 userInfo 和 scopes)使用 Jackson 序列化,存储为 JSON。
  • 字段:包含访问令牌、用户编号、过期时间等信息,适合缓存复杂对象。
步骤 5:定义 Redis Key 常量(RedisKeyConstants)

项目通过 RedisKeyConstants 类集中管理 Redis 键,避免键名散落在代码中。

java 复制代码
public interface RedisKeyConstants {
    String OAUTH2_ACCESS_TOKEN = "oauth2_access_token:%s";
    // 其他键定义...
}

解释

  • 目的:统一管理 Redis 键的格式,类似数据库表的规范管理。
  • 键格式:OAUTH2_ACCESS_TOKEN 使用 %s 占位符,动态生成键(如 oauth2_access_token:token123)。
  • 优势:通过查看 RedisKeyConstants,可以快速了解模块使用的所有 Redis 键,便于维护和调试。
步骤 6:实现 Redis 数据访问对象(OAuth2AccessTokenRedisDAO)

OAuth2AccessTokenRedisDAO 类封装了对 OAuth2AccessTokenDO 的 Redis 缓存操作。

java 复制代码
@Repository
public class OAuth2AccessTokenRedisDAO {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    public OAuth2AccessTokenDO get(String accessToken) {
        String redisKey = formatKey(accessToken);
        return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), OAuth2AccessTokenDO.class);
    }

    public void set(OAuth2AccessTokenDO accessTokenDO) {
        String redisKey = formatKey(accessTokenDO.getAccessToken());
        // 清理多余字段,避免缓存
        accessTokenDO.setUpdater(null).setUpdateTime(null).setCreateTime(null).setCreator(null).setDeleted(null);
        long time = LocalDateTimeUtil.between(LocalDateTime.now(), accessTokenDO.getExpiresTime(), ChronoUnit.SECONDS);
        if (time > 0) {
            stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(accessTokenDO), time, TimeUnit.SECONDS);
        }
    }

    public void delete(String accessToken) {
        String redisKey = formatKey(accessToken);
        stringRedisTemplate.delete(redisKey);
    }

    public void deleteList(Collection<String> accessTokens) {
        List<String> redisKeys = CollectionUtils.convertList(accessTokens, OAuth2AccessTokenRedisDAO::formatKey);
        stringRedisTemplate.delete(redisKeys);
    }

    private static String formatKey(String accessToken) {
        return String.format(OAUTH2_ACCESS_TOKEN, accessToken);
    }
}

解释

  • 注入:@Resource private StringRedisTemplate stringRedisTemplate 注入 StringRedisTemplate,用于操作字符串类型的键值对(值仍为 JSON)。
  • 方法
    • get:从 Redis 获取指定访问令牌的 OAuth2AccessTokenDO,通过 JsonUtils.parseObject 反序列化 JSON 为对象。
    • set:将 OAuth2AccessTokenDO 序列化为 JSON 存储到 Redis,设置动态过期时间(基于 expiresTime)。清理多余字段(如 updater)以减少缓存体积。
    • delete 和 deleteList:删除单个或多个访问令牌的缓存。
  • 键生成:formatKey 使用 RedisKeyConstants.OAUTH2_ACCESS_TOKEN 格式化键名,确保一致性。
步骤 7:业务逻辑中使用 Redis 缓存

在 OAuth2TokenServiceImpl 中,通过注入 OAuth2AccessTokenRedisDAO,实现访问令牌的缓存操作。

java 复制代码
@Service
public class OAuth2TokenServiceImpl {

    @Resource
    private OAuth2AccessTokenRedisDAO accessTokenRedisDAO;

    // 示例方法:获取访问令牌
    public OAuth2AccessTokenDO getAccessToken(String accessToken) {
        // 先从 Redis 获取
        OAuth2AccessTokenDO tokenDO = accessTokenRedisDAO.get(accessToken);
        if (tokenDO != null) {
            return tokenDO;
        }
        // 如果 Redis 没有,从数据库查询
        tokenDO = accessTokenMapper.selectByAccessToken(accessToken);
        if (tokenDO != null) {
            // 存入 Redis
            accessTokenRedisDAO.set(tokenDO);
        }
        return tokenDO;
    }
}

解释

  • 缓存优先:先尝试从 Redis 获取令牌,若存在则直接返回。
  • 数据库回查:若 Redis 缓存不存在,则从数据库查询,并将结果存入 Redis。
  • 封装性:OAuth2AccessTokenRedisDAO 屏蔽了底层的 Redis 操作,业务代码只需调用 DAO 方法,逻辑简洁。

1.2 声明式缓存(Spring Cache)

声明式缓存基于 Spring Cache 框架,使用注解(如 @Cacheable)简化缓存操作。本例以角色(RoleDO)缓存为例。

步骤 1:添加依赖

引入 Spring Cache 依赖以启用注解式缓存。

java 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
  • 解释:此依赖启用 Spring Cache 功能,支持 @Cacheable、@CachePut 和 @CacheEvict 注解。
步骤 2:配置 Redis 作为缓存后端

在 application.yaml 中配置 Redis 连接(与编程式缓存相同),并在 YudaoCacheAutoConfiguration 类中配置 Spring Cache。

java 复制代码
@AutoConfiguration
@EnableConfigurationProperties({CacheProperties.class, YudaoCacheProperties.class})
@EnableCaching
public class YudaoCacheAutoConfiguration {

    @Bean
    @Primary
    public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        // 使用单冒号作为键前缀分隔符
        config = config.computePrefixWith(cacheName -> {
            String keyPrefix = cacheProperties.getRedis().getKeyPrefix();
            if (StringUtils.hasText(keyPrefix)) {
                keyPrefix = keyPrefix.endsWith(":") ? keyPrefix : keyPrefix + ":";
                return keyPrefix + cacheName + ":";
            }
            return cacheName + ":";
        });
        // 使用 JSON 序列化
        config = config.serializeValuesWith(
                RedisSerializationContext.SerializationPair.fromSerializer(YudaoRedisAutoConfiguration.buildRedisSerializer()));

        // 设置缓存属性
        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        if (redisProperties.getTimeToLive() != null) {
            config = config.entryTtl(redisProperties.getTimeToLive());
        }
        if (!redisProperties.isCacheNullValues()) {
            config = config.disableCachingNullValues();
        }
        if (!redisProperties.isUseKeyPrefix()) {
            config = config.disableKeyPrefix();
        }
        return config;
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate<String, Object> redisTemplate,
                                               RedisCacheConfiguration redisCacheConfiguration,
                                               YudaoCacheProperties yudaoCacheProperties) {
        RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory());
        RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory,
                BatchStrategies.scan(yudaoCacheProperties.getRedisScanBatchSize()));
        return new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration);
    }
}

解释

  • 启用缓存:@EnableCaching 激活 Spring Cache 功能。
  • 键前缀:自定义键前缀格式(如 cacheName:),使用单冒号分隔,避免工具(如 Redis Desktop Manager)显示问题。
  • 序列化:复用 YudaoRedisAutoConfiguration.buildRedisSerializer(),确保值以 JSON 格式存储。
  • 缓存属性
    • timeToLive:设置默认缓存过期时间(项目默认 1 小时)。
    • disableCachingNullValues:禁止缓存 null 值。
    • disableKeyPrefix:可选禁用键前缀。
  • RedisCacheManager:创建缓存管理器,基于 Redis 实现缓存存储。
步骤 3:使用 Spring Cache 注解

在 RoleServiceImpl 中,使用 @Cacheable 和 @CacheEvict 实现角色缓存。

java 复制代码
@Service
public class RoleServiceImpl {

    @Resource
    private RoleMapper roleMapper;

    @Cacheable(value = RedisKeyConstants.ROLE, key = "#id", unless = "#result == null")
    public RoleDO getRoleFromCache(Long id) {
        return roleMapper.selectById(id);
    }

    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = RedisKeyConstants.ROLE, key = "#id")
    public void deleteRole(Long id) {
        RoleDO role = validateRoleForUpdate(id);
        roleMapper.deleteById(id);
        permissionService.processRoleDeleted(id);
        LogRecordContext.putVariable("role", role);
    }
}
  • 解释
    • @Cacheable
      • value = RedisKeyConstants.ROLE:指定缓存名称(对应 Redis 键前缀,如 role:)。
      • key = "#id":使用方法参数 id 作为缓存键(如 role:123)。
      • unless = "#result == null":避免缓存 null 值。
      • 执行逻辑:先检查 Redis 是否有缓存,若有则返回;否则执行方法并缓存结果。
    • @CacheEvict:在删除角色时,移除对应的缓存键(如 role:123),确保缓存与数据库一致。
    • 被动读策略:仅在查询时缓存数据(getRoleFromCache),更新或删除时清除缓存(deleteRole),避免缓存非必要数据。
步骤 4:缓存与数据库一致性

yudao 项目采用被动读策略:

  • 查询:从 Redis 获取数据,若无则从 MySQL 查询并缓存。
  • 更新/删除:更新 MySQL 后,删除 Redis 缓存,确保下次查询时重新从数据库加载。
  • 原因
    • 保证 Redis 和 MySQL 数据一致性。
    • 避免主动写入非必要数据,节省 Redis 存储空间。

2.在新项目中实现 Redis 缓存

2.1 项目结构

XML 复制代码
new-project/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/demo/
│   │   │       ├── config/
│   │   │       │   └── RedisConfig.java
│   │   │       ├── dao/
│   │   │       │   └── UserRedisDAO.java
│   │   │       ├── entity/
│   │   │       │   └── UserDO.java
│   │   │       ├── service/
│   │   │       │   ├── UserService.java
│   │   │       │   └── UserServiceImpl.java
│   │   │       └── constants/
│   │   │           └── RedisKeyConstants.java
│   │   └── resources/
│   │       └── application.yml
│   └── pom.xml

2.2 依赖配置

在 pom.xml 中添加必要的依赖。

java 复制代码
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/>
    </parent>

    <dependencies>
        <!-- Spring Boot Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Redis 依赖 -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.25.0</version>
        </dependency>
        <!-- Spring Cache 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <!-- MyBatis-Plus 依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3</version>
        </dependency>
        <!-- MySQL 依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>
        <!-- Lombok 依赖 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <!-- Hutool 工具库 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.20</version>
        </dependency>
    </dependencies>
</project>

解释

  • spring-boot-starter-web:提供 Web 功能。
  • redisson-spring-boot-starter:支持 Redis 操作。
  • spring-boot-starter-cache:支持 Spring Cache 注解。
  • mybatis-plus-boot-starter:用于数据库操作。
  • mysql-connector-java:MySQL 数据库驱动。
  • lombok:简化代码。
  • hutool-all:提供 JSON 序列化等工具,模拟 yudao 的 JsonUtils。

2.3 配置 Redis 连接

在 application.yml 中配置 Redis 和数据库连接。

java 复制代码
spring:
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0
  datasource:
    url: jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
  cache:
    redis:
      time-to-live: 3600000 # 缓存默认过期时间 1 小时(毫秒)
      key-prefix: "cache:" # 缓存键前缀
      cache-null-values: false # 不缓存 null 值
mybatis-plus:
  mapper-locations: classpath*:/mapper/*.xml

解释

  • Redis 配置:与 yudao 一致,指定本地 Redis 服务器。
  • Cache 配置:设置默认过期时间为 1 小时,使用 cache: 前缀。
  • MySQL 配置:连接本地 MySQL 数据库(需创建 demo 数据库)。
  • MyBatis-Plus:配置 Mapper XML 文件路径。

2.4 配置 RedisTemplate 和 Spring Cache

创建 RedisConfig 类,配置 RedisTemplate 和 Spring Cache。

java 复制代码
package com.example.demo.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

@AutoConfiguration
@EnableCaching
public class RedisConfig {

    @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(jsonSerializer());
        template.setHashValueSerializer(jsonSerializer());
        return template;
    }

    private RedisSerializer<Object> jsonSerializer() {
        RedisSerializer<Object> json = RedisSerializer.json();
        ObjectMapper mapper = (ObjectMapper) ReflectUtil.getFieldValue(json, "mapper");
        mapper.registerModule(new JavaTimeModule());
        return json;
    }

    @Bean
    @Primary
    public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        config = config.computePrefixWith(cacheName -> cacheProperties.getRedis().getKeyPrefix() + cacheName + ":");
        config = config.serializeValuesWith(
                RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer()));
        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        if (redisProperties.getTimeToLive() != null) {
            config = config.entryTtl(redisProperties.getTimeToLive());
        }
        if (!redisProperties.isCacheNullValues()) {
            config = config.disableCachingNullValues();
        }
        return config;
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate<String, Object> redisTemplate,
                                              RedisCacheConfiguration redisCacheConfiguration) {
        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
        RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(factory);
        return new RedisCacheManager(cacheWriter, redisCacheConfiguration);
    }
}
  • 解释
    • RedisTemplate:与 yudao 一致,使用字符串序列化键,JSON 序列化值,支持 LocalDateTime。
    • Spring Cache:启用 @EnableCaching,配置 RedisCacheConfiguration 使用 JSON 序列化和自定义键前缀。
    • RedisCacheManager:创建缓存管理器,基于 Redis 存储缓存。

注意:ReflectUtil 模拟 yudao 的反射操作,需替换为 Hutool 的 ReflectUtil 或直接修改 ObjectMapper:

import cn.hutool.core.util.ReflectUtil;

2.5 定义数据对象(UserDO)

创建 UserDO 类,表示用户信息。

java 复制代码
package com.example.demo.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.time.LocalDateTime;

@TableName("user")
@Data
public class UserDO {
    @TableId
    private Long id;
    private String username;
    private String email;
    private LocalDateTime createTime;
}

解释:UserDO 映射数据库表 user,包含基本用户信息。需在 MySQL 中创建表:

java 复制代码
CREATE TABLE user (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100),
    create_time DATETIME
);

2.6 定义 Redis 键常量

创建 RedisKeyConstants 类,定义 Redis 键。

java 复制代码
package com.example.demo.constants;

public interface RedisKeyConstants {
    String USER = "user:%s";
}

解释:定义用户缓存的键格式,如 user:123。

2.7 实现 Redis 数据访问对象(UserRedisDAO)

创建 UserRedisDAO 类,封装用户缓存操作。

java 复制代码
package com.example.demo.dao;

import cn.hutool.json.JSONUtil;
import com.example.demo.constants.RedisKeyConstants;
import com.example.demo.entity.UserDO;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Repository
public class UserRedisDAO {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    public UserDO get(Long id) {
        String redisKey = formatKey(id);
        String value = stringRedisTemplate.opsForValue().get(redisKey);
        return JSONUtil.toBean(value, UserDO.class);
    }

    public void set(UserDO userDO) {
        String redisKey = formatKey(userDO.getId());
        userDO.setCreateTime(null); // 清理多余字段
        stringRedisTemplate.opsForValue().set(redisKey, JSONUtil.toJsonStr(userDO), 3600, TimeUnit.SECONDS);
    }

    public void delete(Long id) {
        String redisKey = formatKey(id);
        stringRedisTemplate.delete(redisKey);
    }

    private static String formatKey(Long id) {
        return String.format(RedisKeyConstants.USER, id);
    }
}

解释

  • 注入:使用 StringRedisTemplate 操作 JSON 字符串。
  • 方法
    • get:从 Redis 获取用户数据,反序列化为 UserDO。
    • set:将 UserDO 序列化为 JSON,设置 1 小时过期时间。
    • delete:删除指定用户的缓存。
  • JSON 处理:使用 Hutool 的 JSONUtil 替代 yudao 的 JsonUtils。

2.8 实现业务逻辑

创建 UserService 接口和 UserServiceImpl 实现类,支持编程式和声明式缓存。

java 复制代码
package com.example.demo.service;

import com.example.demo.entity.UserDO;

public interface UserService {
    UserDO getUser(Long id); // 编程式缓存
    UserDO getUserFromCache(Long id); // 声明式缓存
    void deleteUser(Long id);
}
java 复制代码
package com.example.demo.service;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.example.demo.constants.RedisKeyConstants;
import com.example.demo.dao.UserRedisDAO;
import com.example.demo.entity.UserDO;
import com.example.demo.mapper.UserMapper;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;
    @Resource
    private UserRedisDAO userRedisDAO;

    @Override
    public UserDO getUser(Long id) {
        UserDO user = userRedisDAO.get(id);
        if (user != null) {
            return user;
        }
        user = userMapper.selectById(id);
        if (user != null) {
            userRedisDAO.set(user);
        }
        return user;
    }

    @Override
    @Cacheable(value = RedisKeyConstants.USER, key = "#id", unless = "#result == null")
    public UserDO getUserFromCache(Long id) {
        return userMapper.selectById(id);
    }

    @Override
    @CacheEvict(value = RedisKeyConstants.USER, key = "#id")
    public void deleteUser(Long id) {
        userMapper.deleteById(id);
        // 缓存通过 @CacheEvict 自动清除
    }
}
  • 解释
    • 编程式缓存(getUser):先查 Redis,若无则查数据库并缓存。
    • 声明式缓存(getUserFromCache):使用 @Cacheable,自动处理缓存逻辑。
    • 删除(deleteUser):删除数据库记录并通过 @CacheEvict 清除缓存。
相关推荐
ggdpzhk14 分钟前
输入两个正整数,计算最大公约数和最小公倍数
java·算法
Luffe船长14 分钟前
springboot将文件插入到指定路径文件夹,判断文件是否存在以及根据名称删除
java·spring boot·后端·spring
weixin_4621764118 分钟前
(三十一)深度解析领域特定语言(DSL)第六章——语法分析:递归下降语法分析器(Recursive-Descent Parser)
java·开发语言·软件构建
YuTaoShao20 分钟前
Java八股文——数据结构「数据结构篇」
java·数据结构·面试·八股文
可可格子衫2 小时前
keep-alive缓存文章列表案例完整代码(Vue2)
vue.js·缓存
码农开荒路2 小时前
Redis之缓存一致性
数据库·redis·缓存
程序员清风2 小时前
RocketMQ发送消息默认是什么策略,主同步成功了就算成功了?异步写?还是要大部分从都同步了?
java·后端·面试
starstarzz2 小时前
解决idea无法正常加载lombok包
java·ide·spring·intellij-idea·springboot·web
oioihoii2 小时前
C++11 Thread-Local Storage:从入门到精通
java·开发语言·c++
YuTaoShao2 小时前
Java八股文——消息队列「场景篇」
java·面试·消息队列·八股文