最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring Cache和Redis 聲明式緩存指南(最新推薦)

 更新時(shí)間:2026年01月26日 09:04:56   作者:其美杰布-富貴-李  
文章主要介紹了Spring Cache和Redis在高并發(fā)Web應(yīng)用中的作用和使用方法,文章也討論了緩存的高級(jí)特性,如緩存失效策略、SpEL表達(dá)式應(yīng)用、條件緩存等,感興趣的朋友跟隨小編一起看看吧

Spring Cache + Redis 聲明式緩存指南

1. 背景與動(dòng)機(jī)

1.1 為什么需要緩存

在高并發(fā) Web 應(yīng)用中,數(shù)據(jù)庫(kù)往往成為性能瓶頸:

問(wèn)題影響解決方案
頻繁查詢(xún)數(shù)據(jù)庫(kù)連接耗盡緩存熱點(diǎn)數(shù)據(jù)
慢查詢(xún)接口響應(yīng)時(shí)間長(zhǎng)緩存計(jì)算結(jié)果
高并發(fā)讀數(shù)據(jù)庫(kù) CPU/IO 打滿(mǎn)讀寫(xiě)分離 + 緩存

典型收益

  • 響應(yīng)時(shí)間:從 100ms 降至 5ms(20 倍提升)
  • 數(shù)據(jù)庫(kù)負(fù)載:減少 60%-90%
  • 并發(fā)能力:從 1000 QPS 提升至 10000+ QPS

1.2 Spring Cache 的設(shè)計(jì)理念

Spring Cache 是一個(gè) 抽象層,核心思想是:

應(yīng)用代碼 → Spring Cache 抽象層 → 具體緩存實(shí)現(xiàn)(Redis/EhCache/Caffeine)

優(yōu)勢(shì)

  • ? 聲明式編程:通過(guò)注解而非侵入式代碼
  • ? 實(shí)現(xiàn)無(wú)關(guān):可無(wú)縫切換緩存提供者
  • ? AOP 實(shí)現(xiàn):自動(dòng)攔截方法調(diào)用
  • ? 統(tǒng)一 API:學(xué)習(xí)成本低

1.3 為什么選擇 Redis

特性RedisEhCacheCaffeine
分布式支持? 原生支持? 僅單機(jī)? 僅單機(jī)
持久化? RDB/AOF?? 有限? 純內(nèi)存
數(shù)據(jù)結(jié)構(gòu)? 豐富(String/Hash/List等)?? 簡(jiǎn)單?? 簡(jiǎn)單
高可用? 哨兵/集群??
適用場(chǎng)景分布式系統(tǒng)單體應(yīng)用單體應(yīng)用

Redis 適用于:微服務(wù)架構(gòu)、多實(shí)例部署、需要數(shù)據(jù)共享的場(chǎng)景。

2. 核心概念與架構(gòu)

2.1 Spring Cache 抽象層

┌─────────────────────────────────────────┐
│        應(yīng)用代碼(@Cacheable)             │
├─────────────────────────────────────────┤
│      Spring Cache 抽象層(AOP)          │
│  ┌─────────────┐      ┌──────────────┐ │
│  │CacheManager │ ───> │    Cache     │ │
│  └─────────────┘      └──────────────┘ │
├─────────────────────────────────────────┤
│       緩存提供者實(shí)現(xiàn)(SPI)               │
│  ┌──────────┐  ┌──────────┐  ┌───────┐│
│  │  Redis   │  │ EhCache  │  │Caffeine││
│  └──────────┘  └──────────┘  └───────┘│
└─────────────────────────────────────────┘

2.2 核心接口

CacheManager(緩存管理器)
public interface CacheManager {
    Cache getCache(String name);  // 根據(jù)名稱(chēng)獲取緩存
    Collection<String> getCacheNames();
}
Cache(緩存操作接口)
public interface Cache {
    ValueWrapper get(Object key);           // 獲取緩存
    void put(Object key, Object value);     // 寫(xiě)入緩存
    void evict(Object key);                 // 清除緩存
}

2.3 Redis 作為緩存提供者

RedisCacheManager 是 Spring Data Redis 提供的實(shí)現(xiàn):

RedisCacheManager
    ├─ RedisCacheConfiguration (配置)
    │   ├─ TTL (過(guò)期時(shí)間)
    │   ├─ KeySerializer (Key 序列化)
    │   └─ ValueSerializer (Value 序列化)
    └─ RedisConnectionFactory (連接工廠)

3. 快速開(kāi)始

3.1 依賴(lài)配置

Mavenpom.xml):

<dependencies>
    <!-- Spring Boot Starter -->
    <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>
    <!-- 連接池(推薦 Lettuce) -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    <!-- JSON 序列化(推薦) -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>

Gradlebuild.gradle):

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    implementation 'org.springframework.boot:spring-boot-starter-cache'
    implementation 'org.apache.commons:commons-pool2'
    implementation 'com.fasterxml.jackson.core:jackson-databind'
}

3.2 Redis 連接配置

