#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
}
Смотрите: