java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring Cache深度解析

Spring Cache 最佳实践总结

作者:廋到被风吹走

SpringCache是Spring框架提供的声明式缓存抽象层,通过少量注解即可为应用添加缓存能力,无需侵入业务代码,本文给大家介绍Spring Cache 最佳实践总结,感兴趣的朋友跟随小编一起看看吧

Spring Cache 深度解析

一、什么是 Spring Cache

Spring Cache 是 Spring 框架提供的声明式缓存抽象层,它通过少量注解即可为应用添加缓存能力,无需侵入业务代码。核心设计目标是解耦缓存实现与业务逻辑,支持在运行时灵活切换 EhCache、Redis、Caffeine、Hazelcast 等缓存实现。

关键特性

二、核心注解与生命周期

1.@Cacheable:查询缓存

作用:方法执行前检查缓存,存在则直接返回,不存在则执行方法并将结果存入缓存。

@Service
public class UserService {
    // 缓存key为用户ID,缓存不存在时查询数据库
    @Cacheable(value = "users", key = "#id")
    public User getUser(Long id) {
        return userMapper.selectById(id); // 仅缓存未命中时执行
    }
    // 支持条件缓存:仅当id > 100时缓存
    @Cacheable(value = "users", key = "#id", condition = "#id > 100")
    public User getVipUser(Long id) {
        return userMapper.selectById(id);
    }
    // 使用SpEL生成key:如 "user:100:admin"
    @Cacheable(value = "users", key = "'user:' + #id + ':' + #type")
    public User getUserByType(Long id, String type) {
        return userMapper.selectByIdAndType(id, type);
    }
}

执行流程

  1. 解析 SpEL 表达式生成缓存 Key
  2. 根据 value/cacheNames 定位缓存对象
  3. 查询缓存:
    • 命中:直接返回缓存值(方法体不执行)
    • 未命中:执行方法 → 将返回值存入缓存 → 返回结果

2.@CachePut:更新缓存

作用无论缓存是否存在,都执行方法,并将结果更新到缓存。适用于创建或更新操作

@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
    userMapper.updateById(user);
    return user; // 返回值会更新到缓存
}

⚠️ 注意@CachePut@Cacheable 不能用在同一方法(逻辑冲突)。

3.@CacheEvict:清除缓存

作用:删除缓存项,通常用于删除或修改操作

// 删除指定key的缓存
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
    userMapper.deleteById(id);
}
// 删除整个缓存区的所有条目
@CacheEvict(value = "users", allEntries = true)
public void clearAllUsers() {
    // 批量删除后的清理操作
}
// 在方法执行前清除缓存(默认是执行后)
@CacheEvict(value = "users", key = "#id", beforeInvocation = true)
public void deleteUserBefore(Long id) {
    userMapper.deleteById(id);
}

4.@Caching:组合操作

作用:单个方法上组合多个缓存操作。

@Caching(
    put = {
        @CachePut(value = "users", key = "#user.id"),
        @CachePut(value = "users", key = "#user.email")
    },
    evict = {
        @CacheEvict(value = "userList", allEntries = true)
    }
)
public User createUser(User user) {
    userMapper.insert(user);
    return user;
}

5.@CacheConfig:类级别统一配置

作用:在类上统一指定缓存名称等公共配置,避免重复书写。

@Service
@CacheConfig(cacheNames = "users")
public class UserService {
    @Cacheable(key = "#id") // 无需重复指定value
    public User getUser(Long id) { /*...*/ }
    @CacheEvict(key = "#id")
    public void deleteUser(Long id) { /*...*/ }
}

三、缓存管理器配置

1. 启用缓存

@SpringBootApplication
@EnableCaching // 启用缓存功能
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

2. 配置 Caffeine 本地缓存(推荐)

@Configuration
public class CacheConfig {
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        // 全局默认配置
        cacheManager.setCaffeine(Caffeine.newBuilder()
            .initialCapacity(100)
            .maximumSize(500)
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .recordStats()); // 开启统计
        // 为特定缓存区定制配置
        cacheManager.registerCustomCache("users", 
            Caffeine.newBuilder()
                .maximumSize(1000)
                .expireAfterAccess(30, TimeUnit.MINUTES)
                .build());
        return cacheManager;
    }
}

3. 配置 Redis 分布式缓存

@Configuration
public class RedisCacheConfig {
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration
            .defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30)) // 默认30分钟过期
            .serializeKeysWith(
                RedisSerializationContext.SerializationPair
                    .fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(
                RedisSerializationContext.SerializationPair
                    .fromSerializer(new GenericJackson2JsonRedisSerializer()));
        return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .withCacheConfiguration("users", 
                RedisCacheConfiguration
                    .defaultCacheConfig()
                    .entryTtl(Duration.ofHours(2))) // users缓存2小时过期
            .build();
    }
}

