如何正确配置 Spring Boot 以使用 java.time.ZonedDateTime 进行 postgresql 的持久性?

How do I properly configure Spring boot to use java.time.ZonedDateTime for persistance with postgresql?

提问人:Drazev 提问时间:7/6/2023 最后编辑:AlbinaDrazev 更新时间:7/7/2023 访问量:179

问:

我正在努力升级现有的微服务,并用一些新功能扩展数据库。

我将程序升级为使用 Spring 3.0.6,它使用 hibernate 6.1.7。我使用的数据库是postgresl testcontainers 1.18.1。

当我尝试运行我的程序时,它在尝试将数据保存在数据库中时崩溃。它提供的错误如下...

java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.ZonedDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling ...

当我调查这个问题时,我确实验证了这一点

  • Postgresql 支持 Zoned Timetmaps
  • Hibernate 6 支持ZonedDateTime

以下是该项目的一些片段。

pom.xml

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.6</version>
    </parent>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>       
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.ldap</groupId>
            <artifactId>spring-ldap-core</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-ldap</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-csv</artifactId>
        </dependency>

        <!--    Java 8 Date/time    -->
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>

@Entity
@Table(name="billable_Data",schema="mySchema")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class BillableMetric {

    @Id
    UUID guid;

    @ManyToOne(fetch = FetchType.LAZY)
    CustomerInvoice invoice;

    @ManyToOne(fetch = FetchType.LAZY)
    CustomerRecord billingAccount;

    @OneToOne(fetch = FetchType.LAZY)
    CatalogueItem skuItem;

    @Column(name="dataSourceIdentifier")
    BillableMetricDataSource dataSource;

    Double quantity;

    String unitType;

    ZonedDateTime startTimeInclusive;

    ZonedDateTime endTimeExclusive;
}

表架构

CREATE TABLE mySchema.billable_Data (
    guid uuid  NOT NULL DEFAULT mySchema.gen_random_uuid(),
    invoice bigserial,
    billingAccount uuid, -- Can be null since one possible error is the billing account cannot be found
    skuItem bigserial, -- Can be null since one possible error is the sku is not valid.
    dataSource numeric NOT NULL, --Should be an enum that is associated to a MetricReading Table
    quantity numeric,
    unitType varchar(100),
    startTimeInclusive timestamp ,
    endTimeExclusive timestamp ,
    CONSTRAINT guid_is_pk PRIMARY KEY (guid),
    CONSTRAINT invoice_is_fk FOREIGN KEY (invoice) REFERENCES mySchema.customer_invoice(id),
    CONSTRAINT billable_data_sku_is_fk FOREIGN KEY (skuItem) REFERENCES mySchema.catalog_item(id),
    CONSTRAINT billable_data_billing_account_is_fk FOREIGN KEY (billingAccount)  REFERENCES mySchema.customer(guid)
);

我所读到的是,如果我将 jackson-datatype-jsr310 包含在项目中,那么 spring 应该自动配置 jackson 对象映射器以使用该模块。如果可能的话,我想使用这种行为,而不是通过其他方式自定义对象映射器。

我查找了一些可能的解决方案并尝试了它们。我尝试过的事情是

  1. 添加依赖项com.fasterxml.jackson.datatype:jackson-datatype-jsr310
  2. 将 添加到使用@TimeZoneStorage@EntityZonedDateTime
  3. 添加到spring.jackson.serialization.write_dates_as_timestamps=trueapplication.properties

似乎没有一个解决方案可以解决这个错误。我对解决方案 (2) 持怀疑态度,因为我怀疑它被 Hibernate 6 的行为所取代,它根据数据库和 java 类型自动选择正确的转换。解决方案 (3) 也让我怀疑,因为它似乎正在定义一种行为,现在预计该行为有望在 hibernate 6 中自动执行,并可能导致问题。

Java PostgreSQL 休眠 spring-data-jpa zoneddatetime

评论


答:

0赞 Drazev 7/7/2023 #1

经过进一步的调试,我确实确认spring确实将新模块加载到ObjectMapper中。

这让我发现问题实际上来自另一个为进行一些日志记录而创建的对象映射器。我通过让它从 spring 上下文中自动连接 ObjectMapper 实例而不是创建本地版本来解决这个问题。或者,您可以让它通过本地实例的构建器加载模块。

我还发现了一个有趣的结论。

模块 jackson-datatype-jsr310 现在被视为旧模块。JavaTime 模块包含在 Jackson 核心中,但未自动注册。这可以在官方文档中找到 此处.

请注意,从 2.6 开始,由于存在旧版本 JSR310Module,此模块不支持自动注册。旧版本具有相同的功能,但默认配置略有不同:有关详细信息,请参见 JSR310Module。