Spring Cache如何让接口性能提升

 更新时间:2026年06月26日 09:24:47   作者:一条泥憨鱼  
本文概述了SpringCache作为缓存抽象层的工作原理及配置方法,强调其解耦业务代码的优势,并提供了解决常见缓存问题的建议,感兴趣的朋友跟随小编一起看看吧

前言:

写业务接口的时候,这种事情天天发生:一个查用户信息的方法,每次请求都去摸数据库。接口调用量一大,数据库就哐哐扛。问题是——用户信息半天都不变一次,这些查询全是白干的。

就是把第一次的结果记下来,下次直接用。Spring Cache 干的就是这个。

它是个缓存抽象层。你用注解告诉 Spring「这个方法的返回值可以缓存」,具体怎么存、存哪,不用管。

它不是缓存,是缓存的遥控器

新手容易搞混:Spring Cache 不是 Redis,不是 Caffeine,不是任何一种具体的缓存技术。它是个统一接口层,背后可以接不同的缓存实现

- 本地:ConcurrentMapCache(默认,底层是 Map,只适合测试)、Caffeine(正经的高性能本地缓存)
- 分布式:Redis(生产环境主力,多实例共享)

关键在于——业务代码不用动。换个 CacheManager 配置,就能从本地缓存切到 Redis。解耦这件事,才是 Spring Cache 真正值钱的地方。

怎么开

Spring Boot 项目加两个依赖(Redis 为例):

<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>

启动类上拍一个注解,总开关就打开了:

@SpringBootApplication
@EnableCaching
public class CacheDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(CacheDemoApplication.class, args);
    }
}

四个核心注解

@Cacheable——查

用得最多的一个。先去缓存里找,有就直接返回;没有就执行方法,把结果塞进缓存。

@Cacheable(value = "userCache", key = "#id")
}

第一次调 getUserById(1L) 打日志、查库。第二次同样参数,日志不打了,直接走缓存。

@CachePut——更新时刷新

跟 @Cacheable 的区别:它每次都会执行方法,只是顺手把返回值写回缓存。更新操作用这个:

@CachePut(value = "userCache", key = "#user.id")
public User updateUser(User user) {
    userMapper.updateById(user);
    return user;
}

@CacheEvict——删

数据删了、失效了,缓存也得跟着清。不然数据库已经变了,缓存里还是老数据:

@CacheEvict(value = "userCache", key = "#id")
public void deleteUser(Long id) {
    userMapper.deleteById(id);
}

也可以直接把整个缓存分组清掉:

@CacheEvict(value = "userCache", allEntries = true)
public void clearAllUserCache() {
}

@Caching——组合

一个方法要同时操作多个缓存,用这个拼起来:

@Caching(
    put = { @CachePut(value = "userCache", key = "#user.id") },
    evict = { @CacheEvict(value = "userListCache", allEntries = true) }
)
public User saveAndRefresh(User user) {
    userMapper.insert(user);
    return user;
}

底层就是 AOP

跟 @Transactional 一模一样——动态代理

Spring 检测到 Bean 的方法上有缓存注解,就给这个 Bean 包一层代理。调用链路是这样的:

1. 拿 key 去 CacheManager 找缓存
2. 命中→直接返回,原方法不执行
3. 没命中→执行原方法,结果丢进缓存

这也是那个经典坑的来源:同类内部调用,缓存注解直接不生效。

public void doSomething() {
    this.getUserById(1L); // this 调用,不是代理对象,AOP 被绕过去了
}
@Cacheable(value = "userCache", key = "#id")
public User getUserById(Long id) {
    return userMapper.selectById(id);
}

this.getUserById() 跳过了代理,AOP 根本没有介入的机会。修法通常是拆到另一个 Bean,或者注入自己的代理对象(AopContext.currentProxy())。

几个实用点

条件缓存:condition 和 unless

// condition:执行前判断,满足才缓存
@Cacheable(value = "userCache", key = "#id", condition = "#id > 0")
// unless:执行后判断,满足就不缓存(能拿到返回值 #result)
@Cacheable(value = "userCache", key = "#id", unless = "#result == null")

