Рубрики
Без рубрики

JPA AttributeConverter – Руководство для начинающих

Узнайте, как работает преобразователь атрибутов JPA и как его можно использовать для настройки атрибута сущности для сопоставления столбцов базы данных.

Автор оригинала: 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 или как извлекаются значения столбцов набора результатов , вам необходимо использовать пользовательский тип, специфичный для режима гибернации, как описано в этой статье .