1. 引言
缓存是一种有效的性能提升策略,通过避免在结果(对于已知一段时间内)没有变化的情况下重复执行逻辑。
Spring Boot 提供了@Cacheable
注解,我们将其定义在方法上,以缓存该方法的结果。在某些场景中,比如在较低环境中的测试时,我们可能需要禁用缓存以观察某些修改后的行为。
本文将介绍如何在 Spring Boot 中配置缓存,并学习如何在需要时禁用缓存。
2. 缓存设置
让我们以一个简单的查询书籍评论示例来设置缓存,使用@Cacheable
对方法进行缓存。
我们的实体类将是BookReview
类,包含rating
、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;
// getters & setters
}
在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=?";
// 1st execution
bookReviewsLogic.getBooksByIsbn(bookReview.getIsbn());
String[] logs = output.toString()
.split("\\r?\\n");
assertThat(logs).anyMatch(e -> e.contains(target));
// 2nd execution
bookReviewsLogic.getBooksByIsbn(bookReview.getIsbn());
logs = output.toString()
.split("\\r?\\n");
long count = Arrays.stream(logs)
.filter(e -> e.equals(target))
.count();
// count 1 means the select query log from 1st execution.
assertEquals(1,count);
}
在上述测试中,我们两次执行getBooksByIsbn()
,捕获日志并确认在第二次执行时select
查询只被执行一次,因为getBooksByIsbn()
方法在第二次执行时返回了缓存的结果。
为了生成针对数据库查询的SQL日志,我们可以在application.properties
文件中设置以下属性:
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
实例,它是一个不执行缓存的缓存管理器。否则,我们可以返回基于哈希的缓存管理器。
有了上述简单的设置,我们可以在 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 means the select query log from 1st and 2nd execution.
assertEquals(2, count);
4. 总结
在这篇教程中,我们简要介绍了 Spring Boot 中的缓存,并设置了应用中的缓存。我们也学习了如何在需要时禁用缓存,以便测试代码的特定部分。此外,我们还编写了必要的测试以验证启用和禁用缓存的功能。
如往常一样,示例代码可在GitHub上找到。