application.yml

spring:
  # Redis 連接配置
  data:
    redis:
      host: localhost
      port: 6379
      password: your_password  # 如無(wú)密碼可省略
      database: 0
      timeout: 5000ms
      # Lettuce 連接池配置(推薦)
      lettuce:
        pool:
          max-active: 20     # 最大連接數(shù)
          max-idle: 10       # 最大空閑連接
          min-idle: 5        # 最小空閑連接
          max-wait: 2000ms   # 連接超時(shí)時(shí)間
  # 緩存配置
  cache:
    type: redis              # 指定緩存類(lèi)型
    redis:
      time-to-live: 600000   # 默認(rèn)過(guò)期時(shí)間 10 分鐘(毫秒)
      cache-null-values: true # 是否緩存空值(防止緩存穿透)
      key-prefix: "app:"     # Key 前綴
      use-key-prefix: true   # 是否使用前綴

3.3 啟用緩存注解

在啟動(dòng)類(lèi)或配置類(lèi)添加 @EnableCaching

package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching  // ? 啟用緩存功能
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

4. 核心注解詳解

4.1 @EnableCaching

作用:激活 Spring Cache 功能,觸發(fā) AOP 代理。

原理

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
    boolean proxyTargetClass() default false; // 是否強(qiáng)制使用 CGLIB 代理
    AdviceMode mode() default AdviceMode.PROXY; // 代理模式
}

4.2 @Cacheable(查詢(xún)緩存)

作用:先查緩存,未命中則執(zhí)行方法并緩存結(jié)果。

常用屬性

屬性說(shuō)明示例
cacheNames/value緩存名稱(chēng)@Cacheable("users")
key緩存 Key(SpEL)key = "#id"
condition緩存條件condition = "#id > 0"
unless排除條件unless = "#result == null"
sync同步加載(防擊穿)sync = true

示例

@Service
public class UserService {
    /**
     * 根據(jù) ID 查詢(xún)用戶(hù)(帶緩存)
     * Key: users::1
     * TTL: 使用全局配置(10 分鐘)
     */
    @Cacheable(cacheNames = "users", key = "#id")
    public User getUserById(Long id) {
        System.out.println("從數(shù)據(jù)庫(kù)查詢(xún)用戶(hù): " + id);
        return userRepository.findById(id).orElse(null);
    }
    /**
     * 條件緩存:僅緩存 VIP 用戶(hù)
     */
    @Cacheable(cacheNames = "vipUsers", 
               key = "#id", 
               condition = "#user.isVip()")
    public User getVipUser(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    /**
     * 排除空值:不緩存查詢(xún)結(jié)果為 null 的情況
     */
    @Cacheable(cacheNames = "users", 
               key = "#username", 
               unless = "#result == null")
    public User getUserByUsername(String username) {
        return userRepository.findByUsername(username);
    }
}

執(zhí)行流程

1. 攔截方法調(diào)用
2. 根據(jù) Key 查詢(xún)緩存
3. 命中 → 直接返回
4. 未命中 → 執(zhí)行方法 → 緩存結(jié)果 → 返回

4.3 @CachePut(更新緩存)

作用:無(wú)論緩存是否存在,都執(zhí)行方法并更新緩存。

典型場(chǎng)景:更新操作后同步緩存。

@Service
public class UserService {
    /**
     * 更新用戶(hù)信息(同步更新緩存)
     */
    @CachePut(cacheNames = "users", key = "#user.id")
    public User updateUser(User user) {
        System.out.println("更新數(shù)據(jù)庫(kù): " + user.getId());
        return userRepository.save(user);
    }
}

與 @Cacheable 的區(qū)別

注解執(zhí)行方法緩存操作
@Cacheable僅在緩存未命中時(shí)執(zhí)行讀取 + 寫(xiě)入
@CachePut始終執(zhí)行僅寫(xiě)入

4.4 @CacheEvict(清除緩存)

作用:刪除指定緩存數(shù)據(jù)。

常用屬性

屬性說(shuō)明示例
allEntries清空整個(gè)緩存allEntries = true
beforeInvocation方法執(zhí)行前清除beforeInvocation = true
@Service
public class UserService {
    /**
     * 刪除用戶(hù)(清除緩存)
     */
    @CacheEvict(cacheNames = "users", key = "#id")
    public void deleteUser(Long id) {
        System.out.println("刪除用戶(hù): " + id);
        userRepository.deleteById(id);
    }
    /**
     * 批量導(dǎo)入用戶(hù)(清空整個(gè)緩存)
     */
    @CacheEvict(cacheNames = "users", allEntries = true)
    public void importUsers(List<User> users) {
        userRepository.saveAll(users);
    }
    /**
     * 方法執(zhí)行前清除緩存(防止異常導(dǎo)致緩存不一致)
     */
    @CacheEvict(cacheNames = "users", 
                key = "#id", 
                beforeInvocation = true)
    public void dangerousOperation(Long id) {
        // 可能拋出異常的操作
    }
}

4.5 @Caching(組合操作)

作用:組合多個(gè)緩存注解。

@Service
public class UserService {
    /**
     * 復(fù)雜場(chǎng)景:同時(shí)更新多個(gè)緩存
     */
    @Caching(
        put = {
            @CachePut(cacheNames = "users", key = "#user.id"),
            @CachePut(cacheNames = "usersByEmail", key = "#user.email")
        },
        evict = {
            @CacheEvict(cacheNames = "userList", allEntries = true)
        }
    )
    public User updateUserComplex(User user) {
        return userRepository.save(user);
    }
}

5. 配置與定制化

5.1 RedisCacheConfiguration 核心配置

完整配置類(lèi)

package com.example.demo.config;
import org.springframework.cache.annotation.EnableCaching;
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
@EnableCaching
public class CacheConfig {
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // 默認(rèn)配置
        RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
                // Key 序列化(String)
                .serializeKeysWith(
                        RedisSerializationContext.SerializationPair.fromSerializer(
                                new StringRedisSerializer()
                        )
                )
                // Value 序列化(JSON)
                .serializeValuesWith(
                        RedisSerializationContext.SerializationPair.fromSerializer(
                                new GenericJackson2JsonRedisSerializer()
                        )
                )
                // 默認(rèn)過(guò)期時(shí)間 10 分鐘
                .entryTtl(Duration.ofMinutes(10))
                // 禁用緩存空值
                .disableCachingNullValues()
                // Key 前綴
                .prefixCacheNameWith("app:");
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(defaultConfig)
                .build();
    }
}

