java.time.ZoneId 到数据库的 Grails (Hibernate) 映射

Grails (Hibernate) Mapping of java.time.ZoneId to Database

有什么方法可以支持 Hibernate 5.1.1 中 java.time.ZoneId 到字符串的持久映射。它现在以二进制形式保存 ZoneId。

我刚刚升级到具有 Hibernate 5.1.1 的 Grails 3.2.1。例如,保存 java.time.Instant 工作正常,但 java.time.ZoneId 仅以二进制形式存储。

我认为 Hibernate 不支持。那么我如何编写自己的映射。我尝试使用 Jadira Framework 但这是不可能的,因为在启动 grails 应用程序时存在一些冲突(异常)。

您可以使用 JPA 2.1 定义的自定义属性转换器。像这样声明转换器 class:

@Converter
public static class ZoneIdConverter implements AttributeConverter<ZoneId, String> {

    @Override
    public String convertToDatabaseColumn(ZoneId attribute) {
        return attribute.getId();
    }

    @Override
    public ZoneId convertToEntityAttribute(String dbData) {
        return ZoneId.of( dbData );
    }
}

然后从ZoneId类型的实体属性中引用:

@Convert(converter = ZoneIdConverter.class)
private ZoneId zoneId;

当 persisting/loading zoneId 属性时,将自动调用转换器。

所以我终于找到了一种实现自定义休眠用户类型的好方法。要坚持 java.time.ZoneId 作为 varchar 实现以下用户类型 class:

import org.hibernate.HibernateException
import org.hibernate.engine.spi.SessionImplementor
import org.hibernate.type.StandardBasicTypes
import org.hibernate.usertype.EnhancedUserType

import java.sql.PreparedStatement
import java.sql.ResultSet
import java.sql.SQLException
import java.sql.Types
import java.time.ZoneId

/**
 * A type that maps between {@link java.sql.Types#VARCHAR} and {@link ZoneId}.
 */
class ZoneIdUserType implements EnhancedUserType, Serializable {

    private static final int[] SQL_TYPES = [Types.VARCHAR]

    @Override
    public int[] sqlTypes() {
        return SQL_TYPES
    }

    @Override
    public Class returnedClass() {
        return ZoneId.class
    }

    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        if (x == y) {
            return true
        }
        if (x == null || y == null) {
            return false
        }
        ZoneId zx = (ZoneId) x
        ZoneId zy = (ZoneId) y
        return zx.equals(zy)
    }

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

    @Override
    public Object nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session, Object owner)
        throws HibernateException, SQLException {
        Object zoneId = StandardBasicTypes.STRING.nullSafeGet(resultSet, names, session, owner)
        if (zoneId == null) {
            return null
        }
        return ZoneId.of(zoneId)
    }

    @Override
    public void nullSafeSet(PreparedStatement preparedStatement, Object value, int index, SessionImplementor session)
        throws HibernateException, SQLException {
        if (value == null) {
            StandardBasicTypes.STRING.nullSafeSet(preparedStatement, null, index, session)
        } else {
            def zoneId = (ZoneId) value
            StandardBasicTypes.STRING.nullSafeSet(preparedStatement, zoneId.getId(), index, session)
        }
    }

    @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 value) throws HibernateException {
        return cached
    }

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

    @Override
    public String objectToSQLString(Object object) {
        throw new UnsupportedOperationException()
    }

    @Override
    public String toXMLString(Object object) {
        return object.toString()
    }

    @Override
    public Object fromXMLString(String string) {
        return ZoneId.of(string)
    }
}

然后您需要在您的 Grails 应用的 conf/application.groovy 中注册自定义用户类型:

grails.gorm.default.mapping = {
    'user-type'(type: ZoneIdUserType, class: ZoneId)
}

你可以在你的域中简单地使用 java.time.ZoneId class:

import java.time.ZoneId

class MyDomain {
    ZoneId zoneId
}

参见:

  1. http://docs.grails.org/latest/ref/Database%20Mapping/Usage.html
  2. http://blog.progs.be/550/java-time-hibernate

您可以使用 Hibernate types 库,然后只需编写

@Column
private ZoneId zoneId;

在您的实体中 classes。您必须使用此注释标记实体 class:

@TypeDef(typeClass = ZoneIdType.class, defaultForType = ZoneId.class)