Статьи

Spring 3.1 Caching и @Cacheable

Кеши давно существуют в мире программного обеспечения. Это одна из тех действительно полезных вещей, что, как только вы начинаете их использовать, вы удивляетесь, как же вы обошлись без них, поэтому кажется немного странным, что ребята в Spring только удосужились добавить реализацию кэширования в ядро ​​Spring в версии. 3.1. Я предполагаю, что раньше это не рассматривалось в качестве приоритета, и, кроме того, до введения аннотаций Java одной из трудностей кэширования было соединение кода кэширования с вашим бизнес-кодом, который часто мог стать довольно грязным.

Однако ребята из Spring разработали простую в использовании систему кэширования, основанную на нескольких аннотациях: @Cacheable и @CacheEvict.

Идея аннотации @Cacheable заключается в том, что вы используете ее для пометки возвращаемых значений метода, которые будут храниться в кэше. Аннотация @Cacheable может применяться на уровне метода или типа. При применении на уровне метода возвращаемое значение аннотированного метода кэшируется. При применении на уровне типа возвращаемое значение каждого метода кэшируется.

Код ниже демонстрирует, как применять @Cacheable на уровне типа:

01
02
03
04
05
06
07
08
09
10
11
12
13
@Cacheable(value = "employee")
public class EmployeeDAO {
 
  public Person findEmployee(String firstName, String surname, int age) {
 
    return new Person(firstName, surname, age);
  }
 
  public Person findAnotherEmployee(String firstName, String surname, int age) {
 
    return new Person(firstName, surname, age);
  }
}

Аннотация Cacheable принимает три аргумента: значение, которое является обязательным, вместе с ключом и условием. Первый из них, value, используется для указания имени кэша (или кэшей), в котором хранится возвращаемое значение метода.

1
2
3
4
5
@Cacheable(value = "employee")
public Person findEmployee(String firstName, String surname, int age) {
 
  return new Person(firstName, surname, age);
}

Приведенный выше код гарантирует, что новый объект Person будет сохранен в кэше «employee».

Любые данные, хранящиеся в кеше, требуют ключа для их быстрого поиска. Spring по умолчанию создает ключи кэширования с использованием подписи аннотированного метода, как показано в приведенном выше коде. Вы можете переопределить это, используя второй параметр @ Cacheable: ключ. Чтобы определить пользовательский ключ, вы используете выражение SpEL.

1
2
3
4
5
@Cacheable(value = "employee", key = "#surname")
public Person findEmployeeBySurname(String firstName, String surname, int age) {
 
  return new Person(firstName, surname, age);
}

В коде findEmployeeBySurname (…) строка «#surname» является выражением SpEL, которое означает «иди и создай ключ, используя аргумент фамилии метода findEmployeeBySurname (…)».

Последний аргумент @Cacheable является необязательным аргументом условия. Опять же, это ссылается на выражение SpEL, но на этот раз оно определяет условие, которое используется для определения, добавляется ли возвращаемое значение вашего метода в кэш.

1
2
3
4
5
@Cacheable(value = "employee", condition = "#age < 25")
public Person findEmployeeByAge(String firstName, String surname, int age) {
 
  return new Person(firstName, surname, age);
}

В приведенном выше коде я применил нелепое бизнес-правило, заключающееся в кэшировании только объектов Person, если сотруднику меньше 25 лет.

Быстро продемонстрировав, как применить кеширование, мы должны взглянуть на то, что все это значит.

1
2
3
4
5
6
7
8
@Test
public void testCache() {
 
  Person employee1 = instance.findEmployee("John", "Smith", 22);
  Person employee2 = instance.findEmployee("John", "Smith", 22);
 
  assertEquals(employee1, employee2);
}

Вышеупомянутый тест демонстрирует кеширование в самом простом При первом вызове findEmployee (…) результат еще не кешируется, поэтому мой код будет вызван, и Spring сохранит возвращаемое значение в кеше. Во втором вызове findEmployee (…) мой код не вызывается, и Spring возвращает кэшированное значение; следовательно, локальная переменная employee1 ссылается на ту же ссылку на объект, что и employee2, что означает следующее:

1
assertEquals(employee1, employee2);

Но не всегда все так ясно. Помните, что в findEmployeeBySurname я изменил ключ кеширования так, чтобы аргумент фамилии использовался для создания ключа, и при создании собственного алгоритма ввода следует следить за тем, чтобы любой ключ ссылался на уникальный объект.

1
2
3
4
5
6
7
8
@Test
public void testCacheOnSurnameAsKey() {
 
  Person employee1 = instance.findEmployeeBySurname("John", "Smith", 22);
  Person employee2 = instance.findEmployeeBySurname("Jack", "Smith", 55);
 
  assertEquals(employee1, employee2);
}

Код выше находит два экземпляра Person, которые явно относятся к разным сотрудникам; однако, поскольку я кэширую только фамилию, Spring вернет ссылку на объект, созданный во время моего первого вызова findEmployeeBySurname (…). Это не проблема со Spring, но с моим плохим определением ключа кеша.

Аналогичная осторожность следует проявлять при обращении к объектам, созданным методами, которые имеют условие, примененное к аннотации @Cachable. В моем примере кода я применил произвольное условие только для кэширования экземпляров Person, в которых сотруднику меньше 25 лет.

1
2
3
4
5
6
7
8
@Test
public void testCacheWithAgeAsCondition() {
 
  Person employee1 = instance.findEmployeeByAge("John", "Smith", 22);
  Person employee2 = instance.findEmployeeByAge("John", "Smith", 22);
 
  assertEquals(employee1, employee2);
}

В приведенном выше коде ссылки на employee1 и employee2 одинаковы, поскольку во втором вызове findEmployeeByAge (…) Spring возвращает свой кэшированный экземпляр.

1
2
3
4
5
6
7
8
@Test
public void testCacheWithAgeAsCondition2() {
 
  Person employee1 = instance.findEmployeeByAge("John", "Smith", 30);
  Person employee2 = instance.findEmployeeByAge("John", "Smith", 30);
 
  assertFalse(employee1 == employee2);
}

Аналогично, в приведенном выше коде модульного теста ссылки на employee1 и employee2 относятся к разным объектам, поскольку в этом случае Джону Смиту больше 25 лет.

Это касается примерно @Cacheable, но как насчет @CacheEvict и очищающих элементов, образующих кеш? Также возникает вопрос о добавлении кэширования в конфигурацию Spring и выборе подходящей реализации кэширования. Однако об этом позже …

Ссылка: Spring 3.1 Caching и @Cacheable от нашего партнера по JCG Роджера Хьюза в блоге Captain Debug’s Blog .