Автор оригинала: Vlad Mihalcea.
В этой статье мы рассмотрим, как сопоставить типы столбцов диапазона
PostgreSQL с помощью JPA и Hibernate.
К счастью, вам не нужно реализовывать пользовательский тип гибернации для типа столбцов PostgreSQL range
, поскольку проект hibernate-types уже обеспечивает его поддержку.
Как наследовать свойства от сущности базового класса с помощью @MappedSuperclass с JPA и гибернацией @vlad_mihalcea https://t.co/1mM0bcwNFa pic.twitter.com/lZ9csG8hJS
PostgreSQL поддерживает несколько диапазонов
типов :
int4range
– Хранит диапазонцелых
значенийint8range
– Хранит диапазонbigint
(например,java.util.Длинные
) значенияnumrange
– Хранит диапазончисловых
(например,java.util.BigDecimal
) значенийдиапазон дат
– Хранит диапазонметок времени
(например,java.time.Значения LocalDate
)tsrange
– Хранит диапазонметок времени
(например,java.time.Значения LocalDateTime
)tstzrange
– Хранит диапазонметок времени с часовым поясом
(например,java.time.Значения ZonedDateTime
)
Для представления диапазонов значений, которые могут иметь открытые или закрытые нижние и верхние границы, мы можем использовать класс Range
, поставляемый с проектом hibernate-types
.
При сопоставлении пользовательского типа Hibernate у вас есть два варианта:
- вы можете реализовать интерфейс типа пользователя
- вы можете расширить тип AbstractSingleColumnStandardBasicType
Используя прежнюю стратегию, тип диапазона PostgreSQL
выглядит следующим образом:
public class PostgreSQLRangeType extends ImmutableType{ public PostgreSQLRangeType() { super(Range.class); } @Override public int[] sqlTypes() { return new int[]{Types.OTHER}; } @Override protected Range get( ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws SQLException { Object pgObject = rs.getObject(names[0]); String type = ReflectionUtils.invokeGetter( pgObject, "type" ); String value = ReflectionUtils.invokeGetter( pgObject, "value" ); switch (type) { case "int4range": return Range.integerRange(value); case "int8range": return Range.longRange(value); case "numrange": return Range.bigDecimalRange(value); case "tsrange": return Range.localDateTimeRange(value); case "tstzrange": return Range.zonedDateTimeRange(value); case "daterange": return Range.localDateRange(value); default: throw new IllegalStateException( "The range type [" + type + "] is not supported!" ); } } @Override protected void set( PreparedStatement st, Range range, int index, SharedSessionContractImplementor session) throws SQLException { if (range == null) { st.setNull(index, Types.OTHER); } else { Object holder = ReflectionUtils.newInstance( "org.postgresql.util.PGobject" ); ReflectionUtils.invokeSetter( holder, "type", determineRangeType(range) ); ReflectionUtils.invokeSetter( holder, "value", range.asString() ); st.setObject(index, holder); } } private static String determineRangeType(Range> range) { Class> clazz = range.getClazz(); if (clazz.equals(Integer.class)) { return "int4range"; } else if (clazz.equals(Long.class)) { return "int8range"; } else if (clazz.equals(BigDecimal.class)) { return "numrange"; } else if (clazz.equals(LocalDateTime.class)) { return "tsrange"; } else if (clazz.equals(ZonedDateTime.class)) { return "tstzrange"; } else if (clazz.equals(LocalDate.class)) { return "daterange"; } throw new IllegalStateException( "The class [" + clazz.getName() + "] is not supported!" ); } }
Если вы хотите реализовать пользовательский тип гибернации, реализовав интерфейс UserType
, будет намного проще, если вы просто расширите Неизменяемый тип
, предлагаемый проектом hibernate-types
.
Для получения более подробной информации ознакомьтесь с этой статьей .
Как уже упоминалось, вам не нужно создавать классы диапазона
или типа диапазона//PostgreSQL. Вы можете получить их с помощью
типов гибернации зависимости Maven:
com.vladmihalcea hibernate-types-55 ${hibernate-types.version}
Если вы используете более старую версию Hibernate, перейдите в репозиторий hibernate-types
GitHub и найдите соответствующую зависимость hibernate-types
для вашей текущей версии Hibernate.
Давайте предположим, что мы разрабатываем приложение для книжного магазина, и объекты Книга
выглядят следующим образом:
@Entity(name = "Book") @Table(name = "book") @TypeDef( typeClass = PostgreSQLRangeType.class, defaultForType = Range.class ) public class Book { @Id @GeneratedValue private Long id; @NaturalId private String isbn; private String title; @Column( name = "price_cent_range", columnDefinition = "numrange" ) private RangepriceRange; @Column( name = "discount_date_range", columnDefinition = "daterange" ) private Range discountDateRange; //Getters and setters omitted for brevity }
Обратите внимание на использование аннотации @TypeDef
, которая указывает Hibernate использовать тип диапазона PostgreSQL
Тип гибернации для обработки свойств диапазона
сущности.
Свойство isbn
помечено аннотацией @NaturalID
Hibernate, которая позволяет нам извлекать сущность Book
с помощью ее естественного идентификатора. Для получения более подробной информации об использовании натуральных идентификаторов ознакомьтесь с этой статьей .
используется для того, что очень полезно для сопоставления бизнес – ключей.
Теперь, когда сохраняются следующие две Книги
сущности:
Book book = new Book(); book.setIsbn("978-9730228236"); book.setTitle("High-Performance Java Persistence"); book.setPriceRange( Range.closed( BigDecimal.valueOf(39.95d), BigDecimal.valueOf(45.95d) ) ); book.setDiscountDateRange( Range.closedOpen( LocalDate.of(2019, 11, 29), LocalDate.of(2019, 12, 3) ) ); entityManager.persist(book);
Hibernate создает следующие инструкции SQL INSERT:
INSERT INTO book ( discount_date_range, isbn, price_cent_range, title, id ) VALUES ( '[2019-11-29,2019-12-03)', '978-9730228236', '[39.95,45.95]', 'High-Performance Java Persistence', 1 )
При извлечении ранее сохраненной сущности Book
мы видим, что свойства range
правильно извлечены из базовых столбцов базы данных:
Book book = entityManager .unwrap(Session.class) .bySimpleNaturalId(Book.class) .load("978-9730228236"); assertEquals( BigDecimal.valueOf(39.95d), book.getPriceRange().lower() ); assertEquals( BigDecimal.valueOf(45.95d), book.getPriceRange().upper() ); assertEquals( LocalDate.of(2019, 11, 29), book.getDiscountDateRange().lower() ); assertEquals( LocalDate.of(2019, 12, 3), book.getDiscountDateRange().upper() );
Что хорошо в использовании типов столбцов диапазон
, так это то, что мы можем использовать операторы, зависящие от диапазона, такие как @>
, который проверяет, содержится ли указанное значение в интервале диапазона:
ListdiscountedBooks = entityManager .createNativeQuery( "SELECT * " + "FROM book b " + "WHERE " + " b.discount_date_range @> CAST(:today AS date) = true ", Book.class) .setParameter( "today", LocalDate.of(2019, 12, 1) ) .getResultList(); assertTrue( discountedBooks.stream().anyMatch( book -> book.getTitle().equals("High-Performance Java Persistence") ) );
Круто, правда?
Сопоставление нестандартных типов столбцов базы данных с помощью Hibernate довольно просто. Однако с помощью проекта hibernate-types вам даже не нужно писать все эти типы.
Просто добавьте зависимость Maven в свой проект pom.xml
файл конфигурации и укажите аннотацию @TypeDef
для рассматриваемой сущности и начните сопоставление JSON , МАССИВА , PostgreSQL Перечисления или Inet адресов.