Если вы когда-либо работали с перечислениями Java в JPA, вы наверняка знаете об их ограничениях и ловушках. Использование enum в качестве свойства вашего @Entity часто является очень хорошим выбором, однако JPA до 2.1 не очень хорошо с ними справлялся. Это дало вам 2 + 1 выбора:
-
@Enumerated(EnumType.ORDINAL)(по умолчанию) отобразит значенияenumс помощьюEnum.ordinal(). В основном первое перечисляемое значение будет отображаться в0в столбце базы данных, второе в1и т. Д. Это очень компактно и прекрасно работает до того момента, когда вы захотите изменить свое перечисление. Удаление или добавление значения в середине или перестановка их полностью сломает существующие записи. Ой! Что еще хуже, модульные и интеграционные тесты часто работают на чистой базе данных, поэтому они не улавливают расхождения в старых данных. -
@Enumerated(EnumType.STRING)намного безопаснее, потому что он хранит строковое представлениеenum. Теперь вы можете безопасно добавлять новые значения и перемещать их. Однако переименованиеenumв коде Java по-прежнему нарушает существующие записи в БД. Что еще более важно, такое представление очень многословно, излишне потребляя ресурсы базы данных. - Вы также можете использовать необработанное представление (например, один
charилиint) и отображать его вручную в@PostLoad/@PrePersist/@PreUpdate. Наиболее гибкий и безопасный с точки зрения базы данных, но довольно уродливый.
К счастью, Java Persistence API 2.1 ( JSR-388 ), выпущенный несколько дней назад, предоставляет стандартизированный механизм подключаемых преобразователей данных . Такой API издавна присутствовал в проприетарных формах, и это не совсем ракетостроение, но иметь его в составе JPA — большое улучшение. Насколько мне известно, Eclipselink является единственной реализацией JPA 2.1, доступной на сегодняшний день, поэтому мы будем использовать ее для экспериментов.
Мы начнем с примера приложения Spring, разработанного в рамках статьи « CRUD для бедных: jqGrid, REST, AJAX и Spring MVC в одном доме » . У этой версии не было постоянства, поэтому мы добавим тонкий слой DAO поверх Spring Data JPA при поддержке Eclipselink. Единственная сущность на данный момент — это Book :
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
@Entitypublic class Book { @Id @GeneratedValue(strategy = IDENTITY) private Integer id; //... private Cover cover; //...} |
Где Cover это enum :
|
1
2
3
4
5
|
public enum Cover { PAPERBACK, HARDCOVER, DUST_JACKET } |
Ни ORDINAL ни STRING не являются хорошим выбором здесь. Первая из-за того, что перестановка первых трех значений каким-либо образом нарушит загрузку существующих записей. Последнее слишком многословно. Вот где в игру вступают пользовательские конвертеры в JPA:
|
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
|
import javax.persistence.AttributeConverter;import javax.persistence.Converter; @Converterpublic class CoverConverter implements AttributeConverter<Cover, String> { @Override public String convertToDatabaseColumn(Cover attribute) { switch (attribute) { case DUST_JACKET: return "D"; case HARDCOVER: return "H"; case PAPERBACK: return "P"; default: throw new IllegalArgumentException("Unknown" + attribute); } } @Override public Cover convertToEntityAttribute(String dbData) { switch (dbData) { case "D": return DUST_JACKET; case "H": return HARDCOVER; case "P": return PAPERBACK; default: throw new IllegalArgumentException("Unknown" + dbData); } }} |
Хорошо, я не буду оскорблять вас, мой дорогой читатель, объясняя это. Преобразование enum во что угодно будет храниться в реляционной базе данных и наоборот. Теоретически JPA-провайдер должен применять конвертеры автоматически, если они объявлены с:
|
1
|
@Converter(autoApply = true |
Это не сработало для меня. Более того, объявление их явно вместо @Enumerated в классе @Entity также не сработало:
|
1
2
3
4
5
6
|
import javax.persistence.Convert; //... @Convert(converter = CoverConverter.class)private Cover cover; |
В результате чего происходит исключение:
|
1
2
3
|
Exception Description: The converter class [com.blogspot.nurkiewicz.CoverConverter]specified on the mapping attribute [cover] from the class [com.blogspot.nurkiewicz.Book] was not found.Please ensure the converter class name is correct and exists with the persistence unit definition. |
Ошибка или особенность, я должен был упомянуть конвертер в orm.xml :
|
1
2
3
4
|
<?xml version="1.0"?> <converter class="com.blogspot.nurkiewicz.CoverConverter"/></entity-mappings> |
И это летит! У меня есть свобода изменения моего перечисления Cover (добавление, перестановка, переименование) без влияния на существующие записи.
Один совет, которым я хотел бы поделиться с вами, связан с ремонтопригодностью. Каждый раз, когда у вас есть фрагмент кода из или для enum , убедитесь, что он проверен правильно. И я не имею в виду тестирование всех возможных существующих значений вручную. Я больше после теста, чтобы убедиться, что новые значения enum отражены в коде отображения. Подсказка: приведенный ниже код потерпит неудачу (с помощью IllegalArgumentException ), если вы добавите новое значение enum но забудете добавить код сопоставления из него:
|
1
2
3
|
for (Cover cover : Cover.values()) { new CoverConverter().convertToDatabaseColumn(cover);} |
Пользовательские конвертеры в JPA 2.1 гораздо полезнее, чем мы видели. Если вы объединяете JPA со Scala, вы можете использовать @Converter для непосредственного scala.math.BigDecimal столбцов базы данных с scala.math.BigDecimal , scala.Option или небольшим регистром классов. В Java наконец появится портативный способ отображения времени Joda . И последнее, но не менее важное: если вам нравится (очень) строго типизированный домен, вы можете PhoneNumber класс PhoneNumber (с isInternational() , getCountryCode() и настраиваемой логикой проверки) вместо String или long . Это небольшое дополнение в JPA 2.1, безусловно, значительно улучшит качество доменных объектов.
Если вы хотите немного поиграть с этой функцией, пример веб-приложения Spring доступен на GitHub .
