SpringBoot 2.1.9.RELEASE JPA.2.2 не может преобразовать OffsetDateTime - PullRequest
0 голосов
/ 26 мая 2020

У меня есть следующая сущность (сокращенно):

@Entity
@Table(
        name = "IMPORT_RECORD"
)
public class ImportRecordEntity implements Serializable {

    private static final long serialVersionUID = 2483327758356663412L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID")
    private UUID id;

    @Column(name = "IMPORT_TIME_UTC", columnDefinition = "timestamp(9) WITH TIME ZONE")
    private ImportTime importTime;

    public UUID getId() {
        return id;
    }

    public void setId(final UUID id) {
        this.id = id;
    }

    public ImportTime getImportTime() {
        return importTime;
    }

    public void setImportTime(final ImportTime importTime) {
        this.importTime = importTime;
    }
}

ImportTime - это внутренний тип, который в основном охватывает только экземпляр ZonedDataTime, который используется внутри:

public final class ImportTime {

    public static final ImportTime EMPTY = new ImportTime(null);
    private final ZonedDateTime value;

    private ImportTime(final ZonedDateTime pValue) {
        value = pValue;
    }

    public ZonedDateTime getValue() {
        return value;
    }

    public static ImportTime of(final ZonedDateTime pZonedDateTime) {
        return Optional.ofNullable(pZonedDateTime)
                .map(ImportTime::new).orElse(EMPTY);
    }
}

Чтобы закачать это в базу данных, я создал следующий AttributeConverter:

@Converter(autoApply = true)
public class ImportTimeAttributeConverter implements AttributeConverter<ImportTime, OffsetDateTime> {
    @Override
    public OffsetDateTime convertToDatabaseColumn(final ImportTime pImportTime) {
        return Optional.ofNullable(pImportTime)
                .map(ImportTime::getValue)
                .map(ZonedDateTime::toOffsetDateTime)
                .orElse(null);
    }

    @Override
    public ImportTime convertToEntityAttribute(final OffsetDateTime pImportTime) {
        return Optional.ofNullable(pImportTime)
                .map(OffsetDateTime::toZonedDateTime)
                .map(ImportTime::of)
                .orElse(ImportTime.EMPTY);
    }
}

С этой конфигурацией приложение запускается, но после сохранения значений в БД (H2, 1.4.199) во время тестов я получить исключение, которое добавлено в конце этого вопроса.

Короче говоря, он не находит сопоставления из java .time.OffsetDateTime с java. sql .Types.TIMESTAMP, который должен быть определен в org.hibernate.type.descriptor. sql .JdbcTypeJavaClassMappings, что приводит к "HibernateException: запрошено неизвестное преобразование распаковки: java .time.OffsetDateTime to [B"

Я попытался предоставить другой AttributeConverter, но безуспешно.

Достаточно странно, если я не инкапсулирую OffsetDateTime в отдельный класс типа, а использую его непосредственно в Entity, все g работает нормально.

Я использую org.springframework.boot: spring-boot-starter-parent: 2.1.9.RELEASE с org.springframework.boot: spring-boot-starter-data-jpa и org.hibernate: hibernate-core: 5.3.12 и javax.persistence: javax.persistence-api: 2.2

Есть идеи, чего мне здесь не хватает?

Заранее большое спасибо .

Стефан

Обновление: у меня открывается билет в Hibernate: https://hibernate.atlassian.net/browse/HHH-14042

Hibernate: create table import_record (id varbinary not null, import_time_utc timestamp(9) WITH TIME ZONE, primary key (id))
2020-05-26 21:49:18.842  INFO 9228 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-05-26 21:49:18.852  INFO 9228 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-05-26 21:49:20.040  INFO 9228 --- [           main] c.e.e.p.p.r.ImportRecordRepositorySpec   : Started ImportRecordRepositorySpec in 900.689 seconds (JVM running for 902.24)
2020-05-26 21:49:20.070  INFO 9228 --- [           main] o.s.t.c.transaction.TransactionContext   : Began transaction (1) for test context [DefaultTestContext@457c9034 testClass = ImportRecordRepositorySpec, testInstance = com.my.persistence.repository.ImportRecordRepositorySpec@3bde62ff, testMethod = $spock_feature_0_0@ImportRecordRepositorySpec, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@345f69f3 testClass = ImportRecordRepositorySpec, locations = '{}', classes = '{class com.my.persistence.PersistenceConfiguration}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, [ImportsContextCustomizer@50de186c key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@3e2055d6, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@50f6ac94, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@79defdc, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@f5a8e2f5, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@dc9876b, org.springframework.boot.test.context.SpringBootTestArgs@1], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@4e13af1b]; rollback [true]
Hibernate: insert into import_record (import_time_utc, id) values (?, ?)
2020-05-26 21:49:20.503  INFO 9228 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@457c9034 testClass = ImportRecordRepositorySpec, testInstance = com.my.persistence.repository.ImportRecordRepositorySpec@3bde62ff, testMethod = $spock_feature_0_0@ImportRecordRepositorySpec, testException = org.springframework.orm.jpa.JpaSystemException: Unknown unwrap conversion requested: java.time.OffsetDateTime to [B; nested exception is org.hibernate.HibernateException: Unknown unwrap conversion requested: java.time.OffsetDateTime to [B, mergedContextConfiguration = [MergedContextConfiguration@345f69f3 testClass = ImportRecordRepositorySpec, locations = '{}', classes = '{class com.my.persistence.PersistenceConfiguration}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, [ImportsContextCustomizer@50de186c key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@3e2055d6, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@50f6ac94, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@79defdc, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@f5a8e2f5, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@dc9876b, org.springframework.boot.test.context.SpringBootTestArgs@1], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.spockframework.spring.SpringMockTestExecutionListener.MOCKED_BEANS_LIST' -> list[[empty]]]]

org.springframework.orm.jpa.JpaSystemException: Unknown unwrap conversion requested: java.time.OffsetDateTime to [B; nested exception is org.hibernate.HibernateException: Unknown unwrap conversion requested: java.time.OffsetDateTime to [B

    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:353)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:255)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:178)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    at com.my.persistence.repository.ImportRecordRepositorySpec.save entity(ImportRecordRepositorySpec.groovy:38)
Caused by: org.hibernate.HibernateException: Unknown unwrap conversion requested: java.time.OffsetDateTime to [B
    at org.hibernate.type.descriptor.java.AbstractTypeDescriptor.unknownUnwrap(AbstractTypeDescriptor.java:98)
    at org.hibernate.type.descriptor.java.OffsetDateTimeJavaDescriptor.unwrap(OffsetDateTimeJavaDescriptor.java:98)
    at org.hibernate.type.descriptor.java.OffsetDateTimeJavaDescriptor.unwrap(OffsetDateTimeJavaDescriptor.java:25)
    at org.hibernate.type.descriptor.sql.VarbinaryTypeDescriptor$1.doBind(VarbinaryTypeDescriptor.java:45)
    at org.hibernate.type.descriptor.sql.BasicBinder.bind(BasicBinder.java:73)
    at org.hibernate.type.descriptor.converter.AttributeConverterSqlTypeDescriptorAdapter$1.bind(AttributeConverterSqlTypeDescriptorAdapter.java:88)
    at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:276)
    at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:271)
    at org.hibernate.type.AbstractSingleColumnStandardBasicType.nullSafeSet(AbstractSingleColumnStandardBasicType.java:39)
    at org.hibernate.persister.entity.AbstractEntityPersister.dehydrate(AbstractEntityPersister.java:2929)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3226)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3760)
    at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:107)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604)
    at org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:478)
    at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:684)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:475)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:348)
    at org.hibernate.event.internal.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:57)
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:102)
    at org.hibernate.internal.SessionImpl.autoFlushIfRequired(SessionImpl.java:1317)
    at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1397)
    at org.hibernate.query.internal.AbstractProducedQuery.doList(AbstractProducedQuery.java:1565)
    at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1533)
    at org.hibernate.query.Query.getResultList(Query.java:165)
    at org.hibernate.query.criteria.internal.compile.CriteriaQueryTypeQueryAdapter.getResultList(CriteriaQueryTypeQueryAdapter.java:76)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll(SimpleJpaRepository.java:355)
    at org.springframework.data.repository.core.support.ImplementationInvocationMetadata.invoke(ImplementationInvocationMetadata.java:72)
    at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:382)
    at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:205)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:549)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:155)
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:130)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:366)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
    ... 7 more

2020-05-26 21:49:20.517  INFO 9228 --- [extShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'

1 Ответ

0 голосов
/ 27 мая 2020

Если вы посмотрите на трассировку стека, используемый дескриптор типа Java - OffsetDateTimeJavaDescriptor, а используемый дескриптор типа Sql - VarbinaryTypeDescriptor, т.е. Hibernate рассматривает класс ImportTime как переменную. двоичный (по праву), но он использует OffsetDateTimeJavaDescriptor в качестве преобразователя типов из java в sql типа, поскольку вы используете преобразователь, который преобразуется в тип OffsetDateTime. Это не сработает, потому что, если вы посмотрите на метод OffsetDateTimeJavaDescriptor#unwrap, ни одно из условий не будет соответствовать типу Java для преобразования в тип столбца sql.

В идеальном сценарии ваш код должен предлагать Hibernate для используйте VarbinaryTypeDescriptor в качестве дескриптора типа sql и SerializableTypeDescriptor в качестве дескриптора типа Java.

Вот как это сделать:

Удалить определение столбца:

@Column(name = "IMPORT_TIME_UTC")
private ImportTime importTime;

Сделать ImportTime реализовать Serializable:

public final class ImportTime implements Serializable {
  //the rest of code
}

Удалить конвертер или просто закомментировать аннотацию

//@Converter(autoApply = true)
public class ImportTimeAttributeConverter implements AttributeConverter<ImportTime, OffsetDateTime> {
 // The rest of the code
}

Теперь вы сможете выполнить успешную вставку а также должен иметь возможность правильно извлекать данные. Однако небольшая проблема: тип данных ImportTime в вашей БД не будет меткой времени, что может быть не тем, что вам нужно.

Если вы хотите, чтобы столбец DB имел временную метку, вам нужно немного потрудиться и создать свой собственный TypeDescriptor для ImportTime. Подробное руководство для этого: здесь

...