让我们通过一个例子来学习如何使用@Cacheable注解在Spring Boot中实现缓存机制。
缓存是一个概念,它通过将最常用的数据副本存储在一个临时但快速的存储空间来提高响应时间。在这篇文章中,我们将通过一个例子来了解如何为一个spring boot应用程序启用缓存。
想象一下,你有一个购物网站,有很多产品。在理想情况下,可能会有大量的新订单/购买的流量。然而,关于产品细节的信息几乎没有变化。在这种情况下,不要每次都从数据库中加载价格和产品信息是合理的。
首先,你需要在你的Spring Boot应用程序中添加**@EnableCaching**注解。添加后,该注解会创建一个内存中的缓存实现,你可以在其中存储方法的返回值。
@EnableCaching
@SpringBootApplication
public class SpringBootCacheExampleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootCacheExampleApplication.class, args);
}
}
Code language: PHP (php)
在这一点上,Spring Boot的自动配置将配置一个内存缓存管理器。
由于你已经启用了缓存支持,你可以开始标记你的方法进行缓存。例如,这里的getItem()方法将需要10秒才能完成。由于睡眠,这个方法将需要更多的时间来完成。要做到这一点,请添加Spring Boot @Cacheable,其值如下所示。
@Service
public class ItemService {
private final ItemRepository itemRepository;
public ItemService(ItemRepository itemRepository) {
this.itemRepository = itemRepository;
}
public List<Item> getItems() {
return itemRepository.findAll();
}
public Item getItem(Integer id) {
try {
Thread.<em>sleep</em>(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return itemRepository.findById(id).orElseThrow(RuntimeException::new);
}
public Item createItem(Item item) {
return itemRepository.save(item);
}
public Item updateItem(Integer id, Item request) {
Item item = getItem(id);
item.setPrice(request.getPrice());
item.setProductName(request.getProductName());
return itemRepository.save(item);
}
}
在这种情况下,如果我们能够通过id参数来缓存结果,我们就可以改善响应时间。
看一下下面的代码。注意,我们将getItem方法标记为可缓存。当Spring Boot调用这个方法时,返回值(Item)会被缓存管理器缓存。通过这种方法,Spring boot将从缓存中加载值用于后续调用。
@Service
public class ItemService {
private static final Logger logger = LoggerFactory.getLogger(ItemService.class);
private final ItemRepository itemRepository;
public ItemService(ItemRepository itemRepository) {
this.itemRepository = itemRepository;
}
public List<Item> items() {
return itemRepository.findAll();
}
@Cacheable(value = "items", key = "#id")
public Item getItem(Integer id) {
try {
Thread.<em>sleep</em>(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Item item = itemRepository.findById(id).orElseThrow(RuntimeException::new);
logger.info("Loading data from DB {}", item);
return item;
}
public Item createItem(Item item) {
return itemRepository.save(item);
}
@CacheEvict(value = "items", key = "#id")
public Item updateItem(Integer id, Item request) {
Item item = getItem(id);
item.setPrice(request.getPrice());
item.setProductName(request.getProductName());
return itemRepository.save(item);
}
}
下面是解释。在@Cacheable(value = "items", key = "#{id}")
注解中,items是缓存名称。每次调用getItem
方法时,返回的Item对象会被存储在items缓存中。随后,Spring Boot将从缓存中返回该值,用于未来的请求。例如,让我们创建加载一些假值并测试一下。
首先,我使用CommandLineRunner创建了一些记录进行测试。
id | productName | price |
---|---|---|
1 | 衬衫小号 | 28.99 |
2 | 裤子大号 | 21.99 |
在http://localhost:8080/items/2,对id为2的商品点击API,将返回一个延迟10秒的响应。
没有缓存
此外,在日志中你还可以看到,应用程序从数据库中加载了这个值。这说明该方法被执行了,而且记录确实是从数据库中加载的。
.ItemService:Loading data from DB Item{id=2,productName='Pants Large',price=21.99}
Code language: JavaScript (javascript)
如果你再试着点击一次同样的API,你会发现同样的日志不再被打印出来。这种行为解释了方法调用的控制没有从数据库中提取值。你也可以看到响应时间也有了很大的减少。
with @Cacheable in action
这个方法的问题是,如果我更新了2的价格,缓存中仍然会保留旧的值。对吗?为了避免类似的情况,你需要指示spring Boot何时清除缓存的值。在这种情况下,你需要在每次**updateItem()**方法调用时让spring Boot知道。
这就是**@CacheEvict注解发挥作用的地方。请注意,缓存的释放和存储都是基于被缓存的对象的id**进行的。
@CacheEvict(value = "items", key = "#id")
public Item updateItem(Integer id, Item request) {
Item item = getItem(id);
item.setPrice(request.getPrice());
item.setProductName(request.getProductName());
return itemRepository.save(item);
}
@Cacheable注解可以应用于@Service,@Controller甚至@Repository。只要你能自动连接一个组件,你就能使它们的方法可被缓存。下面是一个应用于Spring Boot JPA存储库的缓存例子。
@Repository
public interface ItemRepository extends JpaRepository<Item, Integer> {
@Cacheable(value = "items",key = "#id")
Item findById(Integer id);
}
综上所述,我们通过一个简单的例子了解了如何在spring boot中实现缓存。本教程的项目可在this github repository中找到。