1. Introduction
Caching is a smart way to speed up our programs. It helps us avoid doing the same work over and over again when the result is the same.
In this tutorial, we’ll set up cache in Spring Boot and learn how to disable caching in Spring Boot application when needed.
Spring Boot gives us a tool called @Cacheable that we can use to save the result of a method. Sometimes, like when we’re testing, we might want to turn off caching to see how changes affect our program.
2. Setup Cache in Spring Boot
Let’s imagine we’re working on a program that looks up student grades by their roll number. We’ll use @Cacheable to save the result of the method in our service.
First, we’ll define a StudentGrade class which will have fields like rollNumber, subject, and grade:
@Entity
@Table(name="STUDENT_GRADES")
public class StudentGrade {
@Id
@GeneratedValue(strategy= GenerationType.SEQUENCE, generator = "student_grades_grade_id_seq")
@SequenceGenerator(name = "student_grades_grade_id_seq", sequenceName = "student_grades_grade_id_seq", allocationSize = 1)
private Long gradeId;
private String rollNumber;
private String subject;
private String grade;
// getters & setters
}
Next, we’ll add a simple findByRollNumber() method in StudentRepository to look up student grades by rollNumber:
public interface StudentRepository extends JpaRepository<StudentGrade, Long> {
List<StudentGrade> findByRollNumber(String rollNumber);
}
StudentGradesService class contains a method getGradesByRollNumber() that calls findByRollNumber() in StudentRepository. We add @Cacheable annotation, which caches the result for given rollNumber in the student_grades cache:
@Service
public class StudentGradesService {
@Autowired
private StudentRepository studentRepository;
@Cacheable(value = "student_grades", key = "#rollNumber")
public List<StudentGrade> getGradesByRollNumber(String rollNumber){
return studentRepository.findByRollNumber(rollNumber);
}
}
Since we’re using @Cacheable in the service class, we need to set up our cache. We can do this by creating a config class with @Configuration and @EnableCaching. Here, we’re using a HashMap to store our cache:
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
}
Now, our cache is ready. If we call getGradesByRollNumber() in StudentGradesService class, our result gets saved in the cache the first time we run it. After that, it’s returned right away without having to do the work again (like querying the database), which makes our program faster.
Let’s verify this with a simple test case:
@Test
public void givenCacheEnabled_whenServiceMethodCalled2ndTime_thenItDoesntQueriesDB(CapturedOutput output){
StudentGrade studentGrade = insertStudentGrade();
String target = "Hibernate: select studentgrade0_.grade_id as grade_id1_0_, "
+ "studentgrade0_.grade as grade2_0_, "
+ "studentgrade0_.roll_number as roll_num3_0_, "
+ "studentgrade0_.subject as subject4_0_ "
+ "from student_grades studentgrade0_ "
+ "where studentgrade0_.roll_number=?";
// 1st execution
studentGradesService.getGradesByRollNumber(studentGrade.getRollNumber());
String[] logs = output.toString()
.split("\\r?\\n");
assertThat(logs).anyMatch(e -> e.contains(target));
// 2nd execution
studentGradesService.getGradesByRollNumber(studentGrade.getRollNumber());
logs = output.toString()
.split("\\r?\\n");
long count = Arrays.stream(logs)
.filter(e -> e.equals(target))
.count();
// if the count is 1, which means the select query logs from 1st execution.
assertEquals(1,count);
}
In this test, we’re executing getGradesByRollNumber() twice, capturing the logs, and checking that the select query is run just once. This is because getGradesByRollNumber() returns a cached result the second time we run it.
To see an SQL log for the query run against the database, we can set the below property in the application.properties file:
spring.jpa.show-sql=true
3. Disable Caching in Spring Boot
Sometimes, we might want to turn off caching. For example, when we’re testing, we might want to see how changes affect our program without caching. To do this, we’ll add a custom property (like app.config.cache.enabled) in the application.properties file:
app.config.cache.enabled=true
Then, we can read this config in the cache config file and check its value:
@Bean
public CacheManager cacheManager(@Value("${app.config.cache.enabled}") String isCacheEnabled) {
if (isCacheEnabled.equalsIgnoreCase("false")) {
return new NoOpCacheManager();
}
return new ConcurrentMapCacheManager();
}
As you can see, we are checking whether the property is set to disable the cache. If it is, we can return an instance of NoOpCacheManager, which is a caching manager that doesn’t perform caching. Otherwise, we can return our Hash-based cache manager.
With this simple setup, we can turn off the cache in Spring Boot application. Let’s verify this setup with a simple test.
First, we need to change the cache property that we defined in application.properties. For our test setup, we can override the property using @TestPropertySource:
@SpringBootTest(classes = StudentGradeApplication.class)
@ExtendWith(OutputCaptureExtension.class)
@TestPropertySource(properties = {
"app.config.cache.enabled=false"
})
public class StudentGradesServiceCacheDisabledUnitTest {
// ...
}
Now, our test would be similar to earlier, where we ran the service method twice. We check the SQL query log to see if it logged twice in the current test since the execution won’t be cached:
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. Conclusion
In this guide, we learned about caching in Spring Boot and set up the cache in our application. We also learned how to turn off the cache in Spring Boot application when we need to test certain parts of the code. Plus, we wrote the necessary tests to check that turning on and off the cache works.
Remember, caching is a powerful tool, but it’s not always the best solution. Always think about whether caching is the right choice based on the requirement.