SpringBoot项目中怎么使用缓存Cache
前言
缓存可以通过将经常访问的数据存储在内存中,减少底层数据源如数据库的压力,从而有效提高系统的性能和稳定性。我想大家的项目中或多或少都有使用过,我们项目也不例外,但是最近在review公司的代码的时候写的很蠢且low, 大致写法如下:
public User getById(String id) { User user = cache.getUser(); if(user != null) { return user; } // 从数据库获取 user = loadFromDB(id); cahce.put(id, user); return user; }
其实Spring Boot 提供了强大的缓存抽象,可以轻松地向您的应用程序添加缓存。本文就讲讲如何使用 Spring 提供的不同缓存注解实现缓存的最佳实践。
启用缓存@EnableCaching
现在大部分项目都是是SpringBoot项目,我们可以在启动类添加注解@EnableCaching
来开启缓存功能。
@SpringBootApplication @EnableCaching public class SpringCacheApp { public static void main(String[] args) { SpringApplication.run(Cache.class, args); } }
既然要能使用缓存,就需要有一个缓存管理器Bean,默认情况下,@EnableCaching
将注册一个ConcurrentMapCacheManager
的Bean,不需要单独的 bean 声明。ConcurrentMapCacheManage
r将值存储在ConcurrentHashMap
的实例中,这是缓存机制的最简单的线程安全实现。
自定义缓存管理器
默认的缓存管理器并不能满足需求,因为她是存储在jvm内存中的,那么如何存储到redis中呢?这时候需要添加自定义的缓存管理器。
1.添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2.配置Redis缓存管理器
@Configuration @EnableCaching public class CacheConfig { @Bean public RedisConnectionFactory redisConnectionFactory() { return new LettuceConnectionFactory(); } @Bean public CacheManager cacheManager() { RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() .disableCachingNullValues() .serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory()) .cacheDefaults(redisCacheConfiguration) .build(); return redisCacheManager; } }
现在有了缓存管理器以后,我们如何在业务层面操作缓存呢?
我们可以使用@Cacheable
、@CachePut
或@CacheEvict
注解来操作缓存了。
@Cacheable
该注解可以将方法运行的结果进行缓存,在缓存时效内再次调用该方法时不会调用方法本身,而是直接从缓存获取结果并返回给调用方。
例子1:缓存数据库查询的结果。
@Service public class MyService { @Autowired private MyRepository repository; @Cacheable(value = "myCache", key = "#id") public MyEntity getEntityById(Long id) { return repository.findById(id).orElse(null); } }
在此示例中,@Cacheable
注解用于缓存 getEntityById()
方法的结果,该方法根据其 ID
从数据库中检索 MyEntity 对象。
但是如果我们更新数据呢?旧数据仍然在缓存中?
@CachePut
然后@CachePut
出来了, 与 @Cacheable
注解不同的是使用 @CachePut
注解标注的方法,在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式写入指定的缓存中。@CachePut
注解一般用于更新缓存数据,相当于缓存使用的是写模式中的双写模式。
@Service public class MyService { @Autowired private MyRepository repository; @CachePut(value = "myCache", key = "#entity.id") public void saveEntity(MyEntity entity) { repository.save(entity); } }
@CacheEvict
标注了 @CacheEvict
注解的方法在被调用时,会从缓存中移除已存储的数据。@CacheEvict
注解一般用于删除缓存数据,相当于缓存使用的是写模式中的失效模式。
@Service public class MyService { @Autowired private MyRepository repository; @CacheEvict(value = "myCache", key = "#id") public void deleteEntityById(Long id) { repository.deleteById(id); } }
@Caching
@Caching
注解用于在一个方法或者类上,同时指定多个 Spring Cache 相关的注解。
例子1:@Caching
注解中的evict
属性指定在调用方法 saveEntity
时失效两个缓存。
@Service public class MyService { @Autowired private MyRepository repository; @Cacheable(value = "myCache", key = "#id") public MyEntity getEntityById(Long id) { return repository.findById(id).orElse(null); } @Caching(evict = { @CacheEvict(value = "myCache", key = "#entity.id"), @CacheEvict(value = "otherCache", key = "#entity.id") }) public void saveEntity(MyEntity entity) { repository.save(entity); } }
例子2:调用getEntityById
方法时,Spring会先检查结果是否已经缓存在myCache
缓存中。如果是,Spring
将返回缓存的结果而不是执行该方法。如果结果尚未缓存,Spring 将执行该方法并将结果缓存在 myCache
缓存中。方法执行后,Spring会根据@CacheEvict
注解从otherCache
缓存中移除缓存结果。
@Service public class MyService { @Caching( cacheable = { @Cacheable(value = "myCache", key = "#id") }, evict = { @CacheEvict(value = "otherCache", key = "#id") } ) public MyEntity getEntityById(Long id) { return repository.findById(id).orElse(null); } }
例子3:当调用saveData
方法时,Spring会根据@CacheEvict
注解先从otherCache
缓存中移除数据。然后,Spring 将执行该方法并将结果保存到数据库或外部 API。
方法执行后,Spring 会根据@CachePut
注解将结果添加到 myCache
、myOtherCache
和 myThirdCache
缓存中。Spring 还将根据@Cacheable
注解检查结果是否已缓存在 myFourthCache
和 myFifthCache
缓存中。如果结果尚未缓存,Spring 会将结果缓存在适当的缓存中。如果结果已经被缓存,Spring 将返回缓存的结果,而不是再次执行该方法。
@Service public class MyService { @Caching( put = { @CachePut(value = "myCache", key = "#result.id"), @CachePut(value = "myOtherCache", key = "#result.id"), @CachePut(value = "myThirdCache", key = "#result.name") }, evict = { @CacheEvict(value = "otherCache", key = "#id") }, cacheable = { @Cacheable(value = "myFourthCache", key = "#id"), @Cacheable(value = "myFifthCache", key = "#result.id") } ) public MyEntity saveData(Long id, String name) { // Code to save data to a database or external API MyEntity entity = new MyEntity(id, name); return entity; } }
@CacheConfig
通过@CacheConfig
注解,我们可以将一些缓存配置简化到类级别的一个地方,这样我们就不必多次声明相关值:
@CacheConfig(cacheNames={"myCache"}) @Service public class MyService { @Autowired private MyRepository repository; @Cacheable(key = "#id") public MyEntity getEntityById(Long id) { return repository.findById(id).orElse(null); } @CachePut(key = "#entity.id") public void saveEntity(MyEntity entity) { repository.save(entity); } @CacheEvict(key = "#id") public void deleteEntityById(Long id) { repository.deleteById(id); } }
Condition & Unless
condition
作用:指定缓存的条件(满足什么条件才缓存),可用SpEL
表达式(如#id>0
,表示当入参 id 大于 0 时才缓存)unless
作用 : 否定缓存,即满足unless
指定的条件时,方法的结果不进行缓存,使用unless
时可以在调用的方法获取到结果之后再进行判断(如 #result == null,表示如果结果为 null 时不缓存)
//when id >10, the @CachePut works. @CachePut(key = "#entity.id", condition="#entity.id > 10") public void saveEntity(MyEntity entity) { repository.save(entity); } //when result != null, the @CachePut works. @CachePut(key = "#id", condition="#result == null") public void saveEntity1(MyEntity entity) { repository.save(entity); }
清理全部缓存
通过allEntries
、beforeInvocation
属性可以来清除全部缓存数据,不过allEntries
是方法调用后清理,beforeInvocation
是方法调用前清理。
//方法调用完成之后,清理所有缓存 @CacheEvict(value="myCache",allEntries=true) public void delectAll() { repository.deleteAll(); } //方法调用之前,清除所有缓存 @CacheEvict(value="myCache",beforeInvocation=true) public void delectAll() { repository.deleteAll(); }
SpEL表达式
Spring Cache注解中频繁用到SpEL表达式,那么具体如何使用呢?
SpEL 表达式的语法
Spring Cache可用的变量
最佳实践
通过Spring
缓存注解可以快速优雅地在我们项目中实现缓存的操作,但是在双写模式或者失效模式下,可能会出现缓存数据一致性问题(读取到脏数据),Spring Cache
暂时没办法解决。最后我们再总结下Spring Cache使用的一些最佳实践。
只缓存经常读取的数据:缓存可以显着提高性能,但只缓存经常访问的数据很重要。很少或从不访问的缓存数据会占用宝贵的内存资源,从而导致性能问题。
根据应用程序的特定需求选择合适的缓存提供程序和策略。
SpringBoot
支持多种缓存提供程序,包括Ehcache
、Hazelcast
和Redis
。使用缓存时请注意潜在的线程安全问题。对缓存的并发访问可能会导致数据不一致或不正确,因此选择线程安全的缓存提供程序并在必要时使用适当的同步机制非常重要。
避免过度缓存。缓存对于提高性能很有用,但过多的缓存实际上会消耗宝贵的内存资源,从而损害性能。在缓存频繁使用的数据和允许垃圾收集不常用的数据之间取得平衡很重要。
使用适当的缓存逐出策略。使用缓存时,重要的是定义适当的缓存逐出策略以确保在必要时从缓存中删除旧的或陈旧的数据。
使用适当的缓存键设计。缓存键对于每个数据项都应该是唯一的,并且应该考虑可能影响缓存数据的任何相关参数,例如用户 ID、时间或位置。
常规数据(读多写少、即时性与一致性要求不高的数据)完全可以使用 Spring Cache,至于写模式下缓存数据一致性问题的解决,只要缓存数据有设置过期时间就足够了。
特殊数据(读多写多、即时性与一致性要求非常高的数据),不能使用 Spring Cache,建议考虑特殊的设计(例如使用 Cancal 中间件等)。
以上是SpringBoot项目中怎么使用缓存Cache的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

热门话题

Jasypt介绍Jasypt是一个java库,它允许开发员以最少的努力为他/她的项目添加基本的加密功能,并且不需要对加密工作原理有深入的了解用于单向和双向加密的高安全性、基于标准的加密技术。加密密码,文本,数字,二进制文件...适合集成到基于Spring的应用程序中,开放API,用于任何JCE提供程序...添加如下依赖:com.github.ulisesbocchiojasypt-spring-boot-starter2.1.1Jasypt好处保护我们的系统安全,即使代码泄露,也可以保证数据源的

使用场景1、下单成功,30分钟未支付。支付超时,自动取消订单2、订单签收,签收后7天未进行评价。订单超时未评价,系统默认好评3、下单成功,商家5分钟未接单,订单取消4、配送超时,推送短信提醒……对于延时比较长的场景、实时性不高的场景,我们可以采用任务调度的方式定时轮询处理。如:xxl-job今天我们采

一、Redis实现分布式锁原理为什么需要分布式锁在聊分布式锁之前,有必要先解释一下,为什么需要分布式锁。与分布式锁相对就的是单机锁,我们在写多线程程序时,避免同时操作一个共享变量产生数据问题,通常会使用一把锁来互斥以保证共享变量的正确性,其使用范围是在同一个进程中。如果换做是多个进程,需要同时操作一个共享资源,如何互斥呢?现在的业务应用通常是微服务架构,这也意味着一个应用会部署多个进程,多个进程如果需要修改MySQL中的同一行记录,为了避免操作乱序导致脏数据,此时就需要引入分布式锁了。想要实现分

springboot读取文件,打成jar包后访问不到最新开发出现一种情况,springboot打成jar包后读取不到文件,原因是打包之后,文件的虚拟路径是无效的,只能通过流去读取。文件在resources下publicvoidtest(){Listnames=newArrayList();InputStreamReaderread=null;try{ClassPathResourceresource=newClassPathResource("name.txt");Input

事情其实是这样的,当时领导交给我一个perf硬件性能监视的任务,在使用perf的过程中,输入命令perf list,我看到了以下信息:我的任务就要让这些cache事件能够正常计数,但关键是,我根本不知道这些misses、loads是什么意思。

SpringBoot和SpringMVC都是Java开发中常用的框架,但它们之间有一些明显的差异。本文将探究这两个框架的特点和用途,并对它们的差异进行比较。首先,我们来了解一下SpringBoot。SpringBoot是由Pivotal团队开发的,它旨在简化基于Spring框架的应用程序的创建和部署。它提供了一种快速、轻量级的方式来构建独立的、可执行

在Springboot+Mybatis-plus不使用SQL语句进行多表添加操作我所遇到的问题准备工作在测试环境下模拟思维分解一下:创建出一个带有参数的BrandDTO对象模拟对后台传递参数我所遇到的问题我们都知道,在我们使用Mybatis-plus中进行多表操作是极其困难的,如果你不使用Mybatis-plus-join这一类的工具,你只能去配置对应的Mapper.xml文件,配置又臭又长的ResultMap,然后再去写对应的sql语句,这种方法虽然看上去很麻烦,但具有很高的灵活性,可以让我们

1、自定义RedisTemplate1.1、RedisAPI默认序列化机制基于API的Redis缓存实现是使用RedisTemplate模板进行数据缓存操作的,这里打开RedisTemplate类,查看该类的源码信息publicclassRedisTemplateextendsRedisAccessorimplementsRedisOperations,BeanClassLoaderAware{//声明了key、value的各种序列化方式,初始值为空@NullableprivateRedisSe
