Let Hibernate recognize database-specific fields

Hibernate provides rich data type support, but for some database-specific data types, the support provided is very limited. For example, the Interval type of PostgreSQL is very convenient for saving a "time period" data.

In development, we expect to map the Interval type to the Java 8 Duration type. However, Hibernate's default mapping of the Duration type is directly mapped to the BigInt type of the database, which directly saves the nanosecond value. Obviously for databases that don't directly support the Interval type, it's more appropriate, but we still expect to map directly to the database's Interval type.

To this end, we need to adjust the Hibernate mapping relationship between the two data types (Duration in the Java world and Interval in the Db world).

Fortunately, Hibernate provides very convenient methods to implement data type mapping.

For this, we need a class that implements the org.hibernate.usertype.UserType interface to implement the data conversion/mapping work between the two worlds.

Hibernate's custom type (UserType)

UserType is a custom data type interface provided by Hibernate. All custom data needs to implement this interface, or choose an appropriate interface from the interfaces defined in org.hibernate.usertype.

Given that our scenario is relatively simple, directly implementing UserType can meet the requirements. This interface provides the following set of methods that you need to implement yourself:

  • assemble(Serializable cached, Object owner)

    Rebuild (Java) object from serialization.

  • deepCopy(Object value)

    Returns a deep copy.

  • disassemble(Object value)

    Convert the serialized data of an object.

  • equals(Object x, Object y)

    Returns whether the data of two maps are equal.

  • hashCode(Object x)

    Get the hash of the object.

  • isMutable()

    Returns whether the object is a mutable type.

  • nullSafeGet(ResultSet rs, String[] names, Object owner)

    Returns the corresponding Java object from database type data. core implementation method

  • nullSafeSet(PreparedStatement st, Object value, int index)

    From the Java object, return the data of the corresponding database type. core implementation method

  • replace(Object original, Object target, Object owner)

    During merging, replace the target value (target) in the entity with the original value (original).

  • returnedClass()

    The class returned by nullSafeGet.

  • sqlTypes()

    Returns the corresponding database type.

example

package framework.postgresql;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.usertype.UserType;
import org.postgresql.util.PGInterval;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.time.Duration;

/**
 * PostgreSql Inteval字段与java.time.Duration映射
 * 目前只支持到最多1个月(30天)的间隔
 * <p>
 * 使用方法:
 * 在实体类上增加
 * \@TypeDef(name="interval", typeClass = IntervalType.class)
 * 在字段定义上增加:
 * \@Type(type = "interval")
 * <p>
 * http://stackoverflow.com/questions/1945615/how-to-map-the-type-interval-in-hibernate/6139581#6139581
 *
 * @version 1.0
 * @since 1.0
 */
public class IntervalType implements UserType {

    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        return cached;
    }

    public Object deepCopy(Object value) throws HibernateException {
        return value;
    }

    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) value;
    }

    public boolean equals(Object arg0, Object arg1) throws HibernateException {
        return arg0 != null && arg1 != null && arg0.equals(arg1) || arg0 == null && arg1 == null;
    }

    public int hashCode(Object object) throws HibernateException {
        return object.hashCode();
    }


    @Override
    public Object nullSafeGet(ResultSet resultSet, String[] names, SharedSessionContractImplementor sessionImplementor, Object o) throws HibernateException, SQLException {
        String interval = resultSet.getString(names[0]);
        if (resultSet.wasNull() || interval == null) {
            return null;
        }
        PGInterval pgInterval = new PGInterval(interval);

        return getDuration(pgInterval);
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor sessionImplementor) throws HibernateException, SQLException {
        if (value == null) {
            st.setNull(index, Types.OTHER);
        } else {
            //this http://postgresql.1045698.n5.nabble.com/Inserting-Information-in-PostgreSQL-interval-td2175203.html#a2175205
            Duration duration = (Duration) value;
            st.setObject(index, getInterval(duration), Types.OTHER);
        }
    }

    public static Duration getDuration(PGInterval pgInterval) {
        return Duration.ofSeconds(pgInterval.getDays() * 24 * 3600 +
                pgInterval.getHours() * 3600 +
                pgInterval.getMinutes() * 60 +
                (int) pgInterval.getSeconds());
    }

    private static PGInterval getInterval(Duration value) {
        long seconds = value.getSeconds();
        int days = (int) (seconds / (24 * 3600));
        seconds -= days * 24 * 3600;
        int hours = (int) (seconds / 3600);
        seconds -= hours * 3600;
        int minutes = (int) (seconds / 60);
        seconds -= minutes * 60;
        seconds = Math.abs(seconds);
        return new PGInterval(0, 0, days, hours, minutes, seconds);
    }


    public boolean isMutable() {
        return false;
    }


    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return original;
    }

    public Class returnedClass() {
        return Duration.class;
    }

    public int[] sqlTypes() {
        return new int[]{Types.OTHER};
    }

}

Use custom type

So far, we have defined our own data types. But Hibernate doesn't know how to use it yet. To do this, we need to use the TypeDef annotation on the Entity and the Type annotation on the property.

for example:

...
@Entity
@TypeDef(name = "interval", typeClass = IntervalType.class)
public class PaperStatis implements Serializable {
...
    @Column(name = "avg_duration")
    @Type(type = "interval")
    public Duration getAvgDuration() {
        return this.avgDuration;
    }
...
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324375663&siteId=291194637