Caching kann die Leistung und Stabilität des Systems effektiv verbessern, indem häufig abgerufene Daten im Speicher gespeichert werden, wodurch der Druck auf zugrunde liegende Datenquellen wie Datenbanken verringert wird. Ich denke, jeder hat es mehr oder weniger in seinen Projekten verwendet, und unser Projekt ist keine Ausnahme. Als ich jedoch kürzlich den Code des Unternehmens überprüfte, war der Text sehr dumm und niedrig. Der grobe Text ist wie folgt.
public User getById(String id) { User user = cache.getUser(); if(user != null) { return user; } // 从数据库获取 user = loadFromDB(id); cahce.put(id, user); return user; }Tatsächlich bietet Spring Boot eine leistungsstarke Cache-Abstraktion, die das Hinzufügen von Cache zu Ihrer Anwendung vereinfacht. In diesem Artikel wird erläutert, wie Sie die verschiedenen von Spring bereitgestellten Cache-Anmerkungen verwenden, um die Best Practices für das Caching zu implementieren. Caching aktivieren @EnableCachingDa es sich bei den meisten Projekten um SpringBoot-Projekte handelt, können wir der Startup-Klasse die Annotation
@EnableCaching
hinzufügen, um die Caching-Funktion zu aktivieren . @SpringBootApplication @EnableCaching public class SpringCacheApp { public static void main(String[] args) { SpringApplication.run(Cache.class, args); } }
@EnableCaching
来开启缓存功能。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
既然要能使用缓存,就需要有一个缓存管理器Bean,默认情况下,@EnableCaching
将注册一个ConcurrentMapCacheManager
的Bean,不需要单独的 bean 声明。ConcurrentMapCacheManage
r将值存储在ConcurrentHashMap
的实例中,这是缓存机制的最简单的线程安全实现。
默认的缓存管理器并不能满足需求,因为她是存储在jvm内存中的,那么如何存储到redis中呢?这时候需要添加自定义的缓存管理器。
1.添加依赖
@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; } }
2.配置Redis缓存管理器
@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
、@CachePut
或@CacheEvict
注解来操作缓存了。
该注解可以将方法运行的结果进行缓存,在缓存时效内再次调用该方法时不会调用方法本身,而是直接从缓存获取结果并返回给调用方。
例子1:缓存数据库查询的结果。
@Service public class MyService { @Autowired private MyRepository repository; @CachePut(value = "myCache", key = "#entity.id") public void saveEntity(MyEntity entity) { repository.save(entity); } }
在此示例中,@Cacheable
注解用于缓存 getEntityById()
方法的结果,该方法根据其 ID
从数据库中检索 MyEntity 对象。
但是如果我们更新数据呢?旧数据仍然在缓存中?
然后@CachePut
出来了, 与 @Cacheable
注解不同的是使用 @CachePut
注解标注的方法,在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式写入指定的缓存中。@CachePut
注解一般用于更新缓存数据,相当于缓存使用的是写模式中的双写模式。
@Service public class MyService { @Autowired private MyRepository repository; @CacheEvict(value = "myCache", key = "#id") public void deleteEntityById(Long id) { repository.deleteById(id); } }
标注了 @CacheEvict
注解的方法在被调用时,会从缓存中移除已存储的数据。@CacheEvict
注解一般用于删除缓存数据,相当于缓存使用的是写模式中的失效模式。
@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); } }
@Caching
注解用于在一个方法或者类上,同时指定多个 Spring Cache 相关的注解。
例子1:@Caching
注解中的evict
属性指定在调用方法 saveEntity
时失效两个缓存。
@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); } }
例子2:调用getEntityById
方法时,Spring会先检查结果是否已经缓存在myCache
缓存中。如果是,Spring
将返回缓存的结果而不是执行该方法。如果结果尚未缓存,Spring 将执行该方法并将结果缓存在 myCache
缓存中。方法执行后,Spring会根据@CacheEvict
注解从otherCache
缓存中移除缓存结果。
@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; } }
例子3:当调用saveData
方法时,Spring会根据@CacheEvict
注解先从otherCache
缓存中移除数据。然后,Spring 将执行该方法并将结果保存到数据库或外部 API。
方法执行后,Spring 会根据@CachePut
注解将结果添加到 myCache
、myOtherCache
和 myThirdCache
缓存中。Spring 还将根据@Cacheable
注解检查结果是否已缓存在 myFourthCache
和 myFifthCache
Da Sie den Cache verwenden möchten, benötigen Sie eine Cache-Manager-Bean. Standardmäßig registriert @EnableCaching
einen ConcurrentMapCacheManager
Bean, es ist keine separate Bean-Deklaration erforderlich. ConcurrentMapCacheManage
r speichert Werte in einer Instanz von ConcurrentHashMap
, der einfachsten threadsicheren Implementierung des Caching-Mechanismus.
@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); } }#🎜🎜#2. Redis-Cache-Manager konfigurieren #🎜🎜#
//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); }#🎜🎜# Wie machen wir das jetzt, da wir den Cache-Manager haben? auf der Geschäftsebene? Was ist mit dem operativen Cache? #🎜🎜##🎜🎜#Wir können die Annotation
@Cacheable
, @CachePut
oder @CacheEvict
verwenden, um den Cache zu betreiben. #🎜🎜##🎜🎜#@Cacheable#🎜🎜##🎜🎜#Diese Annotation kann die Ergebnisse der Methodenausführung innerhalb des Cache-Zeitlimits erneut aufrufen, die Methode selbst wird jedoch nicht aufgerufen direkt aus dem Cache abgerufen werden. Das Ergebnis wird an den Aufrufer zurückgegeben. #🎜🎜##🎜🎜# #🎜🎜##🎜🎜#Beispiel 1: Zwischenspeichern der Ergebnisse von Datenbankabfragen. #🎜🎜#//方法调用完成之后,清理所有缓存 @CacheEvict(value="myCache",allEntries=true) public void delectAll() { repository.deleteAll(); } //方法调用之前,清除所有缓存 @CacheEvict(value="myCache",beforeInvocation=true) public void delectAll() { repository.deleteAll(); }#🎜🎜#In diesem Beispiel wird die Annotation
@Cacheable
verwendet, um die Ergebnisse der Methode getEntityById()
basierend auf ihrer ID zwischenzuspeichern
Rufen Sie das MyEntity-Objekt aus der Datenbank ab. #🎜🎜##🎜🎜#Aber was ist, wenn wir die Daten aktualisieren? Alte Daten noch im Cache? #🎜🎜##🎜🎜#@CachePut#🎜🎜##🎜🎜# Dann kam @CachePut
heraus, und der Unterschied zur Annotation @Cacheable
besteht darin, @ Die mit der Annotation CachePut markierte Methode prüft vor der Ausführung nicht, ob zuvor ausgeführte Ergebnisse im Cache vorhanden sind. Stattdessen wird die Methode jedes Mal ausgeführt und die Ausführungsergebnisse werden in das im Formular angegebene geschrieben von Schlüssel-Wert-Paaren. Die Annotation @CachePut
wird im Allgemeinen zum Aktualisieren von Cache-Daten verwendet. Dies entspricht der Verwendung des Double-Write-Modus im Cache im Schreibmodus. #🎜🎜#rrreee#🎜🎜#@CacheEvict#🎜🎜##🎜🎜#Die mit der Annotation @CacheEvict
gekennzeichnete Methode entfernt die gespeicherten Daten aus dem Cache, wenn sie aufgerufen wird. Die Annotation @CacheEvict
wird im Allgemeinen zum Löschen zwischengespeicherter Daten verwendet. Dies entspricht der Verwendung des Fehlermodus im Cache im Schreibmodus. #🎜🎜##🎜🎜# #🎜🎜#rrreee#🎜🎜#@Caching#🎜🎜##🎜🎜#@Caching
Annotationen werden verwendet, um mehrere Spring Cache-bezogene Annotationen für eine Methode oder Klasse gleichzeitig anzugeben. #🎜🎜##🎜🎜# #🎜🎜##🎜🎜#Beispiel 1: Das Attribut evict
in der Annotation @Caching
gibt an, dass zwei Caches ungültig werden, wenn die Methode saveEntity code> wird aufgerufen. #🎜🎜#rrreee#🎜🎜#Beispiel 2: Beim Aufruf der Methode <code>getEntityById
prüft Spring zunächst, ob das Ergebnis im Cache myCache
zwischengespeichert wurde. Wenn ja, gibt Spring
das zwischengespeicherte Ergebnis zurück, anstatt die Methode auszuführen. Wenn das Ergebnis noch nicht zwischengespeichert ist, führt Spring die Methode aus und speichert das Ergebnis im Cache myCache
. Nachdem die Methode ausgeführt wurde, entfernt Spring das zwischengespeicherte Ergebnis gemäß der Annotation @CacheEvict
aus dem Cache otherCache
. #🎜🎜#rrreee#🎜🎜#Beispiel 3: Wenn die Methode saveData
aufgerufen wird, speichert Spring sie zuerst aus dem otherCache
entsprechend dem @CacheEvict Code> Anmerkung Daten entfernen. Spring führt dann die Methode aus und speichert die Ergebnisse in einer Datenbank oder einer externen API. #🎜🎜##🎜🎜#Nachdem die Methode ausgeführt wurde, fügt Spring die Ergebnisse gemäß <code>@CachePutmyCache
, myOtherCache
und hinzu /code> annotation myThirdCache
ist im Cache. Spring prüft außerdem anhand der Annotation @Cacheable
, ob das Ergebnis in den Caches myFourthCache
und myFifthCache
zwischengespeichert wurde. Wenn das Ergebnis noch nicht zwischengespeichert ist, speichert Spring das Ergebnis im entsprechenden Cache. Wenn das Ergebnis bereits zwischengespeichert wurde, gibt Spring das zwischengespeicherte Ergebnis zurück, anstatt die Methode erneut auszuführen. #🎜🎜#@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(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
作用:指定缓存的条件(满足什么条件才缓存),可用 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(); }
Spring Cache注解中频繁用到SpEL表达式,那么具体如何使用呢?
SpEL 表达式的语法
Spring Cache可用的变量
通过Spring
缓存注解可以快速优雅地在我们项目中实现缓存的操作,但是在双写模式或者失效模式下,可能会出现缓存数据一致性问题(读取到脏数据),Spring Cache
暂时没办法解决。最后我们再总结下Spring Cache使用的一些最佳实践。
只缓存经常读取的数据:缓存可以显着提高性能,但只缓存经常访问的数据很重要。很少或从不访问的缓存数据会占用宝贵的内存资源,从而导致性能问题。
根据应用程序的特定需求选择合适的缓存提供程序和策略。SpringBoot
支持多种缓存提供程序,包括 Ehcache
、Hazelcast
和 Redis
。
使用缓存时请注意潜在的线程安全问题。对缓存的并发访问可能会导致数据不一致或不正确,因此选择线程安全的缓存提供程序并在必要时使用适当的同步机制非常重要。
避免过度缓存。缓存对于提高性能很有用,但过多的缓存实际上会消耗宝贵的内存资源,从而损害性能。在缓存频繁使用的数据和允许垃圾收集不常用的数据之间取得平衡很重要。
使用适当的缓存逐出策略。使用缓存时,重要的是定义适当的缓存逐出策略以确保在必要时从缓存中删除旧的或陈旧的数据。
使用适当的缓存键设计。缓存键对于每个数据项都应该是唯一的,并且应该考虑可能影响缓存数据的任何相关参数,例如用户 ID、时间或位置。
常规数据(读多写少、即时性与一致性要求不高的数据)完全可以使用 Spring Cache,至于写模式下缓存数据一致性问题的解决,只要缓存数据有设置过期时间就足够了。
特殊数据(读多写多、即时性与一致性要求非常高的数据),不能使用 Spring Cache,建议考虑特殊的设计(例如使用 Cancal 中间件等)。
Das obige ist der detaillierte Inhalt vonSo verwenden Sie den Cache im SpringBoot-Projekt. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!