5.2 序列化方案選擇

方案對(duì)比
序列化器優(yōu)點(diǎn)缺點(diǎn)適用場(chǎng)景
JDK原生支持可讀性差、體積大簡(jiǎn)單場(chǎng)景
Jackson可讀性好、跨語(yǔ)言性能中等? 推薦
Protobuf高性能、體積小需要定義 proto高性能要求
Kryo高性能需要注冊(cè)類(lèi)Java 專(zhuān)用
Jackson 序列化配置(推薦)
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
@Bean
public GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer() {
    ObjectMapper objectMapper = new ObjectMapper();
    // 啟用類(lèi)型信息(防止反序列化時(shí)類(lèi)型丟失)
    objectMapper.activateDefaultTyping(
            LaissezFaireSubTypeValidator.instance,
            ObjectMapper.DefaultTyping.NON_FINAL,
            JsonTypeInfo.As.PROPERTY
    );
    return new GenericJackson2JsonRedisSerializer(objectMapper);
}

Redis 中的 JSON 數(shù)據(jù)示例

{
  "@class": "com.example.demo.entity.User",
  "id": 1,
  "username": "alice",
  "email": "alice@example.com",
  "createTime": ["java.time.LocalDateTime", "2024-01-01T10:00:00"]
}

5.3 TTL 過(guò)期時(shí)間配置

全局 TTL
RedisCacheConfiguration.defaultCacheConfig()
    .entryTtl(Duration.ofMinutes(10)); // 10 分鐘
不同緩存空間使用不同 TTL
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
    // 默認(rèn)配置(10 分鐘)
    RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(10));
    // 用戶(hù)緩存(1 小時(shí))
    RedisCacheConfiguration userConfig = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofHours(1));
    // 熱點(diǎn)數(shù)據(jù)緩存(5 分鐘)
    RedisCacheConfiguration hotConfig = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(5));
    return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(defaultConfig)
            .withCacheConfiguration("users", userConfig)      // users 緩存使用 1 小時(shí)
            .withCacheConfiguration("products", hotConfig)    // products 緩存使用 5 分鐘
            .build();
}

5.4 Key 生成策略

默認(rèn) Key 生成規(guī)則
cacheNames::key
例如:users::1
自定義 Key 生成器
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
@Component("customKeyGenerator")
public class CustomKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object target, Method method, Object... params) {
        // 格式:類(lèi)名:方法名:參數(shù)1_參數(shù)2
        return target.getClass().getSimpleName() + ":" +
               method.getName() + ":" +
               Arrays.toString(params).replaceAll("[\\[\\]\\s]", "");
    }
}

使用自定義 Key 生成器

@Cacheable(cacheNames = "users", keyGenerator = "customKeyGenerator")
public User getUserById(Long id) {
    return userRepository.findById(id).orElse(null);
}

5.5 多 CacheManager 配置

場(chǎng)景:同時(shí)使用 Redis(分布式) + Caffeine(本地)。

@Configuration
public class MultiCacheConfig {
    // Redis CacheManager
    @Bean
    @Primary
    public CacheManager redisCacheManager(RedisConnectionFactory factory) {
        return RedisCacheManager.builder(factory)
                .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
                .build();
    }
    // Caffeine CacheManager
    @Bean
    public CacheManager caffeineCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .expireAfterWrite(5, TimeUnit.MINUTES)
                .maximumSize(1000));
        return cacheManager;
    }
}

指定使用哪個(gè) CacheManager

