Handle Many-to-Many relationship with JPA in a Spring Boot application - part 1
Photo by Priscilla Du Preez / Unsplash
Entity Relationship is the way to link the entities in our application. These relationships are translated into database tables in an RDBMS. We usually have three types of relationships:
- One-To-One
- Many-To-One or One-To-Many
- Many-To-Many
Handle Many-To-Many relationship is always tricky, especially when additional information is stored inside the relationship.
In this post, we will see how to handle a Many-to-Many relationship without additional data first and secondly with additional data. We will use Hibernate, the most popular ORM for Java that uses the JPA under the hood to create the class entity with annotation.
The use case
Let's build a movie streaming platform, and we want to develop a feature that allows the user to select his favorite movies. The entity-relationship will look like this:
Prerequisites
To follow this tutorial, you will need a MySQL instance installed on your local computer, or Docker must be installed to create a container from the MySQL Docker image. Here is the command to start a container:
docker run -it --rm -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=movied --name mysqldb -p 3307:3306 mysql:8.0
You will also need the tools required to code in Java:
Setup the Spring Boot Project
We will use the SpringBoot initializer to create the project. The only dependency we need for this project is Spring Web.
Click the button "Generate" to download the project and open it in your favorite IDE.
Create the user entity
Create a package called entities
then, create a file named User.java
representing the user entity. Add the code below:
package com.tericcabrel.movie.entities;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Table(name = "users")
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(nullable = false)
private Integer id;
@Column(unique = true, nullable = false, length = 100)
private String email;
@Column(unique = true, nullable = false, length = 100)
private String name;
public User() {}
public User(String email, String name) {
this.email = email;
this.name = name;
}
public Integer getId() {
return id;
}
public String getEmail() {
return email;
}
public String getName() {
return name;
}
}
Now let's create a file called Movie.java
for the movie entity and add the code below:
package com.tericcabrel.movie.entities;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Table;
@Table(name = "movies")
@Entity
public class Movie {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(nullable = false)
private Integer id;
@Column(nullable = false, length = 150)
private String name;
@Lob
private String description;
@Column(nullable = false)
private Integer releaseDate;
public Movie() {
}
public Movie(String name, String description, Integer releaseDate) {
this.name = name;
this.description = description;
this.releaseDate = releaseDate;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public Integer getReleaseDate() {
return releaseDate;
}
}
Create the Many-to-Many relationship between User and Movie
We use the annotation @ManyToMany to define the relationship in the entity. We have two entities; where do we define the annotation?
It depends on your data access pattern: Do you want to:
- Get movies liked by a user?
or
- Get users who liked a movie?
We will go for the first option so let's update the file User.java to add the code below:
@ManyToMany()
@JoinTable(
name = "users_movies",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "movie_id")
)
private Set<Movie> movies;
public Set<Movie> getMovies() {
return movies;
}
If you still want to get the users who liked a movie, you can add a relationship in the Movie entity, but the declaration is straightforward. Add the code below in the file Movie.java
@ManyToMany(mappedBy = "movies")
private Set<User> users;
public Set<User> getUsers() {
return users;
}
The property mappedBy
has the value movies
which represents the propertymovies
in the file User.java. This is a bidirectional Many-To-Many relationship.
Create the tables in the database with Hibernate.
We defined our entity relation using JPA now; let's configure the database connection in the Spring Boot configuration properties file. Update the file application.properties
with the code below:
server.port=8933
spring.datasource.url=jdbc:mysql://localhost:3307/moviedb?serverTimezone=UTC&allowPublicKeyRetrieval=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=secret
## Hibernate properties
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
spring.jpa.hibernate.use-new-id-generator-mappings=false
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=false
spring.jpa.open-in-view=false
spring.jpa.properties.hibernate.dialect.storage_engine=innodb
Don't forget to start the container for MySQL with the command we gave in the Prerequisites section.
Start the project with the command: mvn spring-boot:run
View the data in your database with your favorite GUI tool. Below is TablePlus.
We used Hibernate to create the database schema, but a better way is to use database migrations. I wrote a whole post about doing that with Spring Boot and Flyway.
Insert data inside the users_movies table
We need to create User and Role repositories first. A Repository acts as a middleware between the Java class and the database schema. It provides methods to interact with the database like inserting, updating, and deleting data.
Create a package called repositories
then, create a file UserRepository.java
.
package com.tericcabrel.movie.repositories;
import com.tericcabrel.movie.entities.User;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends CrudRepository<User, Integer> {
}
Create the file MovieRepository.java
and add the code below:
package com.tericcabrel.movie.repositories;
import com.tericcabrel.movie.entities.Movie;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface MovieRepository extends CrudRepository<Movie, Integer> {
}
Now Let's load our database with some movies, users, and users_movies. Create a file called DataSeeder.java
and the code below:
package com.tericcabrel.movie;
import com.tericcabrel.movie.entities.Movie;
import com.tericcabrel.movie.entities.User;
import com.tericcabrel.movie.repositories.MovieRepository;
import com.tericcabrel.movie.repositories.UserRepository;
import java.util.HashSet;
import java.util.Set;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
@Component
public class DataSeeder implements ApplicationListener<ContextRefreshedEvent> {
private final UserRepository userRepository;
private final MovieRepository movieRepository;
public DataSeeder(UserRepository userRepository, MovieRepository movieRepository) {
this.userRepository = userRepository;
this.movieRepository = movieRepository;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
Movie movie1 = new Movie("Movie 1", "Movie 1 description", 2020);
Movie movie2 = new Movie("Movie 2", "Movie 2 description", 2021);
Movie createdMovie1 = movieRepository.save(movie1);
Movie createdMovie2 = movieRepository.save(movie2);
User user = new User("user@email.com", "John Doe");
Set<Movie> movies = new HashSet<>();
movies.add(createdMovie1);
movies.add(createdMovie2);
user.setMovies(movies);
User createdUser = userRepository.save(user);
createdUser.getMovies().forEach(System.out::println);
}
}
This file will be executed when the server starts. Rerun the server and check your database to see the data inserted.
Yeah ? We are done with this part. Now, if you want to learn how to handle the many-to-many relationships with additional information, check out the second part below:
You can find the code source on the GitHub repository.
Follow me on Twitter or subscribe to my newsletter to not miss the upcoming posts and the tips and tricks I share every week.
Happy to see you soon ?