Many To Many
This chapter introduces how to use the @org.babyfish.jimmer.sql.ManyToMany
annotation to declare many-to-many association properties.
Many-to-many supports bidirectional association. For bidirectional associations, one side must be the owning side, and the other side must be the inverse side.
-
Owning side (required): The actual mapping between the database and association properties to implement unidirectional many-to-many association.
-
Inverse side (optional): If a unidirectional association already exists, the inverse side can be configured as a mirror of the owning side to form a bidirectional association.
Let's assume there are two entity types Book and Author, and establish a bidirectional many-to-many association between them.
Different from JPA/Hibernate, the owning side and inverse side can be chosen arbitrarily. Both can be used to persist associations.
The example in this article chooses:
-
Owning side (required):
Book.authors
-
Inverse side (optional):
Author.books
Owning Side
- Java
- Kotlin
@Entity
public interface Book {
@ManyToMany
List<Author> authors();
...Omit other code...
}
@Entity
interface Book {
@ManyToMany
val authors: List<Author>
...Omit other code...
}
Many-to-many associations must be implemented based on a join table, even if the @JoinTable
annotation is not used in the above code, it is actually equivalent to using the annotation.
If no properties are specified for @JoinTable
, and even @JoinTable
itself is omitted (like the previous code), Jimmer will deduce the column names corresponding to the authors
property based on the naming strategy.
If the default naming strategy is not overridden by the user, the deduced join table information is:
- Join table name: BOOK_AUTHOR_MAPPING
- Join table foreign key column pointing to current entity: BOOK_ID
- Join table foreign key column pointing to associated entity: AUTHOR_ID
So the previous code is equivalent to:
- Java
- Kotlin
@Entity
public interface Book {
@ManyToMany
@JoinTable(
name = "BOOK_AUTHOR_MAPPING",
joinColumnName = "BOOK_ID",
inverseJoinColumnName = "AUTHOR_ID"
)
List<Author> authors();
...Omit other code...
}
@Entity
interface Book {
@ManyToMany
@JoinTable(
name = "BOOK_AUTHOR_MAPPING",
joinColumnName = "BOOK_ID",
inverseJoinColumnName = "AUTHOR_ID"
)
val authors: List<Author>
...Omit other code...
}
The join table BOOK_AUTHOR_MAPPING
is defined as:
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);
// If foreign key pointing to current entity is real,
// create foreign key constraint
alter table BOOK_AUTHOR_MAPPING
add constraint FK_BOOK_AUTHOR_MAPPING__BOOK
foreign key(BOOK_ID)
references BOOK(ID);
// If foreign key pointing to associated entity is real,
// create foreign key constraint
alter table BOOK_AUTHOR_MAPPING
add constraint FK_BOOK_AUTHOR_MAPPING__AUTHOR
foreign key(AUTHOR_ID)
references AUTHOR(ID);
-
The join table has only two foreign keys, both non-null. The join table maintains the association by inserting and deleting data, never storing null data itself.
-
The join table has no corresponding ORM entity and does not need an independent primary key. The two foreign keys combined can serve as the primary key.
Note
The @ManyToMany
association property must be non-null. If an object requires fetching its many-to-many association properties, then for the current object that has no corresponding associated object, the value of this property is a collection of length 0 rather than null.
Inverse Side
The configuration of the inverse side is very simple, just specify that Author.books
is the mirror of Book.authors
.
In the following code:
-
Left: The owning side discussed in the previous section
-
Right: The inverse side association
Author.books
introduced in this section, which is the mirror ofBook.authors
Here, @ManyToMany(mappedBy = "store")
indicates that the current property Authors.books
is the mirror of Book.authors
.
- Java
- Kotlin
@Entity
public interface Book {
@ManyToMany
@JoinTable(
name = "BOOK_AUTHOR_MAPPING",
joinColumnName = "BOOK_ID",
inverseJoinColumnName = "AUTHOR_ID"
)
List<Author> authors();
...Omit other code...
}
@Entity
interface Book {
@ManyToMany
@JoinTable(
name = "BOOK_AUTHOR_MAPPING",
joinColumnName = "BOOK_ID",
inverseJoinColumnName = "AUTHOR_ID"
)
val authors: List<Author>
...Omit other code...
}
- Java
- Kotlin
@Entity
public interface Author {
// `mappedBy` indicates `Author.books`
// is the mirror of `Book.authors`
@ManyToMany(mappedBy = "authors")
List<Book> books();
...Omit other code...
}
@Entity
interface Author {
// `mappedBy` indicates `Author.books`
// is the mirror of `Book.authors`
@ManyToMany(mappedBy = "authors")
val books: List<Book>
...Omit other code...
}
Note:
-
Once the
mappedBy
property of@ManyToMany
is specified,@JoinTable
must not be used. -
Like the
@ManyToMany
association property on the owning side, the@ManyToMany
association on the inverse side must also be non-null for the same reason.
If business fields need to be added to the join table
For the many-to-many association discussed in this article, no business fields other than the two foreign keys can be defined on the join table.
If you want to define more other business fields in the join table of the many-to-many association, please refer to ManyToManyView.