Em um artigo anterior, demonstrei a aplicação da anotação @Cacheable do Spring 3.1, que é usada para marcar os métodos cujos valores de retorno serão armazenados em um cache. No entanto, @Cacheable é apenas uma de um par de anotações que os caras do Spring criaram para o cache, a outra sendo @CacheEvict.
Como @Cacheable, o @CacheEvict também possui os atributos value, key e condition. Eles funcionam exatamente da mesma maneira como aqueles suportados por @Cacheable. Para mais informações sobre eles dê uma olhada no meu artigo anterior.
O @CacheEvict suporta dois atributos adicionais: allEntries e beforeInvocation. Se eu fosse um apostador, eu apostaria meu dinheiro no allEntries como o mais popular. Ele é usado para limpar completamente o conteúdo de um cache definido pelo valor obrigatório do argumento do @CacheEvict. O método a seguir demonstra como aplicar allEntries:
@CacheEvict(value = "employee", allEntries = true) public void resetAllEntries() { // Intentionally blank }
resetAllEntries() define o atributo allEntries do @CacheEvict para “true” e, assumindo que o método FindEmployee (…) fique assim:
@Cacheable(value = "employee") public Person findEmployee(String firstName, String surname, int age) { return new Person(firstName, surname, age); }
… então, no código a seguir, resetAllEntries() irá limpar o cache “employees”. Isso significa que, no teste JUnit abaixo, employee1 não vai referenciar o mesmo objeto como employee2:
@Test public void testCacheResetOfAllEntries() { Person employee1 = instance.findEmployee("John", "Smith", 22); instance.resetAllEntries(); Person employee2 = instance.findEmployee("John", "Smith", 22); assertNotSame(employee1, employee2); }
O segundo atributo é o beforeInvocation. Ele determina quando ou não um item de dados é limpo do cache antes ou depois de seu método ser invocado.
O código abaixo é muito sem sentido; no entanto, ele demonstra que você pode aplicar simultaneamente @CacheEvict e @Cacheable a um método.
@CacheEvict(value = "employee", beforeInvocation = true) @Cacheable(value = "employee") public Person evictAndFindEmployee(String firstName, String surname, int age) { return new Person(firstName, surname, age); }
No código acima, o @CacheEvict exclui quaisquer entradas no cache com uma chave correspondente antes de @Cacheable fazer buscas no cache. Já que o @Cacheable não vai encontrar as entradas, ele chamará o meu código, armazenando o resultado no cache. A chamada subsequente a meu método invocará @CacheEvict, que irá apagar todas as entradas apropriadas, resultando que, no teste JUnit abaixo, a variável employee1 nunca fará referência ao mesmo objeto que employee2:
@Test public void testBeforeInvocation() { Person employee1 = instance.evictAndFindEmployee("John", "Smith", 22); Person employee2 = instance.evictAndFindEmployee("John", "Smith", 22); assertNotSame(employee1, employee2); }
Como eu disse acima, evictAndFindEmployee(…) parece um pouco sem sentido quando estou aplicando @Cacheable e @CacheEvict ao mesmo método. Mas, é mais que isso, ele torna o código confuso e quebra o princípio da responsabilidade única; daí eu recomendo criar separadamente os métodos cacheable e cache-evict. Por exemplo, se você tem um método que está fazendo cache, tal como:
@Cacheable(value = "employee", key = "#surname") public Person findEmployeeBySurname(String firstName, String surname, int age) { return new Person(firstName, surname, age); }
Então, assumindo que você precisa de um controle de cache mais agradável do que um simples “clear-all ‘, você pode definir facilmente uma contrapartida:
@CacheEvict(value = "employee", key = "#surname") public void resetOnSurname(String surname) { // Intentionally blank }
Este é um simples método blank marker que utiliza a mesma expressão SPEL que foi aplicada ao @Cacheable para despejar todas as instâncias Person a partir do cache, onde a chave corresponde ao argumento “surname”.
@Test public void testCacheResetOnSurname() { Person employee1 = instance.findEmployeeBySurname("John", "Smith", 22); instance.resetOnSurname("Smith"); Person employee2 = instance.findEmployeeBySurname("John", "Smith", 22); assertNotSame(employee1, employee2); }
No código acima, a primeira chamada para findEmployeeBySurname(…) cria um objeto Person, o qual Spring armazena no cache “employee” com uma chave definida como: “Smith”. A chamada para resetOnSurname(…) apaga todas as entradas do cache “employee” com um sobrenome “Smith” e, finalmente, a segunda chamada para findEmployeeBySurname(…) cria um novo objeto Person, que o Spring novamente armazena no cache “employee” com a chave ” Smith “. Assim, as variáveis employee1 e employee2 não fazem referência ao mesmo objeto.
Já que eu abordei as anotações do cache Spring, a próxima peça do quebra-cabeça é dar uma olhada na criação de um cache prático: como é possível habilitar o cache do Spring e qual implementação de cache você deve usar?
***
Texto original disponível em http://www.captaindebug.com/2012/09/spring-31-caching-and-cacheevict.html#.UPBWb2_7KSo