ChatGPT解决这个技术问题 Extra ChatGPT

JPA JoinColumn 与 mappedBy

有什么区别:

@Entity
public class Company {

    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
    @JoinColumn(name = "companyIdRef", referencedColumnName = "companyId")
    private List<Branch> branches;
    ...
}

@Entity
public class Company {

    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY, 
    mappedBy = "companyIdRef")
    private List<Branch> branches;
    ...
}
另请参阅 What is the owning side in an ORM mapping 问题以获得对所涉及问题的非常好的解释。

Ó
Óscar López

注释 @JoinColumn 表示该实体是关系的所有者(即:对应表有一个列,该列具有引用表的外键),而属性 mappedBy 表示这边的实体是关系的逆,所有者驻留在“其他”实体中。这也意味着您可以从使用“mappedBy”(完全双向关系)注释的类中访问另一个表。

特别是,对于问题中的代码,正确的注释应如下所示:

@Entity
public class Company {
    @OneToMany(mappedBy = "company",
               orphanRemoval = true,
               fetch = FetchType.LAZY,
               cascade = CascadeType.ALL)
    private List<Branch> branches;
}

@Entity
public class Branch {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "companyId")
    private Company company;
}

在这两种情况下,Branch 都有带有公司 ID 的字段。
公司表没有具有引用表的外键的列-分支有引用公司..为什么说“相应的表有一个具有引用表的外键的列”?你能解释一下吗?
@MykhayloAdamovych 我用示例代码更新了我的答案。请注意,在 Company 中使用 @JoinColumn 是错误的
@MykhayloAdamovych:不,这实际上不太正确。如果 Branch 没有引用 Company 的属性,但基础表有一个引用 Company 的列,那么您可以使用 @JoinTable 来映射它。这是一种不寻常的情况,因为您通常会映射对象中与其表对应的列,但它可能会发生,而且是完全合法的。
这是不喜欢 ORM 的另一个原因。文档通常过于狡猾,在我的书中,这是在太多魔法领域徘徊。我一直在努力解决这个问题,当一个字一个字地跟随一个 @OneToOne 时,子行在其引用父级的 FKey 列中更新为 null
d
donquih0te

@JoinColumn 可用于关系的双方。 问题是关于在 @OneToMany 方面使用 @JoinColumn(极少数情况)。这里的重点在于物理信息重复(列名)以及未优化的 SQL 查询,会产生一些额外的 UPDATE 语句

根据documentation

由于 多对一(几乎)始终是 JPA 规范中双向关系的所有者方,因此一对多关联由 @OneToMany(mappedBy=...) 注释

