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

Сопоставление интервала PostgreSQL с продолжительностью Java с помощью Hibernate

Узнайте, как сопоставить тип столбца интервала PostgreSQL с объектом длительности Java с помощью Hibernate и проекта hibernate-types.

Автор оригинала: Vlad Mihalcea.

Вступление

В этой статье мы рассмотрим, как сопоставить тип столбца интервала PostgreSQL с объектом длительности Java с помощью Hibernate и hibernate-типов проекта.

Еще одна очень полезная функция, представленная проектом hibernate-types , заключается в том, что все типы, расширяющие Неизменяемый тип , теперь могут рассматриваться как стандартные org.hibernate.type.Введите , что позволит значительно улучшить интеграцию API ядра Hibernate.

Модель предметной области

Предполагая, что у нас есть следующая таблица книга базы данных, которая определяет столбец presale_period типа интервал|/.

Мы можем сопоставить таблицу book с сущностью Book JPA следующим образом:

@Entity(name = "Book")
@Table(name = "book")
@TypeDef(
    typeClass = PostgreSQLIntervalType.class,
    defaultForType = Duration.class
)
@TypeDef(
    typeClass = YearMonthDateType.class,
    defaultForType = YearMonth.class
)
public class Book {

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    private String isbn;

    private String title;

    @Column(
        name = "published_on", 
        columnDefinition = "date"
    )
    private YearMonth publishedOn;

    @Column(
        name = "presale_period", 
        columnDefinition = "interval"
    )
    private Duration presalePeriod;

    public Long getId() {
        return id;
    }

    public Book setId(Long id) {
        this.id = id;
        return this;
    }

    public String getIsbn() {
        return isbn;
    }

    public Book setIsbn(String isbn) {
        this.isbn = isbn;
        return this;
    }

    public String getTitle() {
        return title;
    }

    public Book setTitle(String title) {
        this.title = title;
        return this;
    }

    public YearMonth getPublishedOn() {
        return publishedOn;
    }

    public Book setPublishedOn(YearMonth publishedOn) {
        this.publishedOn = publishedOn;
        return this;
    }

    public Duration getPresalePeriod() {
        return presalePeriod;
    }

    public Book setPresalePeriod(Duration presalePeriod) {
        this.presalePeriod = presalePeriod;
        return this;
    }
}

Первое, что следует заметить, – это то, что методы настройки свойств сущности следуют шаблону проектирования интерфейса Fluent .

Второе, что вы можете заметить, это то, что мы определяем бизнес-ключ @NaturalID, который позволяет нам извлекать сущность на основе естественного идентификатора, даже если мы не знаем значения первичного ключа связанной записи таблицы.

Третье, что вы заметите, это то, что мы определяем несколько @TypeDef аннотаций. Оба предназначены для типов объектов, представленных Java 8.

Чтобы отобразить тип Java Год Месяц , мы можем использовать Тип даты Год Месяц , как описано в этой статье .

Чтобы сопоставить столбец интервала PostgreSQL с Java Длительность , нам нужно использовать тип интервала PostgreSQL , предлагаемый проектом hibernate-types .

Сопоставление столбцов интервала Java с интервалом PostgreSQL

При сохранении Книги сущности:

entityManager.persist(
    new Book()
        .setIsbn("978-9730228236")
        .setTitle("High-Performance Java Persistence")
        .setPublishedOn(YearMonth.of(2016, 10))
        .setPresalePeriod(
            Duration.between(
                LocalDate
                    .of(2015, Month.NOVEMBER, 2)
                    .atStartOfDay(),
                LocalDate
                    .of(2016, Month.AUGUST, 25)
                    .atStartOfDay()
            )
        )
);

Мы видим, что Hibernate генерирует соответствующую инструкцию SQL INSERT:

INSERT INTO book (
    isbn, 
    presale_period, 
    published_on, 
    title, 
    id
) 
VALUES (
    '978-9730228236', 
    '0 years 0 mons 297 days 0 hours 0 mins 0.00 secs', 
    '2016-10-01', 
    'High-Performance Java Persistence', 
    1
)

При извлечении сущности Book мы видим, что атрибут Период до продажи Java Продолжительность правильно заполнен соответствующим значением столбца PostgreSQL интервал|/.

Book book = entityManager
    .unwrap(Session.class)
    .bySimpleNaturalId(Book.class)
    .load("978-9730228236");

assertEquals(
    Duration.between(
        LocalDate
            .of(2015, Month.NOVEMBER, 2)
            .atStartOfDay(),
        LocalDate
            .of(2016, Month.AUGUST, 25)
            .atStartOfDay()
    ),
    book.getPresalePeriod()
);

Хотя сохранение и извлечение сущности Book , а также выполнение любого запроса JPQL и API критериев довольно просто, обработка собственных наборов результатов SQL-запросов сложнее при работе с типами столбцов, которые изначально не поддерживаются Hibernate.

Если Hibernate обнаруживает типы столбцов JDBC, для которых у него нет зарегистрированного типа Hibernate, то возникает исключение Без сопоставления диалектов для типа JDBC .

Как я объяснил в этой статье , вы можете решить эту проблему, указав правильный тип гибернации для обработки данного типа столбца JDBC.

В следующем примере собственного SQL-запроса вы можете видеть, что псевдоним столбца published_on результирующего набора настроен на использование типа YearMonthDateType , в то время как псевдоним столбца presale_period обрабатывается типом PostgreSQLIntervalType .

Tuple result = (Tuple) entityManager
.createNativeQuery(
    "SELECT " +
    "   b.published_on AS published_on, " +
    "   b.presale_period  AS presale_period " +
    "FROM " +
    "   book b " +
    "WHERE " +
    "   b.isbn = :isbn ", Tuple.class)
.setParameter("isbn", "978-9730228236")
.unwrap(NativeQuery.class)
.addScalar(
    "published_on", 
    YearMonthDateType.INSTANCE
)
.addScalar(
    "presale_period", 
    PostgreSQLIntervalType.INSTANCE
)
.getSingleResult();

assertEquals(
    YearMonth.of(2016, 10),
    result.get("published_on")
);

assertEquals(
    Duration.between(
        LocalDate.of(2015, Month.NOVEMBER, 2).atStartOfDay(),
        LocalDate.of(2016, Month.AUGUST, 25).atStartOfDay()
    ),
    result.get("presale_period")
);

Метод addScalar интерфейса Hibernate Собственный запрос принимает org.hibernate.тип.Введите Ссылку на объект, в то время как YearMonthDateType и PostgreSQLIntervalType реализуют интерфейс UserType|/.

До выпуска 2.6 типов гибернации было невозможно использовать Неизменяемый тип , который расширяет интерфейс UserType , в вызовах методов addScalar . Однако начиная с версии 2.6 класс ImmutableType abstract реализует как Тип пользователя , так и org.hibernate.type.Введите , поэтому передача неизменяемого типа (который является базовым классом как YearMonthDateType , так и PostgreSQLIntervalType ) в метод addScalar больше не является проблемой.

Вывод

Проект hibernate-types расширился, чтобы вместить большое разнообразие типов гибернации, которые изначально не поддерживаются. Например, теперь вы можете использовать JSON, МАССИВ, HStore, Диапазон, Int, Месяц года, Символ, допускающий значение null, и типы перечислений, специфичные для PostgreSQL.

Хотя вы также можете реализовать все эти типы самостоятельно, гораздо удобнее определить типы гибернации зависимости в вашем проекте pom.xml Файл конфигурации Maven и сосредоточьтесь на бизнес-логике приложения вместо написания типов, специфичных для гибернации.