跳到主要内容

多对多

本文通过介绍如何使用@org.babyfish.jimmer.sql.ManyToMany注解可以声明多对多关联属性

多对多可支持双向关联,对于双向关联而言,其中一方必须主动方,另外一方为从动方。

  • 主动方(必须):真正的数据库和关联属性之间映射,实现单向多对多关联。

  • 从动方(可选):如果已经存在一个单向关联,可以为此配置从动方,作为主动方的镜像,形成双向关联。

我们假设存在书籍Book和作者Author两种实体类型,并在它们之间建立双向多对多关联。

信息

和JPA/Hibernate不同,主动方和从动方可以随意抉择,二者都可以用于保存关联。

本文例子抉择如下:

  • 主动方(必须):Book.authors

  • 从动方(可选):Author.books

主动方

Book.java
@Entity
public interface Book {

@ManyToMany
List<Author> authors();

...省略其他代码...
}

多对多关联必须基于中间表实现,即便上述代码中并未使用@JoinTable注解,其实也等同于使用了该注解。

如果并没有为@JoinTable指定任何属性,甚至连@JoinTable本身也被忽略(就像前面的代码一样),Jimmer会根据命名策略推导authors属性对应的列名。

如果默认的命名策略未被用户覆盖,推导出的中间表信息为:

  • 中间表表名: BOOK_AUTHOR_MAPPING
  • 中间表指向当前实体的外键的列名: BOOK_ID
  • 中间表指向关联实体的外键的列名: AUTHOR_ID

所以,之前的代码和这里的代码等价:

Book.java
@Entity
public interface Book {

@ManyToMany
@JoinTable(
name = "BOOK_AUTHOR_MAPPING",
joinColumnName = "BOOK_ID",
inverseJoinColumnName = "AUTHOR_ID"
)
List<Author> authors();

...省略其他代码...
}

中间表BOOK_AUTHOR_MAPPING定义如下

create table BOOK_AUTHOR_MAPPING(
BOOK_ID bigint not null,
AUTHOR_ID bigint not null
);

alter table AUTHOR_MAPPING
add constraint PK_AUTHOR_MAPPING
primary(BOOK_ID, AUTHOR_ID);

// 如果指向当前对象的外键是真的,建立外键约束
alter table BOOK_AUTHOR_MAPPING
add constraint FK_BOOK_AUTHOR_MAPPING__BOOK
foreign key(BOOK_ID)
references BOOK(ID);

// 如果指向关联对象的外键是真的,建立外键约束
alter table BOOK_AUTHOR_MAPPING
add constraint FK_BOOK_AUTHOR_MAPPING__AUTHOR
foreign key(AUTHOR_ID)
references AUTHOR(ID);
  • 中间表的只有两个外键,而且都非null。中间表靠插入数据和删除数据维护关联,本身从不存储null数据

  • 中间表没有对应的ORM实体,无需独立主键,两个外键联合作为主键即可

警告

注意

@ManyToMany关联属性必须非null,如果某种对象并要求抓取其多对多关联属性,那么对于没有对应关联对象的当前对象而言,该属性的值是一个长度为0的集合,而非null

从动方

从动方的配置非常简单,指定Author.books属性是Book.authors属性的镜像即可。

在下面的代码中

  • 左侧:上一节中讨论过的主动方

  • 右侧:本节要介绍从动方关联Author.books,它是Book.authors的镜像

这里,@ManyToMany(mappedBy = "store"),指当前属性BookStore.booksBook.store的镜像。 这里,@ManyToMany(mappedBy = "authors"),指当前属性Author.booksBook.authors的镜像。

Book.java
@Entity
public interface Book {

@ManyToMany
@JoinTable(
name = "BOOK_AUTHOR_MAPPING",
joinColumnName = "BOOK_ID",
inverseJoinColumnName = "AUTHOR_ID"
)
List<Author> authors();

...省略其他代码...
}

mirror

Author.java
@Entity
public interface Author {

// `mappedBy`表示`Author.books`
// 是`Book.authors`的镜像
@ManyToMany(mappedBy = "authors")
List<Book> books();

...省略其他代码...
}
警告

注意

  • 一旦指定@ManyToManymappedBy属性,就不得使用@JoinTable

  • 和主动方的@ManyToMany关联属性一样,从动方的@ManyToMany关联必须非null。原因相同

如果想为中间表加入业务字段

对于本文所讨论的多对多关联而言,除了两个外键外,中间表不得定义其他业务字段。

如果想在多对多关联中间表中定义更多的其他业务字段,请参见ManyToManyView