@Cacheable(cacheNames = "users", cacheManager = "redisCacheManager")
public User getUserById(Long id) {
    return userRepository.findById(id).orElse(null);
}

6. 完整代碼示例

6.1 項(xiàng)目結(jié)構(gòu)

src/main/java/com/example/demo
├── DemoApplication.java          # 啟動(dòng)類(lèi)
├── config
│   └── CacheConfig.java          # 緩存配置
├── entity
│   └── User.java                 # 實(shí)體類(lèi)
├── repository
│   └── UserRepository.java       # 數(shù)據(jù)訪問(wèn)層
├── service
│   └── UserService.java          # 業(yè)務(wù)層(帶緩存)
└── controller
    └── UserController.java       # 控制器

6.2 配置類(lèi)

package com.example.demo.config;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.annotation.EnableCaching;
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
@EnableCaching
public class CacheConfig {
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // 配置 Jackson 序列化器
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.activateDefaultTyping(
                LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.PROPERTY
        );
        GenericJackson2JsonRedisSerializer jsonSerializer = 
                new GenericJackson2JsonRedisSerializer(objectMapper);
        // 默認(rèn)緩存配置
        RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(
                        RedisSerializationContext.SerializationPair.fromSerializer(
                                new StringRedisSerializer()
                        )
                )
                .serializeValuesWith(
                        RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer)
                )
                .entryTtl(Duration.ofMinutes(10))
                .disableCachingNullValues()
                .prefixCacheNameWith("app:");
        // 用戶(hù)緩存配置(1 小時(shí))
        RedisCacheConfiguration userConfig = defaultConfig
                .entryTtl(Duration.ofHours(1));
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(defaultConfig)
                .withCacheConfiguration("users", userConfig)
                .transactionAware()  // 支持事務(wù)
                .build();
    }
}

6.3 實(shí)體類(lèi)

package com.example.demo.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "t_user")
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false, unique = true, length = 50)
    private String username;
    @Column(nullable = false, length = 100)
    private String email;
    @Column(name = "create_time")
    private LocalDateTime createTime;
    @Column(name = "update_time")
    private LocalDateTime updateTime;
}

6.4 Repository 層

package com.example.demo.repository;
import com.example.demo.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
}

6.5 Service 層(核心)

package com.example.demo.service;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
    /**
     * 查詢(xún)用戶(hù)(帶緩存)
     * Key: users::1
     */
    @Cacheable(cacheNames = "users", key = "#id", unless = "#result == null")
    public User getUserById(Long id) {
        log.info("從數(shù)據(jù)庫(kù)查詢(xún)用戶(hù): {}", id);
        return userRepository.findById(id).orElse(null);
    }
    /**
     * 根據(jù)用戶(hù)名查詢(xún)(帶緩存)
     */
    @Cacheable(cacheNames = "usersByName", key = "#username")
    public User getUserByUsername(String username) {
        log.info("從數(shù)據(jù)庫(kù)查詢(xún)用戶(hù): {}", username);
        return userRepository.findByUsername(username).orElse(null);
    }
    /**
     * 查詢(xún)所有用戶(hù)(帶緩存)
     */
    @Cacheable(cacheNames = "userList")
    public List<User> getAllUsers() {
        log.info("從數(shù)據(jù)庫(kù)查詢(xún)所有用戶(hù)");
        return userRepository.findAll();
    }
    /**
     * 創(chuàng)建用戶(hù)(清除列表緩存)
     */
    @Transactional
    @CacheEvict(cacheNames = "userList", allEntries = true)
    public User createUser(User user) {
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        log.info("創(chuàng)建用戶(hù): {}", user.getUsername());
        return userRepository.save(user);
    }
    /**
     * 更新用戶(hù)(更新緩存 + 清除列表緩存)
     */
    @Transactional
    @Caching(
            put = {
                    @CachePut(cacheNames = "users", key = "#user.id"),
                    @CachePut(cacheNames = "usersByName", key = "#user.username")
            },
            evict = {
                    @CacheEvict(cacheNames = "userList", allEntries = true)
            }
    )
    public User updateUser(User user) {
        user.setUpdateTime(LocalDateTime.now());
        log.info("更新用戶(hù): {}", user.getId());
        return userRepository.save(user);
    }
    /**
     * 刪除用戶(hù)(清除所有相關(guān)緩存)
     */
    @Transactional
    @Caching(evict = {
            @CacheEvict(cacheNames = "users", key = "#id"),
            @CacheEvict(cacheNames = "userList", allEntries = true)
    })
    public void deleteUser(Long id) {
        log.info("刪除用戶(hù): {}", id);
        userRepository.deleteById(id);
    }
}

6.6 Controller 層

package com.example.demo.controller;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.getUserById(id);
    }
    @GetMapping("/username/{username}")
    public User getUserByUsername(@PathVariable String username) {
        return userService.getUserByUsername(username);
    }
    @GetMapping
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }
    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.createUser(user);
    }
    @PutMapping
    public User updateUser(@RequestBody User user) {
        return userService.updateUser(user);
    }
    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
    }
}

