迁移至Quarkus+Hibernate:如何定位PersonBO的JdbcType推荐异常
从Payara+EclipseLink迁移到Quarkus(Hibernate)启动异常排查
问题背景
将应用从Payara + EclipseLink迁移至Quarkus(使用Hibernate)时,启动阶段Hibernate抛出如下异常:
JdbcTypeRecommendationException
Failed to start quarkus: io.quarkus.dev.appstate.ApplicationStartException:
org.hibernate.type.descriptor.java.spi.JdbcTypeRecommendationException:
Could not determine recommended JdbcType forEntity
该应用在EclipseLink环境下运行正常。已为PostgreSQL中所有对应JSONB列的属性添加Hibernate专属注解@JdbcTypeCode(SqlTypes.JSON),且已知错误可能来自关联对象,但关联对象数量较多,难以逐一排查。
疑问
- 能否让Hibernate明确指出触发异常的具体类和属性?
- Hibernate强制要求给
PersonBO添加@DiscriminatorValue(value = "P")注解,但EclipseLink和JPA 2.2均无此要求,该问题是否与启动异常相关?
相关实体代码
PersonBO 实体类
@Entity @Access(AccessType.PROPERTY) @Table(name = PersonBO.TABLENAME) @Inheritance(strategy = InheritanceType.JOINED) @DiscriminatorColumn(discriminatorType = DiscriminatorType.CHAR, name = "prssubclass") @DiscriminatorValue(value = "P") public class PersonBO { /** Name of this database table is {@value} */ final public static String TABLENAME = "person"; // --- Database columns private UUID id = UUID.randomUUID(); private List<AddressGeoBO> adrGeos = null; private Map<String, Object> prsAttributes = null; private Gender prsGender = null; private Set<UUID> tags = null; @Convert(converter = UuidConverter4jpa.class) @GeneratedValue(strategy = GenerationType.UUID) @Id public UUID getId() { return id; } @Json @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = AddressGeoBO.FIELD_PERSONBO, targetEntity = AddressGeoBO.class, orphanRemoval = true) public List<AddressGeoBO> getAdrGeos() { return adrGeos; } @Convert(converter = JsonMapConverter4jpa.class) @JdbcTypeCode(SqlTypes.JSON) @Json public Map<String, Object> getPrsAttributes() { return this.prsAttributes; } @Convert(converter = Gender.Converter4jpa.class) @Json(converter = Gender.Converter4json.class) public Gender getPrsGender() { return this.prsGender; } @Convert(converter = JsonSetUuidConverter4jpa.class) @JdbcTypeCode(SqlTypes.JSON) @Json public Set<UUID> getTags() { return tags; } }
AddressGeoBO 关联实体类
@Entity @Access(AccessType.PROPERTY) @Table(name = AddressGeoBO.TABLENAME) public class AddressGeoBO { final public static String TABLENAME = "addressgeo"; private UUID id = UUID.randomUUID(); private PersonBO personBO = null; @JoinColumn(name = "personId", referencedColumnName = "id") @ManyToOne(targetEntity = PersonBO.class) public PersonBO getPersonBO() { return this.personBO; } }
PersonBO 子类 IdentityBO
@Entity @Access(AccessType.PROPERTY) @Table(name = "identity") @DiscriminatorValue(value = "I") public class IdentityBO extends PersonBO { ... }
完整启动堆栈信息
2023-09-24 15:40:20,279 ERROR [io.qua.dep.dev.IsolatedDevModeMain] (main) Failed to start quarkus: io.quarkus.dev.appstate.ApplicationStartException: org.hibernate.type.descriptor.java.spi.JdbcTypeRecommendationException: Could not determine recommended JdbcType for `com.djarjo.person.PersonBO` at io.quarkus.dev.appstate.ApplicationStateNotification.waitForApplicationStart(ApplicationStateNotification.java:58) at io.quarkus.runner.bootstrap.StartupActionImpl.runMainClass(StartupActionImpl.java:123) at io.quarkus.deployment.dev.IsolatedDevModeMain.firstStart(IsolatedDevModeMain.java:113) at io.quarkus.deployment.dev.IsolatedDevModeMain.accept(IsolatedDevModeMain.java:423) at io.quarkus.deployment.dev.IsolatedDevModeMain.accept(IsolatedDevModeMain.java:55) at io.quarkus.bootstrap.app.CuratedApplication.runInCl(CuratedApplication.java:138) at io.quarkus.bootstrap.app.CuratedApplication.runInAugmentClassLoader(CuratedApplication.java:93) at io.quarkus.deployment.dev.DevModeMain.start(DevModeMain.java:131) at io.quarkus.deployment.dev.DevModeMain.main(DevModeMain.java:62) Caused by: org.hibernate.type.descriptor.java.spi.JdbcTypeRecommendationException: Could not determine recommended JdbcType for `com.djarjo.person.PersonBO` at org.hibernate.type.descriptor.java.spi.UnknownBasicJavaType.getRecommendedJdbcType(UnknownBasicJavaType.java:37) at org.hibernate.boot.model.process.internal.InferredBasicValueResolver.from(InferredBasicValueResolver.java:222) at org.hibernate.mapping.BasicValue.buildResolution(BasicValue.java:480) at org.hibernate.mapping.BasicValue.resolve(BasicValue.java:310) at org.hibernate.mapping.BasicValue.resolve(BasicValue.java:300) at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.lambda$processValueResolvers$4(InFlightMetadataCollectorImpl.java:1842) at java.base/java.util.ArrayList.removeIf(ArrayList.java:1682) at java.base/java.util.ArrayList.removeIf(ArrayList.java:1660) at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processValueResolvers(InFlightMetadataCollectorImpl.java:1841) at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1827) at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:328) at io.quarkus.hibernate.orm.runtime.boot.FastBootMetadataBuilder.build(FastBootMetadataBuilder.java:406) at io.quarkus.hibernate.orm.runtime.PersistenceUnitsHolder.createMetadata(PersistenceUnitsHolder.java:101) at io.quarkus.hibernate.orm.runtime.PersistenceUnitsHolder.constructMetadataAdvance(PersistenceUnitsHolder.java:73) at io.quarkus.hibernate.orm.runtime.PersistenceUnitsHolder.initializeJpa(PersistenceUnitsHolder.java:40) at io.quarkus.hibernate.orm.runtime.HibernateOrmRecorder$1.created(HibernateOrmRecorder.java:78) at io.quarkus.arc.runtime.ArcRecorder.initBeanContainer(ArcRecorder.java:79) at io.quarkus.deployment.steps.ArcProcessor$generateResources844392269.deploy_0(Unknown Source) at io.quarkus.deployment.steps.ArcProcessor$generateResources844392269.deploy(Unknown Source) at io.quarkus.runner.ApplicationImpl.<clinit>(Unknown Source) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499) at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480) at io.quarkus.runtime.Quarkus.run(Quarkus.java:70) at io.quarkus.runtime.Quarkus.run(Quarkus.java:44) at io.quarkus.runtime.Quarkus.run(Quarkus.java:124) at io.quarkus.runner.GeneratedMain.main(Unknown Source) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:104) at java.base/java.lang.Thread.run(Thread.java:833)
问题分析与解决
1. 定位报错具体位置
- 启用Hibernate DEBUG日志,在
application.properties中添加:
启动后,DEBUG日志会输出Hibernate处理每个属性的过程,可从中定位未正确映射的属性。quarkus.log.category."org.hibernate.boot.model.process.internal".level=DEBUG - 逐步注释关联实体或复杂属性,逐个排查缩小范围。
2. @DiscriminatorValue与异常的关联
该注解要求与启动异常无关。Hibernate对JOINED继承策略的处理更严格,要求父类显式指定@DiscriminatorValue,而EclipseLink允许隐式默认值,这只是JPA实现间的差异,不会导致JdbcType推荐异常。
3. 核心问题排查
从堆栈看,Hibernate将PersonBO识别为基本类型而非实体类型,根源可能是:
- 某实体中存在
PersonBO类型属性,但未添加@ManyToOne/@OneToOne等关联注解,导致Hibernate尝试将其作为基本类型映射。 - 检查所有使用
PersonBO作为属性的类,确保关联注解配置正确。 - 排查自定义Converter是否错误处理了
PersonBO类型,混淆了Hibernate的类型映射逻辑。
内容的提问来源于stack exchange,提问作者Jemolah
相关产品推荐
相关产品推荐

