16 votes

Comment mapper le type de colonne Interval de PostgreSQL dans Hibernate ?

Sous PostgreSQL, j'utilise PersistentDuration pour la correspondance entre le type sql interval & duration mais cela ne fonctionne pas.

Un autre utilisateur a rencontré le même problème et a créé sa propre classe :

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

    } 
}

Mais cela ne fonctionne pas avec le format réel car il suppose que le modèle d'intervalle est seulement "hh:mm:ss". Ce n'est pas le cas : voir

Voici quelques exemples réels que je dois analyser à partir de la base de données :

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

Avez-vous une solution propre ?

5voto

bpgergo Points 9407

Il s'agit d'une solution opérationnelle pour JPA et Hibernate (avec des annotations).

C'est le début de la classe d'entité (pour la table qui a une colonne Intervalle) :

@Entity
@Table(name="table_with_interval_col")
@TypeDef(name="interval", typeClass = Interval.class)
public class TableWithIntervalCol implements Serializable {

Il s'agit de la colonne d'intervalle :

@Column(name = "interval_col",  nullable = false)
@Type(type = "interval")    
private Integer intervalCol;

Il s'agit de la classe 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;
    }

}

5voto

Vlad Mihalcea Points 3628

Vous n'avez pas besoin d'écrire votre propre type personnalisé Hibernate pour mapper les données de PostgreSQL. interval à une colonne Java Duration objet. Il suffit d'utiliser la fonction Ttypes Hibernate projet.

Ainsi, après avoir ajouté la dépendance Hibernate appropriée :

<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>2.6.0</version>
</dependency>

Il suffit d'utiliser la fonction @TypeDef pour enregistrer l'annotation 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;
    }
}

Désormais, lors de la persistance du Book l'entité :

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 exécutera l'instruction SQL INSERT appropriée :

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
)

Lors de la recherche du Book nous pouvons voir que l'entité Duration est correctement récupéré dans la base de données :

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()
);

2voto

pstanton Points 8807

Pourquoi ne pas le transformer en valeur numérique et l'associer à un Long ?

SELECT EXTRACT(epoch FROM my_interval)

1voto

PostgreSQL dispose d'une fonction date_part / extract que vous pouvez utiliser pour retourner différents champs, dont l'époque. En extrayant l'époque d'un intervalle, vous obtenez le nombre de secondes contenues dans l'intervalle, et à partir de là, vous pouvez convertir comme vous le souhaitez. J'ai perdu mon expérience avec Hibernate, mais vous pouvez le faire de cette manière :

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;

0voto

Jared Points 23711

D'après le lien, vous devriez avoir un jour suivi d'heures:minutes:secondes. Il faut donc remplacer le code par quelque chose comme ce qui suit, en supposant que l'on n'ait jamais besoin d'avoir plus de 23 heures et 59 minutes dans l'intervalle.

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

Je ne suis pas en mesure de tester ce code car je n'ai pas installé PostGreSql. Pour une autre discussion sur ce même problème, voir le lien suivant, bien qu'il faille modifier le code fourni pour gérer les secondes. Cela ne devrait cependant pas poser de problème.

https://forum.hibernate.org/viewtopic.php?p=2348558&sid=95488ce561e7efec8a2950f76ae4741c

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X