6.7 測(cè)試驗(yàn)證

測(cè)試類(lèi)

package com.example.demo.service;
import com.example.demo.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class UserServiceTest {
    @Autowired
    private UserService userService;
    @Test
    void testCache() {
        // 第一次查詢(xún)(從數(shù)據(jù)庫(kù))
        User user1 = userService.getUserById(1L);
        assertNotNull(user1);
        // 第二次查詢(xún)(從緩存,不會(huì)打印日志)
        User user2 = userService.getUserById(1L);
        assertEquals(user1.getId(), user2.getId());
    }
}

查看 Redis 緩存

# 連接 Redis
redis-cli
# 查看所有 Key
127.0.0.1:6379> KEYS app:*
1) "app:users::1"
2) "app:usersByName::alice"
3) "app:userList"
# 查看緩存內(nèi)容
127.0.0.1:6379> GET "app:users::1"
"{\"@class\":\"com.example.demo.entity.User\",\"id\":1,\"username\":\"alice\",\"email\":\"alice@example.com\",\"createTime\":[\"java.time.LocalDateTime\",\"2024-01-01T10:00:00\"],\"updateTime\":[\"java.time.LocalDateTime\",\"2024-01-01T10:00:00\"]}"
# 查看 TTL(剩余過(guò)期時(shí)間)
127.0.0.1:6379> TTL "app:users::1"
(integer) 3599  # 還剩 3599 秒(約 1 小時(shí))

7. 高級(jí)特性

7.1 緩存失效策略

主動(dòng)失效策略
策略實(shí)現(xiàn)方式適用場(chǎng)景
TTL 過(guò)期entryTtl(Duration)時(shí)效性數(shù)據(jù)
LRU 淘汰Redis maxmemory-policy內(nèi)存受限
手動(dòng)清除@CacheEvict數(shù)據(jù)更新后
Redis 淘汰策略配置

redis.conf

maxmemory 2gb
maxmemory-policy allkeys-lru  # LRU 算法淘汰任意 Key

常用策略

  • volatile-lru:僅淘汰設(shè)置了過(guò)期時(shí)間的 Key
  • allkeys-lru:淘汰所有 Key(推薦)
  • volatile-ttl:淘汰即將過(guò)期的 Key
  • noeviction:不淘汰,寫(xiě)入失敗

7.2 SpEL 表達(dá)式應(yīng)用

常用 SpEL 表達(dá)式

表達(dá)式說(shuō)明示例
#參數(shù)名方法參數(shù)key = "#id"
#p0, #p1參數(shù)索引key = "#p0"
#result方法返回值unless = "#result == null"
#root.methodName方法名key = "#root.methodName + #id"
#root.targetClass目標(biāo)類(lèi)-

復(fù)雜示例

@Cacheable(
    cacheNames = "users",
    key = "#root.targetClass.simpleName + ':' + #root.methodName + ':' + #id",
    condition = "#id > 0 && #includeDeleted == false",
    unless = "#result == null || #result.id == null"
)
public User getUserById(Long id, boolean includeDeleted) {
    return userRepository.findById(id).orElse(null);
}

7.3 條件緩存(condition/unless)

condition(緩存前置條件)
// 僅緩存 ID > 0 的用戶(hù)
@Cacheable(cacheNames = "users", key = "#id", condition = "#id > 0")
public User getUserById(Long id) {
    return userRepository.findById(id).orElse(null);
}
unless(緩存排除條件)
// 不緩存 null 結(jié)果和管理員用戶(hù)
@Cacheable(
    cacheNames = "users", 
    key = "#id",
    unless = "#result == null || #result.role == 'ADMIN'"
)
public User getUserById(Long id) {
    return userRepository.findById(id).orElse(null);
}

兩者區(qū)別

屬性執(zhí)行時(shí)機(jī)適用場(chǎng)景
condition方法執(zhí)行前根據(jù)參數(shù)決定是否緩存
unless方法執(zhí)行后根據(jù)返回值決定是否緩存

7.4 自定義 CacheResolver

場(chǎng)景:動(dòng)態(tài)選擇緩存名稱(chēng)。

import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.stereotype.Component;
@Component("dynamicCacheResolver")
public class DynamicCacheResolver implements CacheResolver {
    @Autowired
    private CacheManager cacheManager;
    @Override
    public Collection<? extends Cache> resolveCaches(
            CacheOperationInvocationContext<?> context) {
        // 根據(jù)參數(shù)動(dòng)態(tài)選擇緩存名稱(chēng)
        Object[] args = context.getArgs();
        String cacheName = (boolean) args[1] ? "vipUsers" : "normalUsers";
        return Collections.singletonList(cacheManager.getCache(cacheName));
    }
}

使用自定義 Resolver

@Cacheable(cacheResolver = "dynamicCacheResolver", key = "#id")
public User getUserById(Long id, boolean isVip) {
    return userRepository.findById(id).orElse(null);
}

8. 生產(chǎn)實(shí)踐與優(yōu)化

