Spring 3.1 Caching and @CacheEvict
@Cacheable
annotation that’s used to mark methods whose return values will be stored in a cache. However, @Cacheable
is only one of a pair of annotations that the Guys at Spring have devised for caching, the other being @CacheEvict
.Like @Cacheable
, @CacheEvict
has value
, key
and condition
attributes. These work in exactly the same way as those supported by @Cacheable
, so for more information on them see my previous blog: Spring 3.1 Caching and @Cacheable.
CacheEvict
supports two additional attributes: allEntries
and beforeInvocation
. If I were a gambling man I’d put money on the most popular of these being allEntries
. allEntries
is used to completely clear the contents of a cache defined by @CacheEvict
‘s mandatory value
argument. The method below demonstrates how to apply allEntries
:
@CacheEvict(value = "employee", allEntries = true) public void resetAllEntries() { // Intentionally blank }
resetAllEntries()
sets @CacheEvict
’s allEntries
attribute to “true” and, assuming that thefindEmployee(...)
method looks like this:
@Cacheable(value = "employee") public Person findEmployee(String firstName, String surname, int age) { return new Person(firstName, surname, age); }
…then in the following code, resetAllEntries()
, will clear the “employees” cache. This means that in the JUnit test below employee1
will not reference the same object as employee2
:
@Test public void testCacheResetOfAllEntries() { Person employee1 = instance.findEmployee("John", "Smith", 22); instance.resetAllEntries(); Person employee2 = instance.findEmployee("John", "Smith", 22); assertNotSame(employee1, employee2); }
The second attribute is beforeInvocation
. This determines whether or not a data item(s) is cleared from the cache before or after your method is invoked.
The code below is pretty nonsensical; however, it does demonstrate that you can apply both @CacheEvict
and @Cacheable
simultaneously to a method.
@CacheEvict(value = "employee", beforeInvocation = true) @Cacheable(value = "employee") public Person evictAndFindEmployee(String firstName, String surname, int age) { return new Person(firstName, surname, age); }
In the code above, @CacheEvict
deletes any entries in the cache with a matching key before @Cacheable
searches the cache. As @Cacheable
won’t find any entries it’ll call my code storing the result in the cache. The subsequent call to my method will invoke @CacheEvict
which will delete any appropriate entries with the result that in the JUnit test below the variable employee1
will never reference the same object asemployee2
:
@Test public void testBeforeInvocation() { Person employee1 = instance.evictAndFindEmployee("John", "Smith", 22); Person employee2 = instance.evictAndFindEmployee("John", "Smith", 22); assertNotSame(employee1, employee2); }
As I said above, evictAndFindEmployee(...)
seems somewhat nonsensical as I’m applying both@Cacheable
and @CacheEvict
to the same method. But, it’s more that that, it makes the code unclear and breaks the Single Responsibility Principle; hence, I’d recommend creating separate cacheable and cache-evict methods. For example, if you have a cacheing method such as:
@Cacheable(value = "employee", key = "#surname") public Person findEmployeeBySurname(String firstName, String surname, int age) { return new Person(firstName, surname, age); }
then, assuming you need finer cache control than a simple ‘clear-all’, you can easily define its counterpart:
@CacheEvict(value = "employee", key = "#surname") public void resetOnSurname(String surname) { // Intentionally blank }
This is a simple blank marker method that uses the same SpEL expression that’s been applied to@Cacheable
to evict all Person
instances from the cache where the key matches the ‘surname’ argument.
@Test public void testCacheResetOnSurname() { Person employee1 = instance.findEmployeeBySurname("John", "Smith", 22); instance.resetOnSurname("Smith"); Person employee2 = instance.findEmployeeBySurname("John", "Smith", 22); assertNotSame(employee1, employee2); }
In the above code the first call to findEmployeeBySurname(...)
creates a Person
object, which Spring stores in the “employee” cache with a key defined as: “Smith”. The call to resetOnSurname(...)
clears all entries from the “employee” cache with a surname of “Smith” and finally the second call tofindEmployeeBySurname(...)
creates a new Person
object, which Spring again stores in the “employee” cache with the key of “Smith”. Hence, the variables employee1
, and employee2
do not reference the same object.
Having covered Spring’s caching annotations, the next piece of the puzzle is to look into setting up a practical cache: just how do you enable Spring caching and which caching implementation should you use? More on that later…
Happy coding and don’t forget to share!
Reference: Spring 3.1 Caching and @CacheEvict from our JCG partner Roger Hughes at the Captain Debug’s Blog blog.