在@ManyToMany关系中,是否应该在两个方向上都指定@JoinTable?

19
我有一个实体“Course”和一个实体“User”。课程和用户之间存在着多对多的关系,因为一个课程可以有多个用户,而一个用户可以在多个课程中注册。在这两个实体中,我在特定字段上放置了@ManyToMany注解,也就是在“Course”中我有:
@ManyToMany
private List<RegisteredUser> members;

User中,我有以下内容:
@ManyToMany
private List<Course> coursesTaken;

现在,我知道这种多对多关系通常通过第三个表来表示。我还知道有一个注解叫做 @JoinTable,它允许我们这样做。我不确定的是我是否应该在两个不同的实体的两个字段上都添加这个注解 @JoinTable。顺便说一下,如果我需要同时添加,那么名称需要匹配对吗?

1
希望这对您有所帮助:https://github.com/iuliana/many-to-many。不客气。 :) - Iuliana Cosmina
4个回答

22

这实际上是个好问题,理解“拥有”实体的概念可以帮助你,因为两边都不需要@JoinTable注释。如果你想要防止双方都有join tables,一个好主意是在一边使用mappedBy=元素。 @JoinTable注释用于指定表名或映射关联的列。

首先看看@JoinTableJavadoc:

指定关联的映射,适用于关联的所有方。

是否有join table@ManyToMany注释中的mappedBy="name"元素控制。 @ManyToMany注释的Javadoc for mappedBy说

拥有关系的字段,除非关系是单向的,否则必须提供。

对于Hibernate(5.0.9.Final)中你的(双向)示例,如果只有两个@ManyToMany注释而没有mappedBy=元素,则默认将有两个Entity表和两个Join Tables

Hibernate: create table Course (id bigint not null, primary key (id))
Hibernate: create table Course_Member (Course_id bigint not null, members_id bigint not null, primary key (Course_id, members_id))
Hibernate: create table Member (id bigint not null, primary key (id))
Hibernate: create table Member_Course (Member_id bigint not null, courses_id bigint not null, primary key (Member_id, courses_id))

虽然这句话表示每个实体“拥有”自己的ManyToMany关系,但在典型的用例中,额外的join table是多余的。不过,如果我决定让Member实体“拥有”这个关系,那么我会在 Course实体中添加mappedBy=元素来指定它不拥有这个关系:

@ManyToMany(mappedBy="courses")
Set<Member> members;

@JoinTable(name="Member_Course")添加到Member实体中不会改变任何内容:它只是将表格命名为本来就应该命名的名称。

由于Course实体不再拥有其ManyToMany关系,所以额外的JoinTable将不会被创建:

Hibernate: create table Course (id bigint not null, primary key (id))
Hibernate: create table Member (id bigint not null, primary key (id))
Hibernate: create table Member_Course (members_id bigint not null, courses_id bigint not null, primary key (members_id, courses_id))
这对于开发人员很重要,因为他或她必须了解除非将关系添加到所拥有的实体(在本例中为Member实体),否则不会保存关系。然而,由于这是双向关系,开发人员应该同时向Member.courses添加一个Course和一个MemberCourse.members

因此,如果您有一个双向ManyToMany关系,这意味着涉及到的两个实体都有ManyToMany,那么您应该在其中一个实体上添加mappedBy="name",以避免出现冗余的join table。由于它是双向的,我认为哪一方成为owning实体并不重要。始终启用SQL日志并查看数据库中发生的情况总是一个好主意:

参考文献:

单向关联和双向关联之间有什么区别?.

双向关联中的关系所有者是什么意思?.

ORM映射中的“拥有方”是什么?.

防止toString()中无限递归的最有效方法是什么?.


14

实际上,你可以在两侧都使用@JoinTable,而且通常这样做非常合理!我是通过寻找解决方案长达数周之后得出这个结论的。

尽管整个互联网、博客和文章都讲述了不同的故事——而且 JPA 的 Javadoc 很容易被误解(或错误)的方式。在专业人士的书籍中看到了这个未经注释的例子后,我尝试了一下,结果发现它是可行的。

如何做到:

歌手-乐器-关联: 歌手方面

@ManyToMany 
@JoinTable(name = "singer_instrument", joinColumns =
@JoinColumn(name = "SINGER_ID"), inverseJoinColumns = @JoinColumn(name = "INSTRUMENT_ID")) 
public Set<Instrument> instruments;

在另一侧完全相同! 仪器侧:

@ManyToMany
@JoinTable(name = "singer_instrument",
joinColumns = @JoinColumn(name = "INSTRUMENT_ID"),
inverseJoinColumns = @JoinColumn(name = "SINGER_ID"))
public Set<Singer> singers;

因此,如果您使用相同的名称访问相同的联接表“singer_instrument”,它将正常工作。 但是,如果您访问一个联接表“singer_instrument”和一个联接表“instrument-singer”,则会在数据库中产生两个不同的联接表。

这很有道理,因为从数据库的角度来看,多对多关系没有所有方。所谓拥有方是指拥有关系的外键的一方。但是,无论是“歌手”表还是“乐器”表都没有外键相互引用。外键位于它们之间必要的联接表中。

@JoinTable在关系的两侧的优点: 假设一个歌手开始学习新乐器:您可以将该乐器添加到歌手(反之亦然,因为它是双向的)并更新/合并歌手。该更新将仅更新歌手和联接表。它不会触及乐器表

现在另一种情况——吉他课已经结束,因此您想删除吉他与前课程参与者/歌手之间的联系:从歌手中删除乐器“吉他”(反之亦然!),然后更新/合并乐器。该更新将仅更新乐器和联接表。它不会触及歌手表

如果只在一侧使用@JoinTable,则始终必须更新/保存/删除此侧,以安全地处理联接表中的条目(歌手和乐器之间的关系)。在这种情况下,您将不得不更新每个结束吉他课程的歌手。这不能正确地反映关系类型,并且可能导致性能问题数据事务期间的冲突


虽然这是可能的,但不应该像这样使用@JoinTable,请参考“11.1.27 JoinTable注释”中的文档
JoinTable注释在关联的拥有方上指定。
- Balu

1

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接