Spring Boot中如何缓存数据库查询结果
缓存是一种技术,可以存储不经常变化的频繁查询数据,并减少请求的延迟。我们可以在软件应用程序的不同层使用这种技术。
在本文中,讨论在 Spring Boot 中使用 ConcurrentHash 和 Redis 缓存数据库结果
使用案例
- 我们已经意识到(在检查和可视化跟踪之后,每当调用` /book_reviews/:isbn ` API时,都会进行查询以从 books 表中获取 book_reviews 。
- 我们的应用程序在book_reivews API上获得了大量流量,并且调用来获取每本书的评论效率不高,因为 book_reviews 的更改并不频繁。
- 因此,我们希望在应用程序级别本地缓存书评,以便减少数据库往返次数并减少数据库上的读取查询,从而可以为其他关键查询提供更好的延迟。
实体
我们将使用 BookReview 实体。
@Entity
@Table(name="BOOK_REVIEWS")
public class BookReview {
@Id
@GeneratedValue(strategy= GenerationType.SEQUENCE, generator = "book_reviews_reviews_id_seq")
@SequenceGenerator(name = "book_reviews_reviews_id_seq", sequenceName = "book_reviews_reviews_id_seq", allocationSize = 1)
private Long reviewsId;
private String userId;
private String isbn;
private String bookRating;
}
确保将一些示例数据添加到 book_reviews 表中。
控制器
控制器是一个调用逻辑类的简单休息端点。
@RestController
@RequestMapping("/books")
public class BookReviewsController {
@Autowired
private BookReviewsWithSimpleCacheLogic bookReviewsLogic;
@GetMapping("/reviews")
@ResponseBody()
public List<BookReview> getBookReviews(@RequestParam("isbn") String isbn){
return bookReviewsLogic.getAllReviewsByIsbn(isbn);
}
}
逻辑
逻辑类使用存储库通过 isbn 获取 book_reviews。
@Service
public class BookReviewsWithSimpleCacheLogic {
@Autowired
private BookRepository bookRepository;
public List<BookReview> getAllReviewsByIsbn(String isbn){
return bookRepository.findByIsbn(isbn);
}
}
缓存
Spring为其cache_abstraction提供了多种存储选项,我们可以在其中进行选择。如果我们没有定义一个,那么它会选择默认值,这是一个简单的并发哈希图。
Spring提供了@Cacheble、@CacheEvict和@CachePut等几种不同的注解来实现缓存。
- @Cacheble:用它来存储定义缓存的方法的缓存结果,如果已经存在则返回。
- @CachePut:调用时强制更新缓存
- @CacheEvict:当调用带注释的方法时逐出缓存
询问
为了查询书评,我们的存储库正在对数据库进行以下查询。
select br1_0.reviews_id,br1_0.book_rating,br1_0.isbn,br1_0.user_id from book_reviews br1_0 where br1_0.isbn=?
缓存查询结果
我们使用@Cacheble注释添加缓存,并定义缓存键和缓存值。这里的值定义了缓存的名称,键用于在缓存中查询特定的键。
@Cacheable(value = "book_reviews", key = "isbn") |
当我们到达 book_reviews 端点时,由于 @Cacheable 注释,它将首先检查缓存是否已存在,如果不存在,则计算方法并将结果分配给缓存。
如果我们第一次到达端点,我们会看到以下查询被执行到数据库。
Executing SQL Query: select br1_0.reviews_id,br1_0.book_rating,br1_0.isbn,br1_0.user_id from book_reviews br1_0 where br1_0.isbn=?
但在后续请求中,我们将看不到任何日志,因为结果将从缓存中返回。
如果您想在 JPA 中记录 SQL 查询,您可以使用以下命令设置 application.properties:
spring.jpa.show-sql=true
您还可以添加 sql 拦截器并在应用程序代码中记录 SQL 查询。
逐出缓存
我们不能永远保留缓存,我们需要根据过期时间驱逐它。例如,我们可以将缓存保留5分钟,然后过期。
5 分钟后,缓存过期,在下一次调用中,它会被重新分配给数据库查询返回的值。
Spring缓存提供@CacheEvict注解来驱逐缓存。我们可以定义任何特定的键,我们希望将其从缓存中删除。
@CacheEvict(value = "book_reviews", key = "isbn") |
我们需要调用evictCachesWithKey ()方法来移除缓存。
出于演示目的,我们将使用端点调用此方法并验证该方法是否按预期工作。
@GetMapping("/evict") |
如果我们到达端点,我们将看到相同的日志,并且在下一个请求中,我们将看到对数据库进行了书评查询,但没有从缓存返回。
自动清除缓存
到目前为止,为了逐出缓存,我们一直在手动调用 evict api。但理想的行为是根据定义的生存时间 (TTL) 自动调用它。
我们需要对逻辑进行一些更改,首先我们需要添加一个存储缓存键及其设置时间的哈希图。这将有助于确定缓存是否已超过定义的 TTL。
private final Map<String, LocalDateTime> cacheKeyWithStartTime = new HashMap<>(); |
其次,我们需要添加一个调度程序来定期运行逐出方法,以便它将删除所有超过定义的 TTL 的到期缓存。
我们还修改了缓存驱逐逻辑,我们不再使用@CacheEvict注释。相反,我们注入cacheManager,它将通过键为我们提供缓存,并且我们将通过引用哈希图来确认是否超过了TTL,因为它存储了设置的时间。
@Autowired |
使用Redis进行缓存
我们可以通过 Spring Boot 使用 Redis 进行缓存。
Redis实例
我有一个Mac实例,在Mac上,我们可以使用brew安装并启动Redis服务。
->> ~ $ brew install redis |
依赖性
我们需要在 pom.xml 中添加 redis-data 依赖项。
<dependency> |
缓存配置
缓存配置将为我们的 Redis 缓存管理器进行设置。在这里我们可以定义缓存的配置和 TTL。我们定义 TTL 为 1 分钟。
@Configuration |
缓存逻辑
现在对于我们的缓存逻辑,我们只需要定义@Cacheble并添加缓存键和值。我们需要确保将 book_reviews 值添加到 Redis 缓存管理器中。
@Service |
我们不需要定义缓存逐出,因为它已经由 Redis 固有地处理。
结论
在本文中,我们学习如何使用简单的并发哈希图存储在 Spring Boot 中进行缓存。我们自动为缓存添加了 TTL 过期时间。我们还设置了 Redis 缓存管理器来使用它进行缓存。