Отображение Grails (в режиме гибернации) java.time.ZoneId в базу данных

#hibernate #jpa #grails #grails-3.0 #hibernate-5.x

#переход в спящий режим #jpa #grails #grails-3.0 #переход в спящий режим-5.x

Вопрос:

Есть ли какой-либо способ поддерживать постоянное отображение java.time.ZoneId в строку в гибернации 5.1.1. Это сохраняет ZoneId в двоичной форме прямо сейчас.

Я только что обновился до Grails 3.2.1, который имеет спящий режим 5.1.1. Экономия java.time.Instant, например, отлично работает, однако java.time.ZoneId хранится только в двоичной форме.

Я думаю, что нет поддержки от Hibernate. Итак, как я могу закодировать свое собственное отображение. Я пытался использовать Jadira Framework, но это невозможно, поскольку при запуске приложения grails возникают некоторые конфликты (исключения).

Комментарии:

1. почему бы не сохранить ZoneId.getId() в виде строки, а затем инициализировать его с помощью ZoneId.of(«ZoneId»)?

2. На самом деле это мой обходной путь, но почему-то я чувствую, что это может быть сделано автоматически. По крайней мере, Jadira делала это именно так (я использовал это перед обновлением с Grails 3.1.9 на Grails 3.2.1)

3. Я понимаю, ну, вы всегда можете создать метод @Transient в объекте, который будет выполнять преобразование из строки в идентификатор зоны, чтобы это было прозрачно

Ответ №1:

Вы можете использовать пользовательский преобразователь атрибутов, как определено в 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 атрибута.

Комментарии:

1. Я видел это раньше, однако это не работает в Grails 3, который использует Hibernate 5. Я нашел решение, реализующее мой пользовательский тип пользователя. Смотрите мой ответ ниже. В любом случае, вы указали мне правильное направление…

2. Да, вы всегда можете выбрать тип пользователя, хотя конвертер намного проще. У вас есть какие-либо подробности, почему это не сработало в Grails 3? Я удивлен этим.

Ответ №2:

Вы можете использовать библиотеку типов спящего режима, а затем просто написать

 @Column
private ZoneId zoneId;
  

в ваших классах сущностей. Вы должны пометить класс сущности этой аннотацией:

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

Комментарии:

1. В моем случае у меня есть библиотека типов гибернации, и я могу использовать ZoneId без @TypeDef ! Ожидается ли это?

2. @dk7 Может быть, произошли некоторые изменения в библиотеке типов гибернации с момента публикации этого ответа? Попробуйте использовать эту версию и скажите, работает ли она по-прежнему.

3. обнаружена проблема, я не рассматривал это в своем интеграционном тесте, и я думал, что это работает, даже когда я пытался ее удалить, но нет, это не так! в любом случае спасибо 🙂

4. @dk7 Просто для уточнения: какую версию библиотеки типов гибернации вы используете? Работает ли это без @TypeDef ?

5. Версия типов гибернации: 2.16.2. Нет, это не работает без @TypeDef 🙂

Ответ №3:

Итак, я, наконец, нашел хороший способ реализовать пользовательские типы пользователей в режиме гибернации. Для сохранения 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)
    }
}
  

Затем вам нужно зарегистрировать пользовательский тип пользователя в conf/application.groovy вашем приложении Grails:

 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 Mapping/Usage.html
  2. http://blog.progs.be/550/java-time-hibernate