8.1 緩存穿透/擊穿/雪崩的防護(hù)

1?? 緩存穿透(查詢(xún)不存在的數(shù)據(jù))

問(wèn)題:惡意請(qǐng)求不存在的 Key,每次都打到數(shù)據(jù)庫(kù)。

解決方案

// 方案 1:緩存空值
@Cacheable(cacheNames = "users", key = "#id")
public User getUserById(Long id) {
    User user = userRepository.findById(id).orElse(null);
    return user != null ? user : new User(); // 返回空對(duì)象而非 null
}
// 方案 2:布隆過(guò)濾器(需要單獨(dú)實(shí)現(xiàn))
@Autowired
private BloomFilter<Long> userIdBloomFilter;
public User getUserById(Long id) {
    if (!userIdBloomFilter.mightContain(id)) {
        return null; // 直接返回,不查數(shù)據(jù)庫(kù)
    }
    return userRepository.findById(id).orElse(null);
}

配置緩存空值

spring:
  cache:
    redis:
      cache-null-values: true  # 允許緩存 null
2?? 緩存擊穿(熱點(diǎn) Key 過(guò)期)

問(wèn)題:熱點(diǎn)數(shù)據(jù)過(guò)期瞬間,大量請(qǐng)求同時(shí)打到數(shù)據(jù)庫(kù)。

解決方案:使用 sync = true 啟用同步鎖。

@Cacheable(
    cacheNames = "hotUsers", 
    key = "#id",
    sync = true  // ? 只有一個(gè)線程查詢(xún)數(shù)據(jù)庫(kù)
)
public User getHotUser(Long id) {
    log.info("查詢(xún)熱點(diǎn)用戶(hù): {}", id);
    return userRepository.findById(id).orElse(null);
}

原理

請(qǐng)求1 → 未命中 → 獲取鎖 → 查詢(xún)DB → 寫(xiě)緩存 → 釋放鎖
請(qǐng)求2 → 未命中 → 等待鎖 ────────────────────> 讀緩存
請(qǐng)求3 → 未命中 → 等待鎖 ────────────────────> 讀緩存
3?? 緩存雪崩(大量 Key 同時(shí)過(guò)期)

問(wèn)題:大量緩存同時(shí)失效,數(shù)據(jù)庫(kù)瞬間壓力劇增。

解決方案:設(shè)置隨機(jī)過(guò)期時(shí)間。

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(10 + new Random().nextInt(5))); // 10-15 分鐘隨機(jī)
    return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .build();
}

對(duì)比表

問(wèn)題原因解決方案
緩存穿透查詢(xún)不存在的數(shù)據(jù)緩存空值 / 布隆過(guò)濾器
緩存擊穿熱點(diǎn) Key 過(guò)期sync = true
緩存雪崩大量 Key 同時(shí)過(guò)期隨機(jī) TTL

8.2 性能監(jiān)控與指標(biāo)

啟用 Redis 監(jiān)控

application.yml

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,caches
  metrics:
    enable:
      cache: true
查看緩存指標(biāo)

訪問(wèn)端點(diǎn)

# 查看緩存統(tǒng)計(jì)
curl http://localhost:8080/actuator/caches
# 查看緩存命中率
curl http://localhost:8080/actuator/metrics/cache.gets?tag=result:hit
curl http://localhost:8080/actuator/metrics/cache.gets?tag=result:miss
自定義監(jiān)控
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Component;
@Component
public class CacheMetrics {
    @Autowired
    private MeterRegistry meterRegistry;
    public void recordCacheHit(String cacheName) {
        meterRegistry.counter("cache.hit", "cache", cacheName).increment();
    }
    public void recordCacheMiss(String cacheName) {
        meterRegistry.counter("cache.miss", "cache", cacheName).increment();
    }
}

8.3 常見(jiàn)問(wèn)題排查

問(wèn)題 1:緩存未生效

現(xiàn)象:每次調(diào)用都執(zhí)行方法。

排查步驟

  1. 確認(rèn) @EnableCaching 是否添加
  2. 檢查方法是否被 Spring 管理(不能是 private)
  3. 確認(rèn)方法調(diào)用是否通過(guò)代理(同類(lèi)調(diào)用會(huì)失效)

錯(cuò)誤示例

@Service
public class UserService {
    // ? 同類(lèi)調(diào)用,緩存失效
    public void businessMethod() {
        getUserById(1L);  // 直接調(diào)用,不走代理
    }
    @Cacheable(cacheNames = "users", key = "#id")
    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}

正確做法

@Service
public class UserService {
    @Autowired
    private UserService self;  // 注入自己
    public void businessMethod() {
        self.getUserById(1L);  // 通過(guò)代理調(diào)用
    }
}
問(wèn)題 2:序列化異常

現(xiàn)象

SerializationException: Could not read JSON: Unrecognized field...

解決方案

// 實(shí)體類(lèi)添加無(wú)參構(gòu)造器
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    // ...
}
問(wèn)題 3:緩存不過(guò)期

