Spring Boot 在测试时禁用 @Cacheable 缓存
1、简介
缓存是一种有效的策略,当执行结果在一段已知时间内没有变化时,可以避免重复执行逻辑,从而提高性能。
Spring Boot 提供了 @Cacheable
注解,可以在方法上定义该注解,它就会缓存方法的结果。在某些情况下,例如在测试环境中进行测试时,我们可能需要禁用缓存来观察某些修改后的行为。
本文将带你了解如何配置 Spring Boot 中的缓存,以及如何在需要时禁用缓存。
2、缓存配置
设置一个简单的用例,通过 ISBN(国际标准书号)查询图书评论,并在某个逻辑中使用 @Cacheable
对该方法返回的结果进行缓存。
实体类 BookReview
如下,它包含 bookRating
、isbn
等信息:
@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;
// Get / Set 方法省略
}
在 BookRepository
中添加一个简单的 findByIsbn()
方法,用于按 isbn
查询书评:
public interface BookRepository extends JpaRepository<BookReview, Long> {
List<BookReview> findByIsbn(String isbn);
}
BookReviewsLogic
类包含一个在 BookRepository
中调用 findByIsbn()
的方法。我们添加了 @Cacheable
注解,将指定 isbn
的结果缓存在 book_reviews
缓存中:
@Service
public class BookReviewsLogic {
@Autowired
private BookRepository bookRepository;
@Cacheable(value = "book_reviews", key = "#isbn")
public List<BookReview> getBooksByIsbn(String isbn){
return bookRepository.findByIsbn(isbn);
}
}
添加了 @Cacheable
注解后,还需要激活、配置缓存。可以在 @Configuration
配置类上添加 @EnableCaching
注解来自动配置缓存。
如下,使用 HashMap
存储缓存项目:
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
}
缓存配置完成后。如果我们在 BookReviewsLogic
中执行 getBooksByIsbn()
,该方法返回的结果就会在第一次执行时被缓存起来,然后立即返回,而无需重新计算(即查询数据库),从而提高了性能。
编写一个简单的测试来验证一下:
@Test
public void givenCacheEnabled_whenLogicExecuted2ndTime_thenItDoesntQueriesDB(CapturedOutput output){
BookReview bookReview = insertBookReview();
String target = "Hibernate: select bookreview0_.reviews_id as reviews_1_0_, "
+ "bookreview0_.book_rating as book_rat2_0_, "
+ "bookreview0_.isbn as isbn3_0_, "
+ "bookreview0_.user_id as user_id4_0_ "
+ "from book_reviews bookreview0_ "
+ "where bookreview0_.isbn=?";
// 第 1 次执行
bookReviewsLogic.getBooksByIsbn(bookReview.getIsbn());
String[] logs = output.toString()
.split("\\r?\\n");
assertThat(logs).anyMatch(e -> e.contains(target));
// 第 2 次执行
bookReviewsLogic.getBooksByIsbn(bookReview.getIsbn());
logs = output.toString()
.split("\\r?\\n");
long count = Arrays.stream(logs)
.filter(e -> e.equals(target))
.count();
// count 1 表示第一次执行的 select 查询日志。
assertEquals(1,count);
}
在上述测试中,我们执行了两次 getBooksByIsbn()
,捕获了日志,并确认 select
查询只执行了一次,因为 getBooksByIsbn()
方法在第二次执行时会返回缓存结果。
要为针对数据库执行的查询生成 SQL 日志,可以在 application.properties
文件中设置以下属性:
# 在日志中输出 JPA 执行的 SQL 语句
spring.jpa.show-sql=true
3、禁用缓存
要禁用缓存,可以在 application.properties
文件中使用一个额外的自定义属性(即 appconfig.cache.enabled
):
appconfig.cache.enabled=true
然后,可以在缓存配置类中注入该配置值,并根据配置值返回同的缓存实现:
@Bean
public CacheManager cacheManager(@Value("${appconfig.cache.enabled}") String isCacheEnabled) {
if (isCacheEnabled.equalsIgnoreCase("false")) {
return new NoOpCacheManager();
}
return new ConcurrentMapCacheManager();
}
如上,查属性是否设置为禁用缓存。如果是,可以返回 NoOpCacheManager
的实例,这是一个不执行缓存的 CacheManager。否则,可以返回基于 Hash 的 CacheManager器。
通过上述简单设置,就可以禁用 Spring Boot 应用中的缓存了。
通过一个简单的测试来验证上述设置。
首先,需要修改在 application.properties
中定义的缓存属性。对于测试设置,可以使用 @TestPropertySource
覆盖该属性:
@SpringBootTest(classes = BookReviewApplication.class)
@ExtendWith(OutputCaptureExtension.class)
@TestPropertySource(properties = {
"appconfig.cache.enabled=false"
})
public class BookReviewsLogicCacheDisabledUnitTest {
// ...
}
然后,测试将与之前类似,执行两次逻辑。检查 SQL 查询日志,看它是否在当前测试中记录了两次,因为缓存被禁用了,所以执行结果不会被缓存:
long count = Arrays.stream(logs)
.filter(e -> e.contains(target))
.count();
// count 2 表示第 1 次和第 2 次执行的 select 查询日志。
assertEquals(2, count);
4、总结
本文简要介绍了 Spring Boot 中的缓存,以及如何在需要测试代码的某些部分时禁用缓存。
Ref:https://www.baeldung.com/spring-boot-disable-cacheable-annotation