@Entity
public class Troop {
    @OneToMany(mappedBy="troop")
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk")
    public Troop getTroop() {
    ...
} 

Troop 通过 troop 属性与 Soldier 具有双向的一对多关系。您不必(不得)在 mappedBy 端定义任何物理映射。

要将双向一对多映射,以 一对多作为拥有方,您必须删除 mappedBy 元素并将多对一 @JoinColumn 设置为 insertable updatable 为假。此解决方案未优化,会产生一些额外的 UPDATE 语句。

@Entity
public class Troop {
    @OneToMany
    @JoinColumn(name="troop_fk") //we need to duplicate the physical information
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk", insertable=false, updatable=false)
    public Troop getTroop() {
    ...
}

我无法弄清楚部队如何在您的第二个片段中成为所有者,士兵仍然是所有者,因为它包含引用部队的外键。 (我正在使用mysql,我检查了你的方法)。
在您的示例中,注释 mappedBy="troop" 指的是哪个字段?
@Fractaliste 注释 mappedBy="troop" 指的是士兵类中的属性部队。在上面的代码中,该属性是不可见的,因为这里 Mykhaylo 省略了它,但您可以通过 getter getTroop() 推断它的存在。检查Óscar López的答案,很清楚,你会明白的。
这个例子是滥用 JPA 2 规范。如果作者的目标是创建双向关系,那么它应该在父端使用 mappedBy,在子端使用 JoinColumn(如果需要)。通过此处介绍的方法,我们得到了 2 个单向关系:OneToMany 和 ManyToOne,它们是独立的,但只是靠运气(更多是由于误用),这两个关系是使用相同的外键定义的
如果您使用的是 JPA 2.x,我在下面的回答会更简洁一些。虽然我建议尝试这两种路由并查看 Hibernate 在生成表时做了什么。如果您正在进行一个新项目,请选择您认为适合您需求的任何一代。如果您使用的是旧数据库并且不想更改结构,请选择与您的架构匹配的那个。
V
Vlad Mihalcea

单向一对多关联

如果您将 @OneToMany 注释与 @JoinColumn 一起使用,则您有一个单向关联,如下图中父 Post 实体和子 PostComment 实体之间的关联:

https://i.stack.imgur.com/ljaHg.png

使用单向一对多关联时,只有父方映射关联。

在此示例中,只有 Post 实体将定义与子 PostComment 实体的 @OneToMany 关联:

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "post_id")
private List<PostComment> comments = new ArrayList<>();

双向一对多关联

如果您将 @OneToManymappedBy 属性集一起使用,则您具有双向关联。在我们的例子中,Post 实体都有一个 PostComment 子实体的集合,并且子 PostComment 实体有一个对父 Post 实体的引用,如下图所示:

https://i.stack.imgur.com/i5YWM.png

PostComment 实体中,post 实体属性映射如下:

@ManyToOne(fetch = FetchType.LAZY)
private Post post;

我们将 fetch 属性显式设置为 FetchType.LAZY 的原因是,默认情况下,所有 @ManyToOne 和 @OneToOne 关联都会被急切地获取,这可能会导致 N+1 查询问题。

Post 实体中,comments 关联映射如下:

@OneToMany(
    mappedBy = "post",
    cascade = CascadeType.ALL,
    orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();

@OneToMany 注释的 mappedBy 属性引用了子 PostComment 实体中的 post 属性,这样,Hibernate 就知道双向关联是由 @ManyToOne 方控制的,它负责管理此表关系所基于的外键列值。

对于双向关联,您还需要有两个实用方法,例如 addChildremoveChild

public void addComment(PostComment comment) {
    comments.add(comment);
    comment.setPost(this);
}

public void removeComment(PostComment comment) {
    comments.remove(comment);
    comment.setPost(null);
}

这两种方法确保双向关联的双方是同步的。如果不同步两端,Hibernate 不保证关联状态更改会传播到数据库。

选择哪一个?

单向 @OneToMany 关联表现不佳,因此您应该避免使用它。

您最好使用效率更高的双向 @OneToMany


S
Soumyaansh

理想情况下,mappedBy 注释应该始终用于双向关系的 Parent 端(Company 类),在这种情况下,它应该在 Company 类中指向 Child 类(Branch 类)的成员变量“company”

注解@JoinColumn 用于指定用于加入实体关联的映射列,此注解可用于任何类(父或子),但理想情况下应仅在一侧使用(在父类或子类中在两者中)在这种情况下,我在双向关系的子端(分支类)中使用了它,指示分支类中的外键。

下面是工作示例:

父类 , 公司

@Entity
public class Company {


    private int companyId;
    private String companyName;
    private List<Branch> branches;

    @Id
    @GeneratedValue
    @Column(name="COMPANY_ID")
    public int getCompanyId() {
        return companyId;
    }

    public void setCompanyId(int companyId) {
        this.companyId = companyId;
    }

    @Column(name="COMPANY_NAME")
    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    @OneToMany(fetch=FetchType.LAZY,cascade=CascadeType.ALL,mappedBy="company")
    public List<Branch> getBranches() {
        return branches;
    }

    public void setBranches(List<Branch> branches) {
        this.branches = branches;
    }


}

子类,分支

@Entity
public class Branch {

    private int branchId;
    private String branchName;
    private Company company;

    @Id
    @GeneratedValue
    @Column(name="BRANCH_ID")
    public int getBranchId() {
        return branchId;
    }

    public void setBranchId(int branchId) {
        this.branchId = branchId;
    }

    @Column(name="BRANCH_NAME")
    public String getBranchName() {
        return branchName;
    }

    public void setBranchName(String branchName) {
        this.branchName = branchName;
    }

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="COMPANY_ID")
    public Company getCompany() {
        return company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }


}

I
IqbalHamid

我不同意 Óscar López 在这里接受的答案。这个答案不准确!

它不是 @JoinColumn,表示该实体是关系的所有者。相反,执行此操作的是 @ManyToOne 注释(在他的示例中)。

@ManyToOne@OneToMany@ManyToMany 等关系注释告诉 JPA/Hibernate创建映射。默认情况下,这是通过单独的 Join Table 完成的。

@JoinColumn

@JoinColumn 的目的是在不存在的情况下创建一个连接列。如果是这样,则此注释可用于命名连接列。

映射依据

MappedBy 参数的目的是指示 JPA:不要创建另一个连接表,因为该关系已由该关系的相反实体映射。



请记住:MappedBy 是关系注释的一个属性,其目的是生成一种机制来关联两个实体,默认情况下它们通过创建连接表来实现。 MappedBy 在一个方向上停止该过程。

不使用 MappedBy 的实体被称为关系的所有者,因为映射的机制是在其类中通过使用针对外键字段的三个映射注释之一来规定的。这不仅指定了映射的性质,还指示了连接表的创建。此外,还可以通过在外键上应用 @JoinColumn 注释来抑制连接表,从而将其保留在所有者实体的表中。

总而言之:@JoinColumn 要么创建一个新的连接列,要么重命名一个现有的;而 MappedBy 参数与另一个(子)类的关系注释协同工作,以便通过连接表或通过在所有者实体的关联表中创建外键列来创建映射。

为了说明 MapppedBy 的工作原理,请考虑以下代码。如果要删除 MappedBy 参数,那么 Hibernate 实际上会创建两个连接表!为什么?因为在多对多关系中存在对称性,并且 Hibernate 没有理由选择一个方向而不是另一个方向。

因此,我们使用 MappedBy 来告诉 Hibernate,我们选择了另一个实体来指示两个实体之间关系的映射。

@Entity
public class Driver {
    @ManyToMany(mappedBy = "drivers")
    private List<Cars> cars;
}

@Entity
public class Cars {
    @ManyToMany
    private List<Drivers> drivers;
}

在所有者类中添加 @JoinColumn(name = "driverID")(见下文),将阻止创建连接表,而是在 Cars 表中创建 driverID 外键列以构造映射:

@Entity
public class Driver {
    @ManyToMany(mappedBy = "drivers")
    private List<Cars> cars;
}

@Entity
public class Cars {
    @ManyToMany
    @JoinColumn(name = "driverID")
    private List<Drivers> drivers;
}

很好的答案,恕我直言,比公认的要好。我只是想知道为什么我仍然有外键列并且没有连接表,即使我从未使用过 @JoinColumn
好的,似乎不需要 @JoinColumn 来避免连接表。用注解声明两边 + 用 mappedBy 声明两边也会引入这种行为。
最好的解释之一,谢谢!
C
Community

我想补充一点,正如 this 答案所暗示的那样,@JoinColumn 并不总是必须与 物理信息位置 相关。即使父表没有指向子表的表数据,您也可以将 @JoinColumn@OneToMany 组合使用。

How to define unidirectional OneToMany relationship in JPA

Unidirectional OneToMany, No Inverse ManyToOne, No Join Table

不过,它似乎只在 JPA 2.x+ 中可用。当您希望子类只包含父类的 ID,而不是完整的参考时,这很有用。


你是对的,JPA2 中引入了对单向 OneToMany 没有连接表的支持
V
Vinjamuri Satya Krishna

让我简单点。无论映射如何,您都可以在任一侧使用 @JoinColumn。

让我们将其分为三种情况。 1)从分公司到公司的单向映射。 2) 从公司到分公司的双向映射。 3) 只有从公司到分公司的单向映射。

