缓存可以通过将经常访问的数据存储在内存中,减少底层数据源如数据库的压力,从而有效提高系统的性能和稳定性。我想大家的项目中或多或少都有使用过,我们项目也不例外,但是最近在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 提供的不同缓存注解实现缓存的最佳实践。
现在大部分项目都是是SpringBoot项目,我们可以在启动类添加注解@EnableCaching来开启缓存功能。
@SpringBootApplication
@EnableCaching
public class SpringCacheApp {
public static void main(String[] args) {
SpringApplication.run(Cache.class, args);
}
}
复制代码
既然要能使用缓存,就需要有一个缓存管理器Bean,默认情况下,@EnableCaching 将注册一个ConcurrentMapCacheManager的Bean,不需要单独的 bean 声明。ConcurrentMapCacheManager将值存储在ConcurrentHashMap的实例中,这是缓存机制的最简单的线程安全实现。
默认的缓存管理器并不能满足需求,因为她是存储在jvm内存中的,那么如何存储到redis中呢?这时候需要添加自定义的缓存管理器。
org.springframework.boot
spring-boot-starter-data-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 缓存中。如果结果尚未缓存,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);
}
}
复制代码
//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使用的一些最佳实践。
页面更新:2024-04-06
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号