You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Quarkus 3迁移中JsonB字段OffsetDateTime时区序列化异常问题

问题:Quarkus 3中JsonB字段内的OffsetDateTime序列化丢失时区

迁移至Quarkus 3后,单个实体(如IdentifierEntity)中的OffsetDateTime字段通过配置hibernate-orm.mapping.timezone.default-storage="NORMALIZE"可正常保留时区信息,但在嵌套的JsonB字段(IdentifierHistoryEntity中的identifier字段,使用JsonBinaryType)里,OffsetDateTime被序列化为UTC格式,丢失了时区偏移信息。已尝试添加@JdbcTypeCode(SqlTypes.JSON)和自定义FormatMapper并配置hibernate.type.json_format_mapper,均未解决问题。


解决方案1:改用Quarkus原生JsonB支持替换JsonBinaryType

Quarkus自带的JsonB集成可更好适配全局配置,替换第三方JsonBinaryType为原生映射:

  1. 修改IdentifierHistoryEntity的字段注解:
@Column(columnDefinition = "jsonb")
@JdbcTypeCode(SqlTypes.JSON)
private IdentifierEntity identifier;
  1. 创建自定义JsonB配置类,指定OffsetDateTime的序列化规则:
import jakarta.json.bind.JsonbConfig;
import jakarta.json.bind.annotation.JsonbDateFormat;
import jakarta.json.bind.serializer.*;
import jakarta.json.stream.JsonGenerator;
import jakarta.json.stream.JsonParser;

import java.lang.reflect.Type;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;

public class JsonbOffsetDateTimeConfig implements JsonbSerializer<OffsetDateTime>, JsonbDeserializer<OffsetDateTime> {

    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME;

    @Override
    public void serialize(OffsetDateTime value, JsonGenerator generator, SerializationContext ctx) {
        generator.write(FORMATTER.format(value));
    }

    @Override
    public OffsetDateTime deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) {
        return OffsetDateTime.parse(parser.getString(), FORMATTER);
    }

    public static JsonbConfig getConfig() {
        return new JsonbConfig()
                .withSerializers(new JsonbOffsetDateTimeConfig())
                .withDeserializers(new JsonbOffsetDateTimeConfig())
                .withDateFormat(JsonbDateFormat.ISO_OFFSET_DATE_TIME);
    }
}
  1. 在Quarkus配置文件中注册该JsonB配置:
quarkus:
  jsonb:
    config-class: "myPackage.JsonbOffsetDateTimeConfig"
  hibernate-orm:
    mapping:
      timezone:
        default-storage: "NORMALIZE"
    jdbc:
      timezone: "Europe/Paris"

解决方案2:为JsonBinaryType指定自定义ObjectMapper

如果坚持使用JsonBinaryType,需显式配置其使用的ObjectMapper,确保OffsetDateTime序列化保留时区:

  1. 编写自定义格式映射器,配置正确的Jackson规则:
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.OffsetDateTimeSerializer;
import io.hypersistence.utils.hibernate.type.json.JacksonJsonFormatMapper;

import java.time.format.DateTimeFormatter;
import java.util.TimeZone;

public class CustomJsonFormatMapper extends JacksonJsonFormatMapper {

    public CustomJsonFormatMapper() {
        super(createCustomObjectMapper());
    }

    private static ObjectMapper createCustomObjectMapper() {
        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);
        
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        // 强制使用带时区的ISO格式序列化
        javaTimeModule.addSerializer(OffsetDateTime.class, 
            new OffsetDateTimeSerializer(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
        
        objectMapper.registerModule(javaTimeModule);
        objectMapper.setTimeZone(TimeZone.getTimeZone("Europe/Paris"));
        return objectMapper;
    }
}
  1. 在实体类中通过@TypeDef为JsonBinaryType指定该映射器:
import io.hypersistence.utils.hibernate.type.json.JsonBinaryType;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;

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")
@TypeDef(
    name = "custom_jsonb",
    typeClass = JsonBinaryType.class,
    parameters = {
        @Parameter(name = "format_mapper", value = "myPackage.CustomJsonFormatMapper")
    }
)
public class IdentifierHistoryEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String idohp;

    @Type(type = "custom_jsonb")
    @Column(columnDefinition = "jsonb")
    private IdentifierEntity identifier;

    // ... 其他字段省略
}
  1. 移除配置文件中unsupported-properties下的hibernate.type.json_format_mapper项,改用上述注解方式绑定映射器。

关键说明

  • Hibernate ORM的时区配置仅作用于直接映射的实体字段,JsonB类型的序列化由Jackson/JsonB库独立处理,需单独配置。
  • 使用第三方JsonBinaryType时,全局配置可能因Quarkus类加载机制失效,通过@TypeDef显式指定参数更可靠。
  • 必须使用DateTimeFormatter.ISO_OFFSET_DATE_TIME作为序列化格式,才能保留时区偏移信息。

内容的提问来源于stack exchange,提问作者AJO

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 08:52:42