因此,任何用例都属于这三个类别。那么让我解释一下如何使用@JoinColumn 和mappedBy。 1)从分公司到公司的单向映射。在 Branch 表中使用 JoinColumn。 2) 从公司到分公司的双向映射。如@Mykhaylo Adamovych 的回答所述,在 Company 表中使用 mappedBy。 3)公司到分公司的单向映射。只需在 Company 表中使用 @JoinColumn。

@Entity
public class Company {

@OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
@JoinColumn(name="courseId")
private List<Branch> branches;
...
}

这表示基于分支表中的外键“courseId”映射,获取所有分支的列表。注意:在这种情况下,您无法从分支机构获取公司,从公司到分支机构仅存在单向映射。


d
donquih0te

JPA 是一个分层的 API,不同的层次都有自己的注解。最高级别是 (1) 描述持久类的实体级别,然后您有 (2) 关系数据库级别,假设实体映射到关系数据库和 (3) java 模型。

级注释:@Entity@Id@OneToOne@OneToMany@ManyToOne@ManyToMany。您可以单独使用这些高级注释在应用程序中引入持久性。但是你必须根据 JPA 所做的假设来创建你的数据库。这些注释指定实体/关系模型。

2 级注释:@Table@Column@JoinColumn、...如果您对 JPA 的默认设置不满意或需要映射到现有的数据库,则会影响从实体/属性到关系数据库表/列的映射数据库。这些注解可以看作是实现注解,它们指定应该如何进行映射。

在我看来,最好尽可能坚持高级注释,然后根据需要引入较低级别的注释。

回答问题:@OneToMany/mappedBy 最好,因为它只使用实体域中的注释。 @oneToMany/@JoinColumn 也可以,但它使用了非严格必要的实现注释。


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