【问题标题】:How do I join tables on non-primary key columns?如何在非主键列上连接表?
【发布时间】:2025-12-03 01:35:01
【问题描述】:

我在 ORM 类层次结构中的对象上的连接表存在问题,其中连接列不是基类的主键,因为数据库结构落后。 以下是表格设计的示例:

CREATE TABLE "SCH"."FOO"
(
        "OWNERID"       NUMBER(10,0) NOT NULL ENABLE,
        "FOOID"         NUMBER(10,0) NOT NULL ENABLE,
        CONSTRAINT "FOO_PK" PRIMARY KEY ("OWNERID", "FOOID")
        CONSTRAINT "FOO_FK1" FOREIGN KEY ("OWNERID") REFERENCES "SCH"."OWNERS" ("OWNERID") ENABLE
)

CREATE TABLE "SCH"."BAR"
(
        "BARID"             NUMBER(10,0) NOT NULL ENABLE,
        "FOOID"             NUMBER(10,0)
        CONSTRAINT "BAR_PK" PRIMARY KEY ("BARID")
)

这里是映射(删除了不必要的信息)

@Entity
@IdClass(FooId.class)
@Table(name = "FOO")
public class Foo implements java.io.Serializable
{
    @Id
    @Column(name = "OWNERID")
    private BigInteger ownerId;

    @Id
    @SequenceGenerator(name = "FOO_GENERATOR", sequenceName = "SEQ_FOO")
    @GeneratedValue(generator = "FOO_GENERATOR")
    @Column(name = "FOOID")
    private BigInteger id;

    @OneToMany(fetch = FetchType.LAZY)
    @JoinColumn(name = "FOOID", referencedColumnName = "FOOID")
    @Fetch(value = FetchMode.SUBSELECT)
    @Cascade(value = {CascadeType.ALL})
    private Set<Bar> bar = new LinkedHashSet<Bar>(0);
}


@Entity
@Table(name = "BAR")
public class Bar implements java.io.Serializable
{
    @Id
    @Column(name = "BARID")
    private BigInteger id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "FOOID", referencedColumnName = "FOOID")
    private Foo foo;
}

这会失败并出现异常:

Caused by: org.hibernate.AnnotationException: referencedColumnNames(FOOID) of com.package.Bar.foo referencing com.package.Foo not mapped to a single property
    at org.hibernate.cfg.BinderHelper.createSyntheticPropertyReference(BinderHelper.java:204)
    at org.hibernate.cfg.ToOneFkSecondPass.doSecondPass(ToOneFkSecondPass.java:114)
    at org.hibernate.cfg.Configuration.processEndOfQueue(Configuration.java:1580)
    at org.hibernate.cfg.Configuration.processFkSecondPassInOrder(Configuration.java:1503)
    at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1419)
    at org.hibernate.cfg.Configuration.buildMappings(Configuration.java:1375)

您能帮忙解决一下吗?

【问题讨论】:

    标签: java hibernate orm


    【解决方案1】:

    您不能两次映射双向关联。必须使用mappedBy 属性将单面标记为多面的反面:

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "foo")
    @Fetch(value = FetchMode.SUBSELECT)
    @Cascade(value = {CascadeType.ALL})
    private Set<Bar> bar = new LinkedHashSet<Bar>(0);
    
    ...
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "FOOID", referencedColumnName = "FOOID")
    private Foo foo;
    

    没有理由告诉 Hibernate 两次关联是由连接列 FOOID 映射的。而且这样做实际上是一个错误,因为它定义了两个不同的单向关联,而不是一个双向关联。

    编辑

    以上应该可以工作,但不是由于以下 Hibernate 错误:这是一个 Hibernate 错误。见HHH-4284

    为了规避这个问题,由于FOOID足以保证唯一性,解决方案是从所有者ID和@IdClass注释中删除@Id注释。

    【讨论】:

    • 之前尝试过,但不起作用:com.package.Bar.foo 的 referencedColumnNames(FOOID) 引用 com.package.Foo 未映射到单个属性
    • 这是一个休眠错误。见hibernate.onjira.com/browse/HHH-4284。 OTOH,您不能简单地从所有者 ID 中删除 @Id 注释,因为 FOOID 足以确保唯一性?
    • 从所有者 ID 中删除 @Id 注释并删除 @IdClass 是可行的,但与预期的不同,但以某种方式起作用。您能否将其发布为不同的答案,以便我可以接受它作为解决方案?非常感谢
    • 引用的错误已于 2013 年关闭,因为不可重现。
    【解决方案2】:

    你可以这样做......它应该工作 -

    @Entity
    @IdClass(FooId.class)
    @Table(name = "FOO")
    public class Foo implements java.io.Serializable
    {
        @Id
        @Column(name = "OWNERID")
        private BigInteger ownerId;
    
        @Id
        @SequenceGenerator(name = "FOO_GENERATOR", sequenceName = "SEQ_FOO")
        @GeneratedValue(generator = "FOO_GENERATOR")
        @Column(name = "FOOID")
        private BigInteger id;
    
        @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
        @JoinColumn(name = "FOOID",nullable=false)
        @ForeignKey(name = "fk")     
        private Set<Bar> bar = new LinkedHashSet<Bar>(0);
    }
    
    
    @Entity
    @Table(name = "BAR")
    public class Bar implements java.io.Serializable
    {
        @Id
        @Column(name = "BARID")
        private BigInteger id;
    
        @ManyToOne(fetch=FetchType.LAZY)
        @JoinColumn(name = "FOOID", updatable = false, insertable = false, nullable=false)  
         private Foo foo;
    }
    

    【讨论】:

    • 不工作,说:从 com.package.Bar 引用 com.package.Foo 的外键列数错误。应该是 2
    【解决方案3】:

    您在 FOO 表中使用了复合主键。因此,您应该尝试@EmbeddedId 属性,并且您应该需要 BAR 实体中与 FOO 实体连接的两列“OWNER_ID”和“FOO_ID”。

    【讨论】:

    • 我知道,但我无法更改数据库结构 :(