Статьи

JPA 2.1 Type Converter — лучший способ сохранить перечисления

Сохранение перечислений с JPA 2.0 возможно, но нет хорошего способа сделать это. Используя аннотацию @Enumerated , вы можете использовать EnumType.ORDINAL или EnumType.STRING для сопоставления значения перечисления с его представлением в базе данных. Но оба варианта имеют некоторые недостатки, которые мы обсудим в первой части этой статьи. Во второй части я покажу вам, как избежать этих недостатков с помощью конвертера типов JPA 2.1.

Сохранение перечислений с JPA 2.0

EnumType.ORDINAL использует возвращаемое значение Enum.ordinal () для сохранения перечисления. Таким образом, первое значение перечисления будет отображаться в 0, второе в 1 и так далее. Хотя это выглядит компактным и простым в использовании, оно вызывает проблемы при изменении перечисления. Удаление перечислимых значений или добавление нового значения где-то между ними изменит отображение всех следующих значений, например:

перед:

1
2
3
4
Vehicle:
CAR     -> 0
TRAIN   -> 1
PLANE   -> 2

после:

1
2
3
4
5
Vehicle:
CAR     -> 0
BUS     -> 1
TRAIN   -> 2
PLANE   -> 3

Добавление Bus во второй позиции потребует обновления базы данных для исправления отображения enum.

EnumType.STRING выглядит как лучший вариант. Он использует строковое представление перечисления, чтобы сохранить его в базе данных. Таким образом, добавление или удаление значений не изменит отображение. Но это представление может быть довольно многословным, и переименование значения enum нарушит отображение.

перед:

1
2
3
4
Vehicle:
CAR     -> CAR
TRAIN   -> TRAIN
PLANE   -> PLANE

после:

1
2
3
4
5
Vehicle:
CAR     -> CAR
BUS     -> BUS
TRAIN   -> TRAIN
PLANE   -> PLANE

Использование конвертера типов JPA 2.1

JPA 2.1 Type Converter предоставляет третий и, на мой взгляд, лучший вариант. Преобразователь типов позволяет нам реализовывать методы для преобразования значения атрибута сущности в его представление базы данных и обратно. Я не буду вдаваться в подробности о том, как реализовать конвертер типов, потому что я уже делал это в одной из моих предыдущих статей .

Реализуя наше собственное отображение, мы можем выбрать компактное представление базы данных и убедиться, что изменение перечисления никоим образом не нарушит существующее отображение. В следующем примере показано, как реализовать преобразователь типов для перечисления Vehicle:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Converter(autoApply = true)
public class VehicleConverter implements AttributeConverter<Vehicle, String> {
 
 @Override
 public String convertToDatabaseColumn(Vehicle vehicle) {
  switch (vehicle) {
  case BUS:
   return "B";
  case CAR:
   return "C";
  case PLANE:
   return "P";
  case TRAIN:
   return "T";
  default:
   throw new IllegalArgumentException("Unknown value: " + vehicle);
  }
 }
 
 @Override
 public Vehicle convertToEntityAttribute(String dbData) {
  switch (dbData) {
  case "B":
   return Vehicle.BUS;
  case "C":
   return Vehicle.CAR;
  case "P":
   return Vehicle.PLANE;
  case "T":
   return Vehicle.TRAIN;
  default:
   throw new IllegalArgumentException("Unknown value: " + dbData);
  }
 }
 
}

VehicleConverter отображает значение перечисления в односимвольную строку. Объявляя его с помощью @Converter (autoApply = true), мы сообщаем провайдеру JPA использовать этот Type Mapper для сопоставления всех перечислений Vehicle. Поэтому нам не нужно указывать конвертер для каждого атрибута сущности типа Vehicle.

Но есть одна вещь, о которой мы должны позаботиться, и если вы читали мою предыдущую статью о JPA Type Converter, вы, возможно, уже подумали. Преобразователь типов нельзя применять к атрибутам, помеченным @Enumerated. Таким образом, мы должны убедиться, что в наших атрибутах сущности типа Vehicle нет аннотации @Enumerated.

Вывод

Мы реализовали простой конвертер типов, который использует наши собственные правила для преобразования перечисления Vehicle в его представление базы данных. Таким образом, мы можем быть уверены, что изменение значений перечисления Vehicle не нарушит существующие / оставшиеся отображения.