This tutorial will walk you through the steps of bidirectional mapping a JPA and Hibernate Many to Many relationship without Joined Entity with @ManyToMany in Spring Boot, Spring Data JPA, Lombok, and MySQL
What you need
JDK 8+ or OpenJDK 8+
Maven 3+
MySQL Server 5+
Your favorite IDE
Init project structure
You can create and init a new Spring Boot project by using Spring Initializr or your IDE
Following is the final project structure with all the files we would create
├── src
│ └── main
│ ├── java
│ │ └── com
│ │ └── hellokoding
│ │ ├── jpa
│ │ ├── book
│ │ │ ├── Book.java
│ │ │ ├── BookRepository.java
│ │ │ ├── Publisher.java
│ │ │ └── PublisherRepository.java
│ │ └── JpaApplication.java
│ └── resources
│ └── application.properties
└── pom.xml
The Many To Many relationship mapping will be implemented in Book.java and Publisher.java
Project dependencies
We will use the following dependencies
spring-boot-starter-data-jpa
provides Hibernate and autoconfigure Spring DataSourcemysql-connector-java
provides MySQL Java Clientlombok
for generating boilerplate-code
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
The Many-To-Many Relationship
Many-to-many relationship refers to the relationship between two tables A and B in which one row of A may be linked with many rows of B, and vice versa, one row of B may be linked to many rows of A
In this example, the book
and publisher
tables have a many-to-many relationship. One book may be published by many publishers and one publisher may publish many books
book_publisher
is a join table of book
and publisher
.
book_publisher.book_id
is a foreign key references to book.id
, book_publisher.publisher_id
is a foreign key references to publisher.id
.
book_id
and publisher_id
is also a composite primary key of book_publisher
.
Try this example if the join table has extra columns beside two foreign keys JPA/Hibernate Many To Many Extra Columns Example of Bidirectional Relationship Mapping
Define JPA and Hibernate Entities
JPA Entity is defined with @Entity
annotation, represent a table in your database
[Book.java]
package com.hellokoding.jpa.book;
import lombok.*;
import javax.persistence.*;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Data
@EqualsAndHashCode(exclude = "publishers")
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(name = "name")
private String name;
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "book_publisher",
joinColumns = @JoinColumn(name = "book_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "publisher_id", referencedColumnName = "id"))
private Set<Publisher> publishers;
public Book(String name, Publisher... publishers) {
this.name = name;
this.publishers = Stream.of(publishers).collect(Collectors.toSet());
this.publishers.forEach(x -> x.getBooks().add(this));
}
}
[Publisher.java]
package com.hellokoding.jpa.book;
import lombok.*;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Data
@Entity
public class Publisher {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
@ManyToMany(mappedBy = "publishers")
private Set<Book> books = new HashSet<>();
public Publisher(String name) {
this.name = name;
}
}
@Id
declares the entity identifier.
@Column
maps the entity's field with the table's column. If @Column
is omitted, the field name of the entity will be used as a column name by default.
@ManyToMany
defines a many-to-many relationship between 2 entities. mappedBy
indicates the entity is the inverse of the relationship.
@JoinTable
defines the join table of 2 associated entities. If the JoinTable
annotation is missing, the default values of the annotation elements apply. The name of the join table is assumed to be the table names of the associated primary tables concatenated together (owning side first) using an underscore
Spring Data JPA Repository
Spring Data JPA contains some built-in Repository
abstracting common functions based on EntityManager
to work with database such as findAll
, findById
, save
, delete
, deleteById
. All we need for this example is extends JpaRepository
[BookRepository.java]
package com.hellokoding.jpa.book;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookRepository extends JpaRepository<Book, Integer>{
}
[PublisherRepository.java]
package com.hellokoding.jpa.book;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PublisherRepository extends JpaRepository<Publisher, Integer>{
}
Application Properties
Configure the Spring Datasource JDBC URL, user name, and password of your local MySQL server in application.properties
[application.properties]
spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false
spring.datasource.username=root
spring.datasource.password=hellokoding
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=create
spring.jpa.database-platform=org.hibernate.dialect.MySQL57Dialect
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
Create the test database in your local MySQL server if not exists
We don't have to create table schemas, the ddl-auto=create config allows JPA and
Hibernate does that based on the entity-relationship mappings. In practice, consider to use ddl-auto=none (default) and use a migration tool such as Flyway for better database management
spring.jpa.show-sql=true for showing generated SQL queries in the application logs, consider to disable it on production environment
Creating data with JPA and Hibernate
Thanks to CascadeType.ALL
, the associated entity Publisher
will be saved at the same time with Book
without the need of calling its save function explicitly.
[JpaApplication.java]
package com.hellokoding.jpa;
import com.hellokoding.jpa.book.Book;
import com.hellokoding.jpa.book.BookRepository;
import com.hellokoding.jpa.book.Publisher;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@Slf4j
@SpringBootApplication
public class JpaApplication implements CommandLineRunner {
@Autowired
private BookRepository bookRepository;
public static void main(String[] args) {
SpringApplication.run(JpaApplication.class, args);
}
@Override
public void run(String... args) {
// Create a couple of Book and Publisher
bookRepository.save(new Book("Book 1", new Publisher("Publisher A"), new Publisher("Publisher B")));
}
}
Run and test
Type the below command at the project root directory
./mvnw clean spring-boot:run
Access to your local MySQL Server to query the schema and data created by JPA/Hibernate based on your mapping
Conclusion
In this tutorial, we learned to map a JPA and Hibernate Many to Many relationship without Joined Entity in Spring Boot and MySQL. You can find the source code on Github