現(xiàn)象:數(shù)據(jù)一直不更新。

排查

# 查看 Key 的 TTL
redis-cli TTL "app:users::1"
(integer) -1  # -1 表示永不過(guò)期

解決:檢查配置是否正確設(shè)置 TTL。

8.4 最佳實(shí)踐建議

? 推薦做法
  1. 合理設(shè)置 TTL:根據(jù)數(shù)據(jù)更新頻率設(shè)置過(guò)期時(shí)間
  2. 使用 JSON 序列化:可讀性好,便于調(diào)試
  3. 啟用 sync = true:防止緩存擊穿
  4. 緩存空值:防止緩存穿透
  5. 監(jiān)控命中率:及時(shí)發(fā)現(xiàn)緩存效率問(wèn)題
? 避免做法
  1. 緩存大對(duì)象:?jiǎn)蝹€(gè) Value 不超過(guò) 1MB
  2. 緩存頻繁變化的數(shù)據(jù):如實(shí)時(shí)庫(kù)存
  3. 同類(lèi)調(diào)用:會(huì)導(dǎo)致緩存失效
  4. 過(guò)度依賴(lài)緩存:數(shù)據(jù)庫(kù)是最終數(shù)據(jù)源
性能優(yōu)化建議
優(yōu)化點(diǎn)建議收益
連接池使用 Lettuce + 連接池減少連接開(kāi)銷(xiāo)
序列化使用 JSON 代替 JDK減少 50% 空間
批量操作使用 Pipeline提升 10 倍吞吐
本地緩存Caffeine + Redis 二級(jí)緩存減少網(wǎng)絡(luò)開(kāi)銷(xiāo)

9. 對(duì)比與擴(kuò)展

9.1 與 EhCache/Caffeine 對(duì)比

特性RedisEhCacheCaffeine
部署模式獨(dú)立服務(wù)嵌入式嵌入式
數(shù)據(jù)共享? 多實(shí)例共享? 單實(shí)例? 單實(shí)例
持久化? 支持?? 有限?
性能網(wǎng)絡(luò)延遲(1-5ms)極快(納秒級(jí))極快(納秒級(jí))
內(nèi)存管理獨(dú)立進(jìn)程占用 JVM 堆占用 JVM 堆
適用場(chǎng)景分布式系統(tǒng)單體應(yīng)用單體應(yīng)用

選擇建議

  • 分布式系統(tǒng) → Redis
  • 單體應(yīng)用 + 小數(shù)據(jù)量 → Caffeine
  • 需要持久化 + 單機(jī) → EhCache

9.2 多級(jí)緩存架構(gòu)

架構(gòu)設(shè)計(jì)

請(qǐng)求 → Caffeine (本地緩存) → Redis (分布式緩存) → 數(shù)據(jù)庫(kù)
       ↑ 命中返回              ↑ 命中返回          ↑ 未命中查詢(xún)

實(shí)現(xiàn)示例

@Configuration
public class MultiLevelCacheConfig {
    @Bean
    @Primary
    public CacheManager cacheManager(
            CacheManager redisCacheManager,
            CacheManager caffeineCacheManager) {
        return new CompositeCacheManager(
                caffeineCacheManager,  // 優(yōu)先查詢(xún)本地緩存
                redisCacheManager      // 本地未命中再查 Redis
        );
    }
}

9.3 進(jìn)階閱讀資源

官方文檔
推薦書(shū)籍
  • 《Redis 設(shè)計(jì)與實(shí)現(xiàn)》- 黃健宏
  • 《Spring Boot 實(shí)戰(zhàn)》- Craig Walls
開(kāi)源項(xiàng)目
  • Redisson:Redis 客戶(hù)端增強(qiáng)工具
  • J2Cache:兩級(jí)緩存框架

?? 總結(jié)

核心要點(diǎn)回顧

  1. Spring Cache 是抽象層,通過(guò)注解實(shí)現(xiàn)聲明式緩存
  2. Redis 是分布式緩存的首選,適合微服務(wù)架構(gòu)
  3. 核心注解@Cacheable(查詢(xún))、@CachePut(更新)、@CacheEvict(清除)
  4. 序列化推薦 Jackson,兼顧性能和可讀性
  5. 防護(hù)三大問(wèn)題:穿透(緩存空值)、擊穿(sync=true)、雪崩(隨機(jī)TTL)

快速檢查清單

? 添加依賴(lài)(spring-boot-starter-data-redis + cache)
? 配置 Redis 連接(application.yml)
? 啟用緩存注解(@EnableCaching)
? 配置序列化器(Jackson JSON)
? 設(shè)置 TTL 過(guò)期時(shí)間
? 添加監(jiān)控指標(biāo)(Actuator)
? 處理緩存穿透/擊穿/雪崩

最后提醒:緩存不是銀彈,合理使用才能發(fā)揮最大價(jià)值。始終以數(shù)據(jù)一致性為第一原則!

