Skip to main content

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.

info

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

Book.java
@Entity
public interface Book {

@ManyToMany
List<Author> authors();

...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:

Book.java
@Entity
public interface Book {

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

...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.

caution

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 of Book.authors

Here, @ManyToMany(mappedBy = "store") indicates that the current property BookStore.books is the mirror of Book.store.

Book.java
@Entity
public interface Book {

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

...Omit other code...
}

mirror

Author.java
@Entity
public interface Author {

// `mappedBy` indicates `Author.books`
// is the mirror of `Book.authors`
@ManyToMany(mappedBy = "authors")
List<Book> books();

...Omit other code...
}
caution

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.