I am migrating to quarkus 3 and i couldnt find a way to serialize properly (like in hibernate 5) my OffsetDateTime for a field annotated with jsonb in an entity :
This is one of my entity, OffsetDateTime is not a problem here when persisting because i added the parameter hibernate-orm.mapping.timezone.default-storage="NORMALIZE" in configuration so i get my date right with the timezone
package myPackage;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.orange.erable.ohp.common.quarkus.rest.internal.identifier.models.IdentifierCharacteristicNameDTO;
import com.orange.erable.ohp.persistence.entities.enums.EIdentifierCategory;
import com.orange.erable.ohp.persistence.entities.enums.EIdentifierStatus;
import io.hypersistence.utils.hibernate.type.basic.PostgreSQLEnumType;
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
import lombok.Data;
import org.hibernate.annotations.Type;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import jakarta.validation.constraints.NotNull;
import java.lang.reflect.Field;
import java.time.OffsetDateTime;
import java.util.UUID;
@Data
@Entity
@Table(name = "t_identifier")
public class IdentifierEntity extends PanacheEntityBase {
@Id
private String idohp;
private String msisdn;
private String idAdv;
private String contractAid;
private String countryCode;
@NotNull
@Enumerated(EnumType.STRING)
@Type(PostgreSQLEnumType.class)
@Column(columnDefinition = "status")
private EIdentifierStatus status;
@Enumerated(EnumType.STRING)
@Type(PostgreSQLEnumType.class)
@Column(columnDefinition = "category")
private EIdentifierCategory category;
@Column(updatable = false)
private OffsetDateTime creationDate = OffsetDateTime.now();
private OffsetDateTime updateDate = OffsetDateTime.now();
@JsonInclude(JsonInclude.Include.NON_NULL)
@Transient
private boolean updated = false;
@JsonInclude(JsonInclude.Include.NON_NULL)
@Transient
private boolean created = false;
}
But for this entity which includes previous entity, the OffsetDateTime inside identifier are not persisted with the right format, it is persisted with UTC format, im missing my timezone:
package myPackage;
import io.hypersistence.utils.hibernate.type.json.JsonBinaryType;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.*;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.time.OffsetDateTime;
@Data
@NoArgsConstructor
@Entity
@Table(name = "t_identifier_history")
public class IdentifierHistoryEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String idohp;
@Type(JsonBinaryType.class)
@Column(columnDefinition = "jsonb")
private IdentifierEntity identifier;
@Column(insertable = false, updatable = false)
private OffsetDateTime creationDate = OffsetDateTime.now();
@Column(insertable = false)
private OffsetDateTime updateDate = OffsetDateTime.now();
private String historyVersion;
public IdentifierHistoryEntity(IdentifierEntity identifierEntity, String historyVersion) {
this.idohp = identifierEntity.getIdohp();
this.identifier = identifierEntity;
this.historyVersion = historyVersion;
}
}
I have tried setting @JdbcTypeCode( SqlTypes.JSON ) on identifier field
I have tried to create my own format mapper and refer to it with quarkus.hibernate-orm.unsupported-properties."hibernate.type.json_format_mapper"="myCustomFormatMapper" Here is the format mapper :
public class RegisterJacksonCustomizer implements FormatMapper {
private final FormatMapper delegate = new JacksonJsonFormatMapper(createObjectMapper());
private static ObjectMapper createObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.disable(SerializationFeature.WRAP_ROOT_VALUE);
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
final JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(OffsetDateTime.class, new CustomOffsetDateTimeSerializer(DATE_TIME_FORMATTER));
javaTimeModule.addDeserializer(OffsetDateTime.class, new CustomOffsetDateTimeDeserializer(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
@Override
public <T> T fromString(CharSequence charSequence, JavaType<T> javaType, WrapperOptions wrapperOptions) {
return delegate.fromString(charSequence, javaType, wrapperOptions);
}
@Override
public <T> String toString(T t, JavaType<T> javaType, WrapperOptions wrapperOptions) {
return delegate.toString(t, javaType, wrapperOptions);
}
}
Combined with this configuration :
quarkus:
hibernate-orm:
mapping:
timezone:
default-storage: "NORMALIZE"
jdbc:
timezone: "Europe/Paris"
unsupported-properties:
- "hibernate.type.json_format_mapper": "pathToMyMapper"
I see several confusing things in your questions, so I'll address them separately.
Mapping as JSON most likely ignores object/relational mapping!
When you map a property as JSON/XML, you're sidestepping any relational mapping, so it's no surprise that the dates inside the
jsonbidentifier would ignorequarkus.hibernate-orm.mapping.timezone.default-storage. Same would happen if, say, anIntegercolumn inIdentifierEntitywas mapped as a string in the database: JSON serialization would still map it as an integer.If you want to alter the way your entity gets serialized to JSON, you will have to use a custom
FormatMapper, which doesn't have first-class support in Quarkus yet but has an unsupported workaround. You're claiming you tried, but if you want help on that, you should explain what didn't work exactly.NORMALIZEwill not preserve the timezone!You're using
hibernate-orm.mapping.timezone.default-storage=NORMALIZE, so what you're getting is exactly the expected behavior: date/times are normalized to UTC.See this section of the documentation:
If you want to preserve the timezone, I'd suggest you use
AUTO, which will use a native DB type that preserves the timezone if possible, or a separate column for the timezone otherwise.Update after your edit
Your configuration is missing the
quarkusprefix?Well that would be a problem since you'll lose the timezone on updates.
I'll reiterate:
About your format mapper, sorry but we're getting too far from my area of expertise, let's home someone chimes in to tell you what's wrong in your implementation. You might want to show the code of
CustomOffsetDateTimeSerializer.