到此這篇關(guān)于Spring Cache + Redis 聲明式緩存指南的文章就介紹到這了,更多相關(guān)Spring Cache Redis聲明式緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • mybatis框架xml下trim中的prefix與suffix等標(biāo)簽的用法

    mybatis框架xml下trim中的prefix與suffix等標(biāo)簽的用法

    這篇文章主要介紹了mybatis框架xml下trim中的prefix與suffix等標(biāo)簽的用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • SpringBoot?jwt的token如何刷新

    SpringBoot?jwt的token如何刷新

    這篇文章主要給大家介紹了關(guān)于SpringBoot?jwt的token如何刷新的相關(guān)資料,Json web token(JWT)是為了在網(wǎng)絡(luò)應(yīng)用環(huán)境間傳遞聲明而執(zhí)行的一種基于JSON的開(kāi)放標(biāo)準(zhǔn),需要的朋友可以參考下
    2023-07-07
  • Java基礎(chǔ)學(xué)習(xí)之接口詳解

    Java基礎(chǔ)學(xué)習(xí)之接口詳解

    接口,是Java語(yǔ)言中一種引用類(lèi)型,是方法的集合,如果說(shuō)類(lèi)的內(nèi)部封裝了成員變量、構(gòu)造方法和成員方法,那么接口的內(nèi)部主要就是封裝了方法。本文通過(guò)一些示例詳細(xì)為大家展示了接口的使用,需要的可以參考一下
    2022-10-10
  • 關(guān)于SpringSecurity的基本使用示例

    關(guān)于SpringSecurity的基本使用示例

    這篇文章主要介紹了關(guān)于SpringSecurity的基本使用示例,SpringSecurity 本質(zhì)是一個(gè)過(guò)濾器鏈SpringSecurity 采用的是責(zé)任鏈的設(shè)計(jì)模式,它有一條很長(zhǎng)的過(guò)濾器鏈,需要的朋友可以參考下
    2023-05-05
  • spring AOP代理執(zhí)行@EnableAspectJAutoProxy的exposeProxy屬性詳解

    spring AOP代理執(zhí)行@EnableAspectJAutoProxy的exposeProxy屬性詳解

    這篇文章主要為大家介紹了spring AOP代理執(zhí)行@EnableAspectJAutoProxy的exposeProxy屬性詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • 詳解MyBatis的XML實(shí)現(xiàn)方法(附帶注解方式實(shí)現(xiàn))

    詳解MyBatis的XML實(shí)現(xiàn)方法(附帶注解方式實(shí)現(xiàn))

    這篇文章主要詳細(xì)介紹了MyBatis的XML實(shí)現(xiàn)方法(附帶注解方式實(shí)現(xiàn)),文中通過(guò)代碼示例給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-05-05
  • Mybatis中 SQL語(yǔ)句復(fù)用

    Mybatis中 SQL語(yǔ)句復(fù)用

    這篇文章主要介紹了Mybatis中 SQL語(yǔ)句復(fù)用,需要的朋友可以參考下
    2017-03-03
  • Java騷操作之CountDownLatch代碼詳解

    Java騷操作之CountDownLatch代碼詳解

    這篇文章主要介紹了Java騷操作之CountDownLatch代碼詳解,本文通過(guò)實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-02-02
  • 如何使用 Spring Boot 3.3 和 JdbcTemplate 操作 MySQL 數(shù)據(jù)庫(kù)

    如何使用 Spring Boot 3.3 和 JdbcTemplate

    本文介紹了如何在Spring Boot 3.3項(xiàng)目中使用 JdbcTemplate操作 MySQL數(shù)據(jù)庫(kù),通過(guò)RowMapper接口,我們將查詢(xún)結(jié)果映射為User對(duì)象,感興趣的朋友跟隨小編一起看看吧
    2025-06-06
  • Java 由淺入深帶你掌握?qǐng)D的遍歷

    Java 由淺入深帶你掌握?qǐng)D的遍歷

    圖的遍歷是指,從給定圖中任意指定的頂點(diǎn)(稱(chēng)為初始點(diǎn))出發(fā),按照某種搜索方法沿著圖的邊訪問(wèn)圖中的所有頂點(diǎn),使每個(gè)頂點(diǎn)僅被訪問(wèn)一次,這個(gè)過(guò)程稱(chēng)為圖的遍歷。遍歷過(guò)程中得到的頂點(diǎn)序列稱(chēng)為圖遍歷序列
    2022-03-03

最新評(píng)論

磐安县| 乌海市| 长丰县| 太湖县| 金寨县| 平远县| 台南市| 唐海县| 德兴市| 阿合奇县| 石河子市| 江华| 桐梓县| 湖口县| 丹东市| 鄄城县| 清水县| 五指山市| 滁州市| 盱眙县| 师宗县| 汝城县| 正定县| 陆川县| 济宁市| 黔江区| 泰宁县| 民县| 孟村| 调兵山市| 阳曲县| 寻乌县| 讷河市| 久治县| 江都市| 秦安县| 龙川县| 靖边县| 怀远县| 新蔡县| 长武县|