@ManyToMany
private List<RegisteredUser> members;
在
User
中,我有以下内容:@ManyToMany
private List<Course> coursesTaken;
现在,我知道这种多对多关系通常通过第三个表来表示。我还知道有一个注解叫做
@JoinTable
,它允许我们这样做。我不确定的是我是否应该在两个不同的实体的两个字段上都添加这个注解 @JoinTable
。顺便说一下,如果我需要同时添加,那么名称需要匹配对吗?@ManyToMany
private List<RegisteredUser> members;
User
中,我有以下内容:@ManyToMany
private List<Course> coursesTaken;
@JoinTable
,它允许我们这样做。我不确定的是我是否应该在两个不同的实体的两个字段上都添加这个注解 @JoinTable
。顺便说一下,如果我需要同时添加,那么名称需要匹配对吗?这实际上是个好问题,理解“拥有”实体的概念可以帮助你,因为两边都不需要@JoinTable
注释。如果你想要防止双方都有join tables
,一个好主意是在一边使用mappedBy=
元素。 @JoinTable
注释用于指定表名或映射关联的列。
首先看看@JoinTable
的Javadoc:
指定关联的映射,适用于关联的所有方。
是否有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
和一个Member
到Course.members
。
因此,如果您有一个双向ManyToMany
关系,这意味着涉及到的两个实体都有ManyToMany
,那么您应该在其中一个实体上添加mappedBy="name"
,以避免出现冗余的join table
。由于它是双向的,我认为哪一方成为owning
实体并不重要。始终启用SQL日志并查看数据库中发生的情况总是一个好主意:
参考文献:
实际上,你可以在两侧都使用@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,则始终必须更新/保存/删除此侧,以安全地处理联接表中的条目(歌手和乐器之间的关系)。在这种情况下,您将不得不更新每个结束吉他课程的歌手。这不能正确地反映关系类型,并且可能导致性能问题和数据事务期间的冲突。
如果要在现有模式(不是由Hibernate创建的)上使用@ManyToMany
,则必须在两个类上使用@JoinTable注释来指定表格以及哪些列映射到适当类中的Java成员变量。我认为这个例子可以帮助您确定应该传递给注释的属性: