캐싱은 자주 액세스하는 데이터를 메모리에 저장하고 데이터베이스와 같은 기본 데이터 소스에 대한 부담을 줄여 시스템의 성능과 안정성을 효과적으로 향상시킬 수 있습니다. 다들 프로젝트에 어느 정도 사용해 보셨을 거라 생각하는데, 저희 프로젝트도 예외는 아닙니다. 그런데 최근 회사의 코드를 검토해보니 글이 매우 멍청하고 수준이 낮았습니다.
public User getById(String id) { User user = cache.getUser(); if(user != null) { return user; } // 从数据库获取 user = loadFromDB(id); cahce.put(id, user); return user; }
In 사실, Spring Boot는 캐싱 추상화를 통해 애플리케이션에 캐싱을 쉽게 추가할 수 있는 강력한 기능을 제공합니다. 이 기사에서는 캐싱에 대한 모범 사례를 구현하기 위해 Spring에서 제공하는 다양한 캐시 주석을 사용하는 방법에 대해 설명합니다.
이제 대부분의 프로젝트는 SpringBoot 프로젝트입니다. 캐싱 기능을 활성화하려면 시작 클래스에 @EnableCaching
주석을 추가할 수 있습니다. @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
注解来操作缓存了。
该注解可以将方法运行的结果进行缓存,在缓存时效内再次调用该方法时不会调用方法本身,而是直接从缓存获取结果并返回给调用方。
例子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
出来了, 与 @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
注解一般用于删除缓存数据,相当于缓存使用的是写模式中的失效模式。
@Service public class MyService { @Autowired private MyRepository repository; @CacheEvict(value = "myCache", key = "#id") public void deleteEntityById(Long id) { repository.deleteById(id); } }
@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
@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; } }캐싱을 사용하려면 캐시 관리자 Bean이 필요합니다. 기본적으로
@EnableCaching
은 ConcurrentMapCacheManager
Bean을 등록하며 별도의 Bean은 없습니다. 필수. ConcurrentMapCacheManage
r은 캐싱 메커니즘의 가장 간단한 스레드 안전 구현인 ConcurrentHashMap
의 인스턴스에 값을 저장합니다. 🎜🎜사용자 정의 캐시 관리자🎜🎜기본 캐시 관리자는 jvm 메모리에 저장되기 때문에 요구 사항을 충족할 수 없는데 어떻게 redis에 저장하나요? 이때 사용자 정의 캐시 관리자를 추가해야 합니다. 🎜🎜1. 종속성 추가🎜@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 캐시 관리자 구성🎜
//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); }🎜이제 캐시 관리자가 생겼으니 비즈니스 수준에서 캐시를 어떻게 운영할까요? 🎜🎜
@Cacheable
, @CachePut
또는 @CacheEvict
주석을 사용하여 캐시를 작동할 수 있습니다. 🎜🎜@Cacheable🎜🎜이 주석은 메소드 실행 결과를 캐시할 수 있습니다. 캐시 시간 제한 내에 메소드가 다시 호출되면 메소드 자체는 호출되지 않지만 결과는 캐시에서 직접 얻어서 반환됩니다. 방문객. 🎜🎜🎜🎜예제 1: 캐시 데이터베이스 쿼리의 결과입니다. 🎜//方法调用完成之后,清理所有缓存 @CacheEvict(value="myCache",allEntries=true) public void delectAll() { repository.deleteAll(); } //方法调用之前,清除所有缓存 @CacheEvict(value="myCache",beforeInvocation=true) public void delectAll() { repository.deleteAll(); }🎜이 예에서
@Cacheable
주석은 ID
를 기반으로 데이터베이스에서 getEntityById()
메서드의 결과를 캐시하는 데 사용됩니다. > MyEntity 개체를 검색합니다. 🎜🎜하지만 데이터를 업데이트하면 어떻게 될까요? 오래된 데이터가 아직 캐시에 있나요? 🎜🎜@CachePut🎜🎜 그러다가 @CachePut
이 나왔습니다. @Cacheable
주석과 다른 점은 @CachePut
주석 방식을 사용한다는 것입니다. 실행 이전에 실행한 결과가 캐시에 있는지 확인하는 대신 매번 메소드를 실행하고 실행 결과를 키-값 쌍의 형태로 지정된 캐시에 기록합니다. @CachePut
주석은 일반적으로 캐시 데이터를 업데이트하는 데 사용되며 이는 쓰기 모드에서 이중 쓰기 모드를 사용하는 캐시와 동일합니다. 🎜rrreee🎜@CacheEvict🎜🎜 @CacheEvict
주석이 달린 메서드는 호출 시 저장된 데이터를 캐시에서 제거합니다. @CacheEvict
주석은 일반적으로 캐시된 데이터를 삭제하는 데 사용됩니다. 이는 쓰기 모드에서 실패 모드를 사용하는 캐시와 동일합니다. 🎜🎜🎜rrreee🎜@Caching 🎜 🎜@Caching
주석은 메서드나 클래스에 여러 Spring Cache 관련 주석을 동시에 지정하는 데 사용됩니다. 🎜🎜🎜🎜예제 1: @Caching 주석의 evict
속성은 saveEntity
메소드가 호출될 때 두 캐시가 모두 무효화됨을 지정합니다. 🎜rrreee🎜예 2: getEntityById
메서드를 호출할 때 Spring은 먼저 결과가 myCache
캐시에 캐시되었는지 여부를 확인합니다. 그렇다면 Spring
은 메서드를 실행하는 대신 캐시된 결과를 반환합니다. 결과가 아직 캐시되지 않은 경우 Spring은 메서드를 실행하고 myCache
캐시에 결과를 캐시합니다. 메서드가 실행된 후 Spring은 @CacheEvict
주석에 따라 otherCache
캐시에서 캐시된 결과를 제거합니다. 🎜rrreee🎜예 3: saveData
메서드를 호출할 때 Spring은 먼저 @CacheEvict
주석에 따라 otherCache
캐시에서 데이터를 제거합니다. 그런 다음 Spring은 메서드를 실행하고 결과를 데이터베이스나 외부 API에 저장합니다. 🎜🎜메서드가 실행된 후 Spring은 @CachePut에 따라 <code>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(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 中间件等)。
위 내용은 SpringBoot 프로젝트에서 캐시를 사용하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!