Back-End

18 jan, 2013

Armazenamento em cache do Spring 3.1 e @CacheEvict

Publicidade

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