unless 特别有用。null 结果不缓存,不然缓存穿透问题会放大。

自定义 key

参数多的时候用 SpEL 拼:

@Cacheable(value = "orderCache", key = "#userId + '_' + #orderId")
public Order getOrder(Long userId, Long orderId) {
    return orderMapper.selectOrder(userId, orderId);
}

过期时间(Redis)

注解上没法直接设,要在 CacheManager 配置里统一处理:

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                    .fromSerializer(new GenericJackson2JsonRedisSerializer()));
    return RedisCacheManager.builder(factory).cacheDefaults(config).build();
}

避坑清单

1. 缓存穿透:

大量请求查不存在的数据,每次都绕过缓存打到 DB。用 unless 缓存空对象,或者上前置的布隆过滤器。

2. 缓存雪崩:

一堆 key 同时过期,瞬间流量全压到数据库。TTL 加随机偏移,别让它们集体去世。

3. 数据一致性:

更新数据库忘了清缓存,或者顺序搞反了。常规做法是先更新数据库再删缓存(反过来会有并发问题)。

4. 同类自调用失效:

上面说过了,AOP 只在外部调用时拦截。

总结

Spring Cache 把「要不要缓存」「用什么缓存」拆开了。业务代码只需要几个注解,底层从 ConcurrentMap 换到 Redis 一行业务代码都不用改。理解了 AOP 代理这件事,大部分坑就能绕开。

到此这篇关于Spring Cache如何让接口性能提升的文章就介绍到这了,更多相关Spring Cache接口性能内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring LDAP目录服务的使用示例

    Spring LDAP目录服务的使用示例

    本文主要介绍了Spring LDAP目录服务的使用示例
    2025-04-04
  • idea如何配置springboot热部署

    idea如何配置springboot热部署

    文章介绍了如何在不同版本的IntelliJ IDEA中配置静态和动态编译,并提供了触发热部署的方法
    2025-01-01
  • Hibernate悲观锁和乐观锁实例详解

    Hibernate悲观锁和乐观锁实例详解

    这篇文章主要介绍了Hibernate悲观锁和乐观锁实例详解,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-02-02
  • Java如何发起http请求的实现(GET/POST)

    Java如何发起http请求的实现(GET/POST)

    这篇文章主要介绍了Java如何发起http请求的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • Java中HashMap如何解决哈希冲突

    Java中HashMap如何解决哈希冲突

    本文主要介绍了Java中HashMap如何解决哈希冲突,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • 解决Error:Java:无效的源发行版:14问题

    解决Error:Java:无效的源发行版:14问题

    在项目开发中,版本不一致常见问题,首先,应检查本地JDK版本,使用命令java-version,其次,核对项目及模块版本,若有不一致,通过修改pom.xml文件同步版本,重新下载依赖即可解决问题,这种方法简单有效,适用于多种开发环境
    2024-10-10
  • java中random的用法小结

    java中random的用法小结

    这篇文章主要介绍了java中random的用法详解,主要包括java.lang.Math.random()方法的用法及java.util.Random类用法,本文通过示例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-06-06
  • Java自定义注解实现数据脱敏

    Java自定义注解实现数据脱敏

    在实际开发中经常会遇到有一些信息不能全部展示用户,需要隐藏(可以叫脱敏),所以本文为大家分享了利用自定义注解实现数据脱敏的示例代码,需要的可以参考下
    2023-07-07
  • JAVA中反射机制和模块化的深入讲解

    JAVA中反射机制和模块化的深入讲解

    很多刚学Java反射的同学可能对反射技术一头雾水,为什么要学习反射,学习反射有什么作用,下面这篇文章主要给大家介绍了关于JAVA中反射机制和模块化的相关资料,需要的朋友可以参考下
    2021-09-09
  • springboot配置mongodb连接池的方法步骤

    springboot配置mongodb连接池的方法步骤

    这篇文章主要介绍了springboot配置mongodb连接池的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01

最新评论