Drazev
Drazev

Reputation: 11

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

I am working on upgrading an existing microservice and extending the database with some new features.

I upgraded the program to use Spring 3.0.6 which uses hibernate 6.1.7. The database I'm using is postgresl testcontainers 1.18.1.

When I attempt to run my program it crashes when attempting to persist the data in the database. The error it provides is as follows...

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 ...

When I looked into the issue I did verify that

Here are some snips from the project.

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

Table Schema

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

What I have read is that if I include jackson-datatype-jsr310 into the project then spring should automatically configure the jackson object mapper to use that module. I would like to use this behavior if possible rather than customizing the object mapper through other means.

I looked up some possible solutions and tried them. The things I tried were

  1. Adding the com.fasterxml.jackson.datatype:jackson-datatype-jsr310 dependency
  2. Adding the @TimeZoneStorage to the @Entity class on the attribute that uses ZonedDateTime
  3. Adding spring.jackson.serialization.write_dates_as_timestamps=true to application.properties

None of the solutions seemed to resolve the error. I am suspicious of solution (2) because I suspect that was replaced by Hibernate 6's behavior where it automatically chooses the right conversion based on the database and java type. Solution (3) also makes me suspicious because it seems to be defining a behavior that is now expected to be automated with hibernate 6 and could potentially cause problems.

Upvotes: 0

Views: 912

Answers (1)

Drazev
Drazev

Reputation: 11

After further debugging I did confirm that spring does load the new module into the ObjectMapper.

This lead me to find that the problem actually was sourced from another object mapper created to do some logging. I resolved this by having it autowire ObjectMapper instance from the spring context instead of creating a local version. Alternatively you could have it load the modules via its builder for the local instance.

I also found an interesting conclusion.

The module jackson-datatype-jsr310 is now considered legacy. The JavaTime module was included into Jackson core but is not auto registered. This can be found in official documentation here.

Note that as of 2.6, this module does NOT support auto-registration, because of existence of legacy version, JSR310Module. Legacy version has the same functionality, but slightly different default configuration: see JSR310Module for details.

Upvotes: 0

Related Questions