已疯 發表於 2025-9-24 09:30:36

redis在springboot中做缓存操作的两种方法应用实例

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">一.进行redis的安装与开启(简略)</a></li><li><a href="#_label1">二.进行依赖引入和配置文件搭建</a></li><li><a href="#_label2">三.构建config以及加注解</a></li><ul class="second_class_ul"><li><a href="#_lab2_2_0">配置Redis缓存管理器</a></li></ul><li><a href="#_label3">四.接下来就是要进行缓存管理</a></li><ul class="second_class_ul"><li><a href="#_lab2_3_1">1.声明式注解缓存(Annotation-based Caching)</a></li><li><a href="#_lab2_3_2">应用示例</a></li><ul class="third_class_ul"><li><a href="#_label3_3_2_0">(2) 更新数据后刷新缓存</a></li><li><a href="#_label3_3_2_1">(3) 删除数据后清理缓存</a></li></ul><li><a href="#_lab2_3_3">2.命令式编程缓存(Imperative Caching with RedisTemplate)</a></li><ul class="third_class_ul"></ul><li><a href="#_lab2_3_4">核心思想</a></li><ul class="third_class_ul"></ul><li><a href="#_lab2_3_5">应用示例</a></li><ul class="third_class_ul"><li><a href="#_label3_3_5_2">(2) 操作复杂数据结构(Hash)</a></li></ul><li><a href="#_lab2_3_6">3.两种方式的深度对比</a></li><ul class="third_class_ul"></ul></ul><li><a href="#_label4">最后 关键场景选择建议</a></li><ul class="second_class_ul"></ul></ul></div><p>众所周知,redis是一个高性能的键值对存储数据库,在现在的程序构建时,当数据量较大时或数据重复利用时常常利用缓存技术来减少时间消耗和资源浪费,本文就是介绍在springboot中如何利用redis做缓存</p>
<p class="maodian"><a name="_label0"></a></p><h2>一.进行redis的安装与开启(简略)</h2>
<p>在windows系统或Linux系统都可</p>
<p>默认已安装完redis</p>
<p class="maodian"><a name="_label1"></a></p><h2>二.进行依赖引入和配置文件搭建</h2>
<p>依赖</p>
<blockquote><p>&lt;dependency&gt;<br />&nbsp; &nbsp; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br />&nbsp; &nbsp; &lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;<br />&lt;/dependency&gt;<br />&lt;dependency&gt;<br />&nbsp; &nbsp; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br />&nbsp; &nbsp; &lt;artifactId&gt;spring-boot-starter-cache&lt;/artifactId&gt;<br />&lt;/dependency&gt;</p></blockquote>
<p>配置文件(application.properties)</p>
<blockquote><p>spring.redis.host=(你redis主机的ip地址)<br />spring.redis.port=6379<br />spring.redis.password=<br /># 可选连接池配置<br />spring.redis.lettuce.pool.max-active=8<br />spring.redis.lettuce.pool.max-idle=8<br />spring.redis.lettuce.pool.min-idle=0</p></blockquote>
<p class="maodian"><a name="_label2"></a></p><h2>三.构建config以及加注解</h2>
<p><strong>在主启动类添加@EnableCaching注解:</strong></p>
<div class="jb51code"><pre class="brush:java;">@SpringBootApplication
@EnableCaching
public class Application {
    public static void main(String[] args) {
      SpringApplication.run(Application.class, args);
    }
}</pre></div>
<p class="maodian"><a name="_lab2_2_0"></a></p><h3>配置Redis缓存管理器</h3>
<div class="jb51code"><pre class="brush:java;">@Configuration
public class RedisCacheConfig {
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
      RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .entryTtl(Duration.ofMinutes(30)); // 默认过期时间30分钟
      return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .withCacheConfiguration("userCache", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10))) // 自定义缓存过期
                .transactionAware()
                .build();
    }
}</pre></div>
<p>userCache&quot;这是缓存名称。在 Spring 应用程序中,你可以通过指定不同的缓存名称来管理不同的缓存区域。</p>
<p><strong>管理器中进行了与redis的连接还有序列化(数据库中存储的形式)</strong></p>
<p>例如,如果你缓存了一个 User 对象,它会被序列化为 JSON 字符串,然后存储在 Redis 中。在 Redis 中,数据看起来可能是这样的:</p>
<blockquote><p>&quot;key&quot;: &quot;{\&quot;id\&quot;:\&quot;1\&quot;,\&quot;name\&quot;:\&quot;John Doe\&quot;,\&quot;email\&quot;:\&quot;john.doe@example.com\&quot;}&quot;</p></blockquote>
<p class="maodian"><a name="_label3"></a></p><h2>四.接下来就是要进行缓存管理</h2>
<p>缓存操作管理通常有两种方式分别为&nbsp;<strong>声明式注解缓存(Annotation-based Caching)</strong>和<strong>命令式编程(Imperative Caching with RedisTemplate),</strong>接下来就谈谈我对这两种方法的理解及其运用</p>
<p><strong>提示:</strong>通过我的RedisCacheConfig文件序列化后,我们在redis数据库中的对象就是字符串类型的方式来存储</p>
<p class="maodian"><a name="_lab2_3_1"></a></p><h3>1.声明式注解缓存(Annotation-based Caching)</h3>
<p><strong>核心思想</strong><br />通过 AOP(面向切面编程)实现,开发者通过注解声明缓存行为,由框架自动完成缓存的读写和失效管理。</p>
<p class="maodian"><a name="_lab2_3_2"></a></p><p class="maodian"><a name="_lab2_3_5"></a></p><h3>应用示例</h3>
<p><strong>(1) 读取数据优先缓存</strong></p>
<div class="jb51code"><pre class="brush:java;">@Cacheable(value = "userCache", key = "#id", unless = "#result == null")
public User getUserById(Long id) {
    return userRepository.findById(id).orElse(null);
}</pre></div>
<p>原理:</p>
<p>方法执行前检查 userCache::id 是否存在(</p>
<p>value = &quot;userCache&quot;<br />标识该缓存属于名为userCache的缓存区(Cache Region),对应Redis中的键前缀(如userCache::1)</p>
<p>key = &quot;#id&quot;<br />使用SpEL表达式定义缓存键,此处表示用方法参数id的值作为键的后缀</p>
<p>)</p>
<p>存在则直接返回缓存值</p>
<p>不存在则执行方法体,将结果存入缓存</p>
<p>unless 确保空值不缓存(防止缓存穿透)</p>
<p class="maodian"><a name="_label3_3_2_0"></a></p><h4>(2) 更新数据后刷新缓存</h4>
<div class="jb51code"><pre class="brush:java;">@CachePut(value = "userCache", key = "#user.id")
public User updateUser(User user) {
    return userRepository.save(user); // 强制更新缓存
}</pre></div>
<p><strong>原理</strong>:</p>
<ul><li>无论缓存是否存在,始终执行方法体</li><li>将返回结果覆盖旧缓存</li></ul>
<p class="maodian"><a name="_label3_3_2_1"></a></p><h4>(3) 删除数据后清理缓存</h4>
<div class="jb51code"><pre class="brush:java;">@CacheEvict(value = "userCache", key = "#id", beforeInvocation = true)
public void deleteUser(Long id) {
    userRepository.deleteById(id);
}</pre></div>
<p><strong>原理</strong>:</p>
<ul><li><code>beforeInvocation = true</code>&nbsp;表示在方法执行前删除缓存(避免方法执行失败导致缓存残留)</li></ul>
<p class="maodian"><a name="_lab2_3_3"></a></p><h3>2.命令式编程缓存(Imperative Caching with RedisTemplate)</h3>
<p class="maodian"><a name="_lab2_3_4"></a></p><h3>核心思想</h3>
<p>通过&nbsp;<code>RedisTemplate</code>&nbsp;直接操作 Redis 的 API,开发者需手动控制缓存逻辑,灵活性更高。</p>
<p>首先要配置一个<code>RedisTemplate</code></p>
<div class="jb51code"><pre class="brush:java;">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;
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate&lt;String, Object&gt; redisTemplate(RedisConnectionFactory redisConnectionFactory) {
      RedisTemplate&lt;String, Object&gt; template = new RedisTemplate&lt;&gt;();
      template.setConnectionFactory(redisConnectionFactory);
      // 设置 key 序列化器
      template.setKeySerializer(new StringRedisSerializer());
      // 设置 value 序列化器
      template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
      template.afterPropertiesSet();
      return template;
    }
}</pre></div>
<p><strong>RedisTemplate 是 Spring Data Redis 提供的一个用于操作 Redis 的模板类。它包含了很多操作 Redis 的方法,主要分为以下几类:</strong><br /><strong>通用操作</strong><br />hasKey(K key): 检查 key 是否存在。<br />delete(K key): 删除 key。<br />delete(Collection&lt;K&gt; keys): 批量删除 keys。<br />expire(K key, long timeout, TimeUnit unit): 设置 key 的过期时间。<br />expireAt(K key, Date date): 设置 key 在指定时间过期。<br />keys(K pattern): 查找所有符合给定模式 pattern 的 key。<br />move(K key, int dbIndex): 将 key 移动到指定的数据库。<br />randomKey(): 随机返回一个 key。<br />rename(K oldKey, K newKey): 重命名 key。<br />type(K key): 返回 key 的类型。<br /><strong>字符串操作</strong><br />opsForValue(): 获取字符串操作对象,进而可以使用如 set, get, increment, decrement 等方法。<br /><strong>列表操作</strong><br />opsForList(): 获取列表操作对象,进而可以使用如 leftPush, rightPush, range, size 等方法。<br /><strong>集合操作</strong><br />opsForSet(): 获取集合操作对象,进而可以使用如 add, members, size, isMember 等方法。<br /><strong>有序集合操作</strong><br />opsForZSet(): 获取有序集合操作对象,进而可以使用如 add, range, rangeByScore, score 等方法。<br /><strong>哈希操作</strong><br />opsForHash(): 获取哈希操作对象,进而可以使用如 put, get, entries, keys, values 等方法。<br /><strong>事务操作</strong><br />execute(RedisCallback&lt;T&gt; action): 执行一个 RedisCallback。<br />executePipelined(RedisCallback&lt;T&gt; action): 以管道的方式执行多个命令。<br />multi(): 标记事务开始。<br />exec(): 执行所有事务块内的命令。<br />这些方法提供了对 Redis 数据结构的基本操作,可以满足大部分的使用场景。需要注意的是,这些操作都是同步的,如果需要异步操作,可以使用 RedisTemplate 的异步版本 StringRedisTemplate。</p>
<h3>应用示例</h3>
<p><strong>(1) 手动缓存读取与写入</strong></p>
<div class="jb51code"><pre class="brush:java;">public User getUserById(Long id) {
    // 生成缓存键,格式为 "user:{id}"(如 "user:123")
    String cacheKey = "user:" + id;
    // 获取 Redis 的 String 类型操作接口
    ValueOperations&lt;String, User&gt; ops = redisTemplate.opsForValue();
    // 尝试从 Redis 中获取缓存数据
    User user = ops.get(cacheKey);
    // 缓存未命中(包含空值标记的情况)
    if (user == null) {
      // 穿透到数据库查询真实数据
      user = userRepository.findById(id).orElse(null);
      if (user != null) {
            // 数据库存在数据:写入缓存并设置 30 分钟过期时间
            ops.set(cacheKey, user, Duration.ofMinutes(30));
      } else {
            // 数据库不存在数据:写入特殊空值标记,设置 5 分钟较短过期时间
            // 使用 new NullValue() 而非 null 是为了区分:
            // 1. 真实缓存空值(防止穿透)
            // 2. Redis 未存储该键(真正的缓存未命中)
            ops.set(cacheKey, new NullValue(), Duration.ofMinutes(5));
      }
    }
    // 返回前进行空值标记判断
    return user instanceof NullValue ? null : user;
}</pre></div>
<p>特点:</p>
<p>完全手动控制缓存逻辑</p>
<p>可精细处理空值缓存和 TTL</p>
<p class="maodian"><a name="_label3_3_5_2"></a></p><h4>(2) 操作复杂数据结构(Hash)</h4>
<div class="jb51code"><pre class="brush:java;">public void updateUserProfile(Long userId, Map&lt;String, String&gt; profile) {
    String hashKey = "userProfiles";
    redisTemplate.opsForHash().putAll(hashKey, profile);
    // 设置整个 Hash 的过期时间
    redisTemplate.expire(hashKey, Duration.ofHours(24));
}</pre></div>
<p class="maodian"><a name="_lab2_3_6"></a></p><h3>3.两种方式的深度对比</h3>
<table border="1" cellpadding="1" cellspacing="1"><tbody><tr><th>维度</th><td><strong>声明式注解缓存 (@Cacheable&nbsp;等)</strong></td><td><strong>命令式编程缓存 (RedisTemplate)</strong></td></tr><tr><td><strong>抽象层级</strong></td><td>高层抽象,屏蔽缓存实现细节</td><td>底层操作,直接面向 Redis API</td></tr><tr><td><strong>代码侵入性</strong></td><td>无侵入(通过注解实现)</td><td>显式代码调用</td></tr><tr><td><strong>灵活性</strong></td><td>有限(受限于注解参数)</td><td>极高(可自由操作所有 Redis 命令)</td></tr><tr><td><strong>数据结构支持</strong></td><td>仅支持简单键值对(通过序列化)</td><td>支持所有 Redis 数据结构(Hash/List 等)</td></tr><tr><td><strong>事务支持</strong></td><td>与 Spring 事务管理集成</td><td>需手动使用&nbsp;<code>multi/exec</code>&nbsp;或&nbsp;<code>SessionCallback</code></td></tr><tr><td><strong>异常处理</strong></td><td>统一通过&nbsp;<code>CacheErrorHandler</code></td><td>需自行&nbsp;<code>try-catch</code></td></tr><tr><td><strong>缓存策略配置</strong></td><td>集中式配置(通过&nbsp;<code>RedisCacheManager</code>)</td><td>分散在代码各处</td></tr><tr><td><strong>性能优化</strong></td><td>自动批量化(部分实现支持)</td><td>分散在代码各处</td></tr></tbody></table>
<p class="maodian"><a name="_label4"></a></p><h2>最后 关键场景选择建议</h2>
<p><strong>(1) 优先使用注解的场景</strong><br />简单的 CRUD 缓存需求</p>
<p>需要快速实现缓存逻辑</p>
<p>希望代码保持简洁(如业务层方法只需关注核心逻辑)</p>
<p>需要兼容多缓存后端(如同时支持 Redis 和本地缓存)</p>
<p><strong>(2) 必须使用命令式的场景</strong><br />操作 Redis 特有数据结构(如 GEO、HyperLogLog)</p>
<p>实现分布式锁、限流等高级功能</p>
<p>需要精细控制每个操作的 TTL</p>
<p>使用事务、Pipeline 等 Redis 特性</p>
頁: [1]
查看完整版本: redis在springboot中做缓存操作的两种方法应用实例