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为原生映射:
- 修改
IdentifierHistoryEntity的字段注解:
@Column(columnDefinition = "jsonb") @JdbcTypeCode(SqlTypes.JSON) private IdentifierEntity identifier;
- 创建自定义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); } }
- 在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序列化保留时区:
- 编写自定义格式映射器,配置正确的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; } }
- 在实体类中通过
@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; // ... 其他字段省略 }
- 移除配置文件中
unsupported-properties下的hibernate.type.json_format_mapper项,改用上述注解方式绑定映射器。
关键说明
- Hibernate ORM的时区配置仅作用于直接映射的实体字段,JsonB类型的序列化由Jackson/JsonB库独立处理,需单独配置。
- 使用第三方
JsonBinaryType时,全局配置可能因Quarkus类加载机制失效,通过@TypeDef显式指定参数更可靠。 - 必须使用
DateTimeFormatter.ISO_OFFSET_DATE_TIME作为序列化格式,才能保留时区偏移信息。
内容的提问来源于stack exchange,提问作者AJO
相关产品推荐
相关产品推荐

