【问题标题】:Grails (Hibernate) Mapping of java.time.ZoneId to DatabaseGrails (Hibernate) java.time.ZoneId 到数据库的映射
【发布时间】:2017-03-05 23:33:27
【问题描述】:

在 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 应用程序时存在一些冲突(异常),这是不可能的。

【问题讨论】:

  • 为什么不将 zoneId.getId() 保存为字符串,然后使用 ZoneId.of("zoneId") 对其进行初始化?
  • 这实际上是我的解决方法,但不知何故我觉得它可以自动完成。至少 Jadira 是这样做的(我在从 Grails 3.1.9 升级到 Grails 3.2.1 之前使用过它)
  • 我明白了,你总是可以在实体中创建一个 @Transient 方法来进行从字符串到区域 id 的转换,所以它是透明的

标签: hibernate jpa grails grails-3.0 hibernate-5.x


【解决方案1】:

你可以使用Hibernate types库然后直接写

@Column
private ZoneId zoneId;

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

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

【讨论】:

    【解决方案2】:

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

    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:

    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

    【讨论】:

      【解决方案3】:

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

      @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;
      

      在持久化/加载zoneId 属性时会自动调用转换器。

      【讨论】:

      • 我以前见过这个,但是它在使用 Hibernate 5 的 Grails 3 中不起作用。我找到了实现我的自定义用户类型的解决方案。请看下面我的回答。反正你给我指明了好的方向……
      • 是的,您总是可以选择用户类型,尽管转换器要简单得多。你有任何细节为什么它不能在 Grails 3 中工作吗?我对此感到惊讶。
      猜你喜欢
      • 1970-01-01
      • 2015-04-29
      • 2011-05-27
      • 1970-01-01
      • 1970-01-01
      • 2018-06-23
      • 2018-11-29
      • 2014-10-01
      • 1970-01-01
      相关资源
      最近更新 更多