Автор оригинала: Vlad Mihalcea.
Вступление
В этой статье мы рассмотрим, как работает JPA AttributeConverter и как мы можем использовать его для настройки атрибута сущности для сопоставления столбцов базы данных.
Например, мы могли бы использовать JPA AttributeConverter для сопоставления Java MonthDay
с базой данных ДАТОЙ
столбцом, потому что Hibernate не предлагает встроенного MonthDayType
для обработки этого конкретного сопоставления.
Модель предметной области
Наше приложение использует следующую таблицу annual_subscription
базы данных:
Годовая подписка должна продлеваться каждый год в определенный день, поэтому в столбце payment_date
хранятся день и месяц, когда необходимо произвести платеж.
Таблица Годовая подписка
сопоставляется со следующей сущностью Годовая подписка
JPA:
@Entity(name = "AnnualSubscription") @Table(name = "annual_subscription") public class AnnualSubscription { @Id private Long id; @Column( name = "price_in_cents" ) private int priceInCents; @Column( name = "payment_day", columnDefinition = "date" ) @Convert( converter = MonthDayDateAttributeConverter.class ) private MonthDay paymentDay; public Long getId() { return id; } public AnnualSubscription setId( Long id) { this.id = id; return this; } public int getPriceInCents() { return priceInCents; } public AnnualSubscription setPriceInCents( int priceInCents) { this.priceInCents = priceInCents; return this; } public MonthDay getPaymentDay() { return paymentDay; } public AnnualSubscription setPaymentDay( MonthDay paymentDay) { this.paymentDay = paymentDay; return this; } }
Сущность Годовая подписка
использует API в стиле Fluent, который, как вы скоро увидите, значительно упрощает способ создания сущности JPA. Для получения более подробной информации об использовании API в стиле FLuent с JPA и Hibernate ознакомьтесь с этой статьей .
Тип атрибута день платежа
сущности – День месяца
. Однако по умолчанию Hibernate не поддерживает этот тип Java, поэтому нам необходимо предоставить пользовательский картограф.
Без предоставления пользовательского сопоставителя Hibernate будет использовать Сериализуемый тип
для атрибута день оплаты
сущности и сохранит его как байт[]
тип столбца массива, который не будет работать для нас, так как payment_day
тип столбца дата
.
Итак, у нас есть два варианта. Мы можем либо использовать Настраиваемый тип для гибернации или преобразователь атрибутов JPA для обработки сопоставления между типом День месяца
Java и типом дата
столбец.
Преобразователь атрибутов JPA
Если вам не нужно предоставлять пользовательскую логику привязки и выборки JDBC, то конвертер атрибутов JPA является жизнеспособным решением для определения сопоставления между заданным типом объекта Java и типом столбца базы данных.
В нашем случае нам нужно создать следующий Класс AttributeConverter даты дня месяца
, реализующий интерфейс JPA AttributeConverter
:
public class MonthDayDateAttributeConverter implements AttributeConverter{ @Override public java.sql.Date convertToDatabaseColumn( MonthDay monthDay) { if (monthDay != null) { return java.sql.Date.valueOf( monthDay.atYear(1) ); } return null; } @Override public MonthDay convertToEntityAttribute( java.sql.Date date) { if (date != null) { LocalDate localDate = date.toLocalDate(); return MonthDay.of( localDate.getMonth(), localDate.getDayOfMonth() ); } return null; } }
Метод convertToDatabaseColumn
вызывается поставщиком JPA перед выполнением инструкции INSERT или UPDATE. Метод convertToDatabaseColumn
принимает один параметр, который является атрибутом сущности, и возвращает значение, которое необходимо задать в соответствующем столбце таблицы.
В нашем случае метод convertToDatabaseColumn
преобразует атрибут День месяца
сущности в java.sql.Дата
, который будет установлен в столбце payment_day
ДАТА. Обратите внимание, что для года задано предопределенное значение, так как нас не интересует это временное поле. Обратите внимание, что указанный параметр день месяца
может быть равен нулю, поэтому нам нужно применять преобразование только для ненулевых День месяца
Ссылок на объекты.
Метод convertToEntityAttribute
вызывается поставщиком JPA при извлечении сущности из базы данных с помощью метода find
или при выполнении запроса JPQL или API критериев. Метод convertToEntityAttribute
также принимает один параметр, который является значением базового столбца таблицы, и возвращает значение, которое необходимо задать для связанного атрибута сущности.
Наша реализация метода convertToEntityAttribute
преобразует значение столбца java.sql.Date
в объект MonthDay
Java, который будет установлен для связанного атрибута сущности. Обратите внимание, что указанный параметр дата
может быть равен нулю, поэтому нам необходимо применить преобразование только для ненулевых значений столбцов ДАННЫХ
базы данных.
Сопоставление преобразователя атрибутов JPA
Чтобы указать поставщику JPA использовать данную реализацию AttributeConverter
, мы можем использовать аннотацию @Convert
JPA для атрибута сущности, который необходимо преобразовать при чтении и записи в базу данных:
@Column( name = "payment_day", columnDefinition = "date" ) @Convert( converter = MonthDayDateAttributeConverter.class ) private MonthDay paymentDay;
Автоматическая регистрация преобразователя атрибутов JPA
Если у вас есть несколько сущностей, использующих данный тип Java, который обрабатывается одним и тем же преобразователем атрибутов JPA, вы можете автоматически зарегистрировать преобразователь с помощью аннотации @Converter
в реализации AttributeConverter
, как показано в следующем примере:
@Converter(autoApply = true) public static class MonthDayDateAttributeConverter implements AttributeConverter{ //Code omitted for brevity }
Теперь, если вы используете Hibernate, вы можете определить Создателя метаданных
реализацию , которая регистрирует MonthDayDateAttributeConverter
, как это:
public class AttributeConverterMetadataBuilderContributor implements MetadataBuilderContributor { @Override public void contribute( MetadataBuilder metadataBuilder) { metadataBuilder.applyAttributeConverter( MonthDayDateAttributeConverter.class ); } }
Чтобы указать Hibernate использовать Конструктор метаданных AttributeConverter
при загрузке EntityManagerFactory
или SessionFactory
, нам необходимо использовать свойство конфигурации hibernate.metadata_builder_contributor
.
Если вы используете Spring Boot, вы можете определить его в файле application.properties
, например:
hibernate.metadata_builder_contributor=com.vladmihalcea.book.hpjp.hibernate.type.AttributeConverterMetadataBuilderContributor
Свойство hibernate.metadata_builder_contributor
может принимать полное имя класса, реализующего MetadataBuilderContributor
.
Или, если вы используете JPA persistence.xml
файл, вы можете указать свойство hibernate.metadata_builder_contributor
в теге свойства
XML:
Время тестирования
При сохранении Годовой подписки
организации:
entityManager.persist( new AnnualSubscription() .setId(1L) .setPriceInCents(700) .setPaymentDay( MonthDay.of(Month.AUGUST, 17) ) );
Мы видим, что Hibernate генерирует следующую инструкцию SQL INSERT:
INSERT INTO annual_subscription ( id, price_in_cents, payment_day ) VALUES ( 1, 700, '0001-08-17' )
И при извлечении сущности Годовая подписка
мы видим, что атрибут день оплаты
сущность правильно преобразован из значения столбца ДАТА в объект День месяца
Java:
AnnualSubscription subscription = entityManager.find( AnnualSubscription.class, 1L ); assertEquals( MonthDay.of(Month.AUGUST, 17), subscription.getPaymentDay() );
Вот и все!
Вывод
Функция JPA AttributeConverter очень полезна, когда нам нужно преобразовать атрибут сущности перед сохранением или извлечением его из базы данных.
Однако, если вы хотите получить больше контроля над тем, как привязан базовый параметр JDBC PreparedStatement
или как извлекаются значения столбцов набора результатов
, вам необходимо использовать пользовательский тип, специфичный для режима гибернации, как описано в этой статье .