如何在Hibernate中MapPostgreSQL Interval列类型?

qncylg1j  于 2023-11-21  发布在  PostgreSQL
关注(0)|答案(6)|浏览(97)

在PostgreSQL下,我使用PersistentDuration来MapSQL类型interval和duration,但它不起作用。
另一个用户也发现了同样的问题,并使用了他自己的类:

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)+"'"); 

    } 
}

字符串
但它不适用于真实的格式,因为它假设间隔模式仅为“hh:mm:ss”。
这里有一些我需要从数据库中解析的真实的例子:
http://www.postgresql.org/docs/current/interactive/datatype-datetime.html
你有干净的解决方案吗?

v7pvogib

v7pvogib1#

这是一个适用于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;
    }

}

q0qdq0h2

q0qdq0h22#

您不必编写自己的Hibernate自定义类型来将PostgreSQL interval列Map到Java Duration对象。您所需要做的就是使用Hibernate Ttypes项目。
因此,在添加适当的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 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()
);

7qhs6swi

7qhs6swi3#

为什么不把它转换成一个数字,然后Map成一个长型呢?

SELECT EXTRACT(epoch FROM my_interval)

字符串

9gm1akwq

9gm1akwq4#

PostgreSQL有一个date_part / extract函数,你可以用它来返回不同的字段,epoch就是其中之一。当从一个interval中提取epoch时,你会收到interval中包含的秒数,从那里你可以任意转换。我失去了使用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;

字符串

4ktjp1zp

4ktjp1zp5#

根据这个链接,你应该有一天,然后是小时:分钟:秒。所以把代码改成下面这样,假设你永远不需要有超过23小时59分钟的时间间隔。

statement.setString(index, "'0 "+ hours +":" 
+ intervalFormat.format(minutes) + ":" 
+ intervalFormat.format(secondes)+"'");

字符串
我无法测试这段代码,因为我没有安装PostgreSQL。关于这个问题的另一个讨论,请参阅下面的链接,尽管你必须修改提供的代码来处理秒数。这应该不会有太大的问题。
https://forum.hibernate.org/viewtopic.php?p=2348558&sid=95488ce561e7efec8a2950f76ae4741c

vdgimpew

vdgimpew6#

Hibernate能够做到这一点已经有一段时间了。

Hibernate 5

Hibernate 5在Java方面对Duration提供了原生支持,但它希望您将其存储在数据库中的SQL DECIMAL列中,例如,在PostgreSQL中,numeric类型的列。它将简单地读取和写入Duration值作为十进制数。这是有意义的,因为通过这种策略,Hibernate能够始终支持Duration,而不管目标数据库是否对真正的SQL INTERVAL列类型有任何真实的支持。

Hibernate 6(当前)

现在已经有了对PostgreSQL interval列类型的原生支持。(内容如下:不想破坏现有的应用程序),你需要显式地告诉Hibernate哪些应用程序正在使用你的数据库的SQL INTERVAL特性(如果有的话)。如果你不这样做,那么Hibernate仍然会像在v5中那样,也就是说,它将尝试以十进制的形式写入列。显然,如果你的数据库是PostgreSQL,而你的列是interval类型,这将不起作用。
你的实体类应该看起来像这样:

@Entity
@Table(name = "mouse_observations")
public class MyTestEntity {
    ...

    @Column(name = "my_duration", columnDefinition = "interval")
    private Duration myDuration;
    ...
}

字符串
重要提示:
您需要设置hibernate.type.preferred_duration_jdbc_type=INTERVAL_SECOND属性,让Hibernate知道您正在使用数据库的INTERVAL类型。
注意事项:我在这里放的columnDefinition只是为了明确在我的例子中对应的数据库列类型是什么。我不认为Hibernate会在Hibernate场景之外使用columnDefinition。换句话说:你可以省略它。

相关问题