Spring Boot 自动配置

四、缓存 Key 生成策略

默认策略

自定义 KeyGenerator

@Configuration
public class CacheConfig implements CachingConfigurer {
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getSimpleName())
              .append(":")
              .append(method.getName())
              .append(":");
            // 自定义参数序列化逻辑
            return sb.toString();
        };
    }
}

五、应用场景与解决方案

1. 缓存穿透(Cache Penetration)

场景:查询不存在的数据,大量请求直达数据库。

解决方案

@Cacheable(value = "users", key = "#id", unless = "#result == null")
public User findUser(Long id) {
    return userMapper.selectById(id);
}
// 或使用空值缓存
@Cacheable(value = "users", key = "#id")
public User findUser(Long id) {
    User user = userMapper.selectById(id);
    return user == null ? User.NULL : user; // 缓存空对象
}

2. 缓存击穿(Cache Breakdown)

场景:热点数据过期瞬间,大量请求涌入数据库。

解决方案

// 设置热点数据永不过期 + 后台异步更新
@Cacheable(value = "hotData", key = "#key")
public HotData getHotData(String key) {
    return loadFromDatabase(key);
}
// 或使用分布式锁(Redisson)
@Cacheable(value = "users", key = "#id")
public User getUserWithLock(Long id) {
    RLock lock = redissonClient.getLock("user:lock:" + id);
    try {
        if (lock.tryLock(100, 10, TimeUnit.SECONDS)) {
            return userMapper.selectById(id);
        }
    } finally {
        lock.unlock();
    }
    return null;
}

3. 缓存雪崩(Cache Avalanche)

场景:大量缓存同时过期,数据库压力骤增。

解决方案

# application.yml
spring:
  cache:
    caffeine:
      spec: maximumSize=1000,expireAfterWrite=30m,expireAfterAccess=25m
      # 设置随机过期时间,避免集中失效
      # expireAfterWrite=30m,randomTime=5m

4. 缓存与事务一致性

问题:事务未提交,缓存已更新,导致脏读。

解决方案

@Transactional
@CacheEvict(value = "users", key = "#id", beforeInvocation = true)
public void updateUser(Long id, User user) {
    // beforeInvocation=true 确保事务回滚时缓存也被清除
    userMapper.updateById(user);
}

六、缓存监控与统计

1. 开启 Caffeine 统计

Caffeine.newBuilder()
    .recordStats()
    .build();

2. 暴露监控端点

@Component
public class CacheMetrics {
    @Autowired
    private CacheManager cacheManager;
    @Scheduled(fixedRate = 60000)
    public void printStats() {
        Cache usersCache = cacheManager.getCache("users");
        if (usersCache.getNativeCache() instanceof com.github.benmanes.caffeine.cache.Cache) {
            com.github.benmanes.caffeine.cache.Cache nativeCache = 
                (com.github.benmanes.caffeine.cache.Cache) usersCache.getNativeCache();
            System.out.println("缓存命中率: " + nativeCache.stats().hitRate());
        }
    }
}

Spring Boot Actuator 自动暴露 /actuator/caches 端点,可查看和清除缓存。

七、最佳实践总结

✅推荐使用

  1. 优先本地缓存:Caffeine(性能最优)或 EhCache
  2. 分布式场景:Redis(集群高可用)
  3. Key 设计类名:方法名:参数 格式,如 user:100
  4. TTL 策略:热点数据短过期,冷数据长过期
  5. 异常处理:缓存操作不应影响主流程,捕获异常并降级
@Cacheable(value = "users", key = "#id")
public User getUser(Long id) {
    try {
        return userMapper.selectById(id);
    } catch (Exception e) {
        log.error("查询用户失败", e);
        return User.EMPTY; // 返回空对象避免缓存穿透
    }
}

⚠️注意事项

  1. 缓存 Key 必须唯一:避免不同方法数据覆盖
  2. @CachePut:确保返回值为完整数据,避免缓存不完整对象
  3. 序列化成本:Redis 缓存注意对象序列化开销
  4. 缓存一致性:更新操作必须清除相关缓存
  5. 大对象缓存:评估内存占用,避免 OOM

📊性能对比

缓存类型读取性能写入性能适用场景
Caffeine极高(本地)极高高频读、数据量适中
Redis高(网络)分布式、数据共享
EhCache传统项目、功能丰富

Spring Cache 通过统一的抽象层,让开发者以极简的注解实现强大的缓存能力,是提升应用性能的必备利器。

到此这篇关于Spring Cache 最佳实践总结的文章就介绍到这了,更多相关Spring Cache深度解析内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文