SpringBoot 2.1.9.RELEASE JPA.2.2不能转换OffsetDateTime。

问题描述 投票:0回答:1

我有以下实体(简略的说)。

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

有了这个配置,应用程序启动了,但是在测试过程中,当把值保存到DB(H2,1.4.199)时,我得到了异常,这是在这个问题的最后添加的。

简而言之,它没有找到从java.time.OffsetDateTime到java.sql.Types.TIMESTAMP的映射,而这个映射应该在org.hibernate.type.descriptor.sql.JdbcTypeJavaClassMappings中定义,导致 "HibernateException: Unknown unwrap conversion requested: java.time.OffsetDateTime to [B"

我曾试图提供另一个AttributeConverter,但没有成功。

奇怪的是,如果我不把OffsetDateTime封装在一个独立的类型类中,而是直接在实体上使用它,一切都能正常工作。

我使用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。

有什么想法,我在这里遗漏了什么?

先谢谢你了。

Stefan

更新:我在Hibernate开了一个票。https:/hibernate.atlassian.netbrowseHHH-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'
hibernate spring-boot converters
1个回答
0
投票

如果你看一下堆栈跟踪,正在使用的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中不会是一个时间戳,这可能不是你想要的。

如果你想让DB列成为时间戳,那么你需要更加努力,创建你自己的 TypeDescriptor 对于 ImportTime. 这方面的综合教程是 此处

© www.soinside.com 2019 - 2024. All rights reserved.