Как отобразить тип столбца PostgreSQL Interval в Hibernate?
В PostgreSQL я использую PersistentDuration
для сопоставления между интервалом типа sql и продолжительностью, но он не работает.
Другой пользователь нашел ту же проблему и пришел со своим классом:
public void nullSafeSet(PreparedStatement statement, Object value, int index)
throws HibernateException, SQLException {
if (value == null) {
statement.setNull(index, Types.OTHER);
} else {
Long interval = ((Long) value).longValue();
Long hours = interval / 3600;
Long minutes = (interval - (hours * 3600)) / 60;
Long secondes = interval - (hours * 3600) - minutes * 60;
statement.setString(index, "'"+ hours +":"
+ intervalFormat.format(minutes) + ":"
+ intervalFormat.format(secondes)+"'");
}
}
Но он не работает с реальным форматом, потому что он допускает, что шаблон интервала
"Чч: мм: сс". Это не так: см.
Вот несколько реальных примеров, которые мне нужно проанализировать из базы данных:
1 day 00:29:42
00:29:42
1 week 00:29:42
1 week 2 days 00:29:42
1 month 1 week 2 days 00:29:42
1 year 00:29:42
1 decade 00:29:42
http://www.postgresql.org/docs/current/interactive/datatype-datetime.html
У вас есть чистое решение?
Ответы
Ответ 1
Это рабочее решение для JPA, Hibernate (с аннотациями).
Это начало класса сущности (для таблицы с столбцом Interval):
@Entity
@Table(name="table_with_interval_col")
@TypeDef(name="interval", typeClass = Interval.class)
public class TableWithIntervalCol implements Serializable {
Это интервал столбца:
@Column(name = "interval_col", nullable = false)
@Type(type = "interval")
private Integer intervalCol;
И это класс Interval:
package foo.bar.hibernate.type;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Date;
import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;
import org.postgresql.util.PGInterval;
/**
* Postgres Interval type
*
* @author bpgergo
*
*/
public class Interval implements UserType {
private static final int[] SQL_TYPES = { Types.OTHER };
@Override
public int[] sqlTypes() {
return SQL_TYPES;
}
@SuppressWarnings("rawtypes")
@Override
public Class returnedClass() {
return Integer.class;
}
@Override
public boolean equals(Object x, Object y) throws HibernateException {
return x.equals(y);
}
@Override
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
@Override
public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
throws HibernateException, SQLException {
String interval = rs.getString(names[0]);
if (rs.wasNull() || interval == null) {
return null;
}
PGInterval pgInterval = new PGInterval(interval);
Date epoch = new Date(0l);
pgInterval.add(epoch);
return Integer.valueOf((int)epoch.getTime() / 1000);
}
public static String getInterval(int value){
return new PGInterval(0, 0, 0, 0, 0, value).getValue();
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index)
throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.VARCHAR);
} else {
//this http://postgresql.1045698.n5.nabble.com/Inserting-Information-in-PostgreSQL-interval-td2175203.html#a2175205
st.setObject(index, getInterval(((Integer) value).intValue()), Types.OTHER);
}
}
@Override
public Object deepCopy(Object value) throws HibernateException {
return value;
}
@Override
public boolean isMutable() {
return false;
}
@Override
public Serializable disassemble(Object value) throws HibernateException {
return (Serializable) value;
}
@Override
public Object assemble(Serializable cached, Object owner)
throws HibernateException {
return cached;
}
@Override
public Object replace(Object original, Object target, Object owner)
throws HibernateException {
return original;
}
}
Ответ 2
Почему бы просто не превратить его в числовое и отобразить в Long?
SELECT EXTRACT(epoch FROM my_interval)
Ответ 3
Как я объяснял в этой статье, вам не нужно писать свой собственный пользовательский тип Hibernate для сопоставления столбца PostgreSQL interval
с объектом Java Duration
. Все, что вам нужно сделать, это использовать проект hibernate-types.
Итак, после добавления правильной зависимости Hibernate:
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>2.6.0</version>
</dependency>
Вам просто нужно использовать аннотацию @TypeDef
, чтобы зарегистрировать PostgreSQLIntervalType
:
@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;
}
}
Теперь при сохранении сущности Book
:
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
мы видим, что атрибут Duration
правильно извлечен из базы данных:
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()
);
Ответ 4
PostgreSQL имеет функцию date_part/extract, которую вы можете использовать для возврата разных полей, одна из которых является эпохой. При извлечении эпохи из интервала вы получаете количество секунд, содержащихся в интервале, и оттуда вы можете конвертировать, как хотите. Я потерял свой опыт в Hibernate, но вы можете сделать это следующим образом:
SELECT
average_interval_between_airings
, date_part('epoch', average_interval_between_airings) / 60 as minutes
, date_part('epoch', average_interval_between_airings) as seconds
FROM shows;
Ответ 5
Согласно ссылке, у вас должен быть день, за которым следуют часы: минуты: секунды. Поэтому, меняя код на что-то вроде следующего, предполагая, что вам не нужно больше 23 часов 59 минут в интервале.
statement.setString(index, "'0 "+ hours +":"
+ intervalFormat.format(minutes) + ":"
+ intervalFormat.format(secondes)+"'");
Я не могу проверить этот код, так как у меня нет PostGreSql. Для другого обсуждения этой же проблемы см. Следующую ссылку, хотя вам придется изменить код, предоставленный для обработки секунд. Однако это не должно быть проблемой.
https://forum.hibernate.org/viewtopic.php?p=2348558&sid=95488ce561e7efec8a2950f76ae4741c