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

SpringBoot3.1.2升级后单向@OneToOne映射失效,报PropertyAccessException

SpringBoot 3.1.2(Hibernate 6.2.6)升级后@OneToOne继承映射类型推断异常

问题描述

将API从SpringBoot 3.0.6(Hibernate 6.1.7.Final)升级至SpringBoot 3.1.2(Hibernate 6.2.6.Final)后,调用RecordDao时原有单向@OneToOne映射出现类型转换异常,报错如下:

Caused by: org.hibernate.PropertyAccessException: Could not set value of type [com.hidden.CurrentRole] : 'com.hidden.Record.futureRole' (setter)

代码结构说明

采用@Inheritance(strategy = InheritanceType.JOINED)继承策略的父实体Resource,两个子实体FutureRole和CurrentRole继承自它,Record实体通过@OneToOne映射同时关联这两个子实体。

父实体代码

@Entity
@Table(name = "RESOURCE")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "RESOURCE_TYPE_CD", discriminatorType = DiscriminatorType.INTEGER, columnDefinition = "INT")
abstract class Resource(

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "RESOURCE_ID_SEQ")
    @SequenceGenerator(name = "RESOURCE_ID_SEQ", sequenceName = "RESOURCE_ID_SEQ", allocationSize = 1)
    override var id: Long? = null,

    @Column(name = "RESOURCE_TYPE_CD", insertable = false, updatable = false)
    open var resourceType: ResourceType

): MutableAuditableBaseJpa<Long>()

子实体代码

@Entity
@Table(name = "RESOURCE_FUTURE_ROLE")
@DiscriminatorValue("1")
class FutureRole(
    ...
): Resource(null, ResourceType.FUTURE_ROLE)
@Entity
@Table(name = "RESOURCE_CURRENT_ROLE")
@DiscriminatorValue("2")
class CurrentRole(
    ...
): Resource(null, ResourceType.CURRENT_ROLE)

Record实体及Dao代码

@Entity
@Table(name = "RECORD")
open class Record(

    @Column(name = "RESOURCE_ID") override var resourceId: Long,
    ...
): MutableAuditableBaseJpa<Long>() {
    @OneToOne
    @JoinColumn(name = "RESOURCE_ID", updatable = false, insertable = false)
    @NotFound(action = NotFoundAction.IGNORE)
    open var futureRole: FutureRole? = null

    @OneToOne
    @JoinColumn(name = "RESOURCE_ID", updatable = false, insertable = false)
    @NotFound(action = NotFoundAction.IGNORE)
    open var employee: CurrentRole? = null
}
interface RecordDao: JpaRepository<Record, Long> {
    fun findByResourceId(resourceId: Long): Record?
}

已尝试的无效方案

曾将@OneToOne关联改为@ManyToOne并使用@JoinTable,修改后的映射代码如下:

@ManyToOne
@JoinTable(name = "RESOURCE_FUTURE_ROLE", joinColumns = [JoinColumn(name = "ID", insertable=false, updatable=false)], inverseJoinColumns = [JoinColumn(name = "ID", insertable=false, updatable=false)])
@NotFound(action = NotFoundAction.IGNORE)
open var futureRole: FutureRole? = null

@ManyToOne
@JoinTable(name = "RESOURCE_CURRENT_ROLE", joinColumns = [JoinColumn(name = "ID", insertable=false, updatable=false)], inverseJoinColumns = [JoinColumn(name = "ID", insertable=false, updatable=false)])
@NotFound(action = NotFoundAction.IGNORE)
open var employee: CurrentRole? = null

修改后虽能返回资源,但所有数据均不正确。


解决方案

核心原因

Hibernate 6.2.x版本对JOINED继承策略下的多字段关联同一外键场景,类型推断逻辑做了调整。原代码中Record的两个关联字段共用RESOURCE_ID外键,但Hibernate无法自动通过鉴别器字段区分目标子实体类型,导致实例化时出现类型不匹配。

可行修复方案

方案1:添加@Where注解明确过滤条件

在每个@OneToOne关联上添加@Where注解,通过鉴别器字段指定对应子实体的类型,让Hibernate查询时精准过滤:

@OneToOne
@JoinColumn(name = "RESOURCE_ID", updatable = false, insertable = false)
@NotFound(action = NotFoundAction.IGNORE)
@Where(clause = "RESOURCE_TYPE_CD = 1")
open var futureRole: FutureRole? = null

@OneToOne
@JoinColumn(name = "RESOURCE_ID", updatable = false, insertable = false)
@NotFound(action = NotFoundAction.IGNORE)
@Where(clause = "RESOURCE_TYPE_CD = 2")
open var employee: CurrentRole? = null

注意:clause中的值必须和子实体@DiscriminatorValue完全一致。

方案2:Dao层自定义JPQL查询

如果@Where注解不生效,可在Dao层通过JPQL明确关联条件,强制指定子实体类型:

interface RecordDao: JpaRepository<Record, Long> {
    @Query("""
        SELECT r FROM Record r 
        LEFT JOIN FETCH r.futureRole fr 
        LEFT JOIN FETCH r.employee e 
        WHERE r.resourceId = :resourceId 
          AND fr.resourceType = com.hidden.ResourceType.FUTURE_ROLE 
          AND e.resourceType = com.hidden.ResourceType.CURRENT_ROLE
    """)
    fun findByResourceId(resourceId: Long): Record?
}

方案3:调整实体映射结构(推荐)

将Record中的两个子实体关联改为父实体类型,通过类型转换获取子实体,符合JPA继承映射规范:

@Entity
@Table(name = "RECORD")
open class Record(

    @Column(name = "RESOURCE_ID") override var resourceId: Long,
    ...
): MutableAuditableBaseJpa<Long>() {
    @OneToOne
    @JoinColumn(name = "RESOURCE_ID", updatable = false, insertable = false)
    @NotFound(action = NotFoundAction.IGNORE)
    open var resource: Resource? = null

    // 提供类型转换的getter方法
    fun getFutureRole(): FutureRole? = resource as? FutureRole
    fun getCurrentRole(): CurrentRole? = resource as? CurrentRole
}

这种方式避免了多字段关联同一外键的歧义,同时简化了映射逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 16:35:09