Many-to-Many Relationship in Spring Boot Rest + JPA

In this article, we will learn about the Many-to-Many relationship in JPA and its implementation in a Spring Boot Application. Let’s make a start!

@ManytoMany annotation

A many-to-many relationship occurs when multiple records in a table have an association with multiple records in another table. @ManytoMany annotation defines a many-valued association with many-to-many multiplicity. As per the official documentation:

Every many-to-many association has two sides, the owning side, and the non-owning, or inverse, side. The join table is specified on the owning side. If the relationship is bidirectional, the non-owning side must use the mappedBy element of the ManyToMany Annotation to specify the relationship field or property of the owning side.

Unidirectional or Bidirectional?

In JPA, we use the @ManyToMany annotation to model Many to Many Relationships. It could either be Uni-directional or Bi-directional.

  • In a unidirectional association, only one entity points to another.
  • In a bidirectional association, both the entities point to each other.

Illustration using an Employees Portal Example

Let’s take an example of an Employees Portal for a University. An Employee can be a Teacher and a Chairman at the same time. Likewise, the role of Teacher and Chairman can assign to several employees from different departments. This is how a Many-to-Many relationship works.

Let’s consider the two entities Users and Role to understand the Many-to-Many relationships in the scenario under consideration. Consequently, here is the simplest form of the Entity-Relationship Diagram:

 

Database Structure

In our example, we are using the PostgreSQL database as the RDMS. On a side note, have you tried creating a table with the name “User” in Postgres? It would not work as User is the keyword in Postgres!

Returning to the ER Diagram, we have users table with id as the Primary Key, and another table role with id as the Primary Key. There is also join table called users_roles responsible for connecting the two sides with the use of the Foreign Keys user id and role id.

Initialize Project Structure

Create a Maven, Spring Boot Project in any of your favorite IDE. Refer to this article for more details.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.6</version>
        <relativePath/>
        <!-- lookup parent from repository -->
    </parent>
    <groupId>com.library</groupId>
    <artifactId>manytomanyrelationship</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ManyToManyExample</name>
    <description>Many to Many Relationship Example in Spring Boot Application</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

 

application.properties

pring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=test
spring.datasource.password=test
spring.datasource.driverClassName=org.postgresql.Driver
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto = create
spring.jpa.show-sql= true

Define the Domain Model Classes

Create model classes for Users and Role with the JPA Annotations. Later on execution, Spring and JPA will handle the rest for you.

Users

package com.manytomanyrelationship.domain;

import javax.persistence.*;
import java.util.Collection;

@Entity
public class Users {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Integer id;
    String username;
    String password;

    @ManyToMany
    @JoinTable(name = "users_roles", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
    private Collection<Role> roles;

    public Users() {

    }

    public Users(String username, String password, Collection<Role> roles) {
        this.username = username;
        this.password = password;
        this.roles = roles;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Collection<Role> getRoles() {
        return roles;
    }

    public void setRoles(Collection<Role> roles) {
        this.roles = roles;
    }
}

Role

package com.manytomanyrelationship.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Integer id;
    String name;

    public Role(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Role(String name) {
        super();
        this.name = name;
    }

    public Role() { // TODO Auto-generated constructor stub }

    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

The role is a simple Model class with no information about Users at all. However, we have used @ManyToMany annotation in a Users entity, therefore it makes a Unidirectional Many to Many Mapping. Moreover, navigation is only possible from one side.

  • The annotation @Entity indicates that the classes are JPA entities.
  • The attribute id is marked with @Id and @GeneratedValue annotations in both classes. Firstly, @Id annotation denotes that this is the primary key. The latter annotation defines the primary key generation strategy with a strategy type as AUTOINCREMENT.
  • The @ManyToMany annotation is applied to the List of Role attribute in the Users class, indicating many-to-many relationship between the two entities. Furthermore, using CascadeType.ALL assures cascading means after the persistence of Users tuples, the persistence of Role tuples also occurs.
  • In addition, to the @ManyToMany annotation, the @JoinTable annotation is used. The @JoinTable annotation serves the purpose of creating a users_role table resulting in connecting the Users and Role entity. The parameter joinColumn will hold the primary key of this Users entity (the Owning side) whereas inverseJoinColumns will hold the primary key of the other entity (the inverse of the relationship) i.e. Role in our example.

Define the Rest Controller

UsersController

package com.manytomanyrelationship.rest;

import com.manytomanyrelationship.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UsersController {
@Autowired
private UsersService usersService;

@RequestMapping("/getUsers")
public ResponseEntity<Object> getAllUsers() {
return usersService.getAllUsers();
}
}

Define the Service Class

UsersService

package com.manytomanyrelationship.service;

import com.manytomanyrelationship.domain.Users;
import com.manytomanyrelationship.repository.UsersRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UsersService {

    @Autowired
    private UsersRepository usersRepository;

    public ResponseEntity<Object> getAllUsers() {
        List<Users> dbUsers = usersRepository.findAll();
        if (dbUsers.isEmpty()) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        // TODO Auto-generated method stub
        return new ResponseEntity<>(dbUsers, HttpStatus.OK);
    }
}

Define the Repository Class

UsersRepository

Let’s define the UsersRepository to get Users from the database. Create class UsersRepository and extend it from the JpaRepository interface. It provides methods for generic CRUD operations.

package com.manytomanyrelationship.repository;

import com.manytomanyrelationship.domain.Users;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UsersRepository extends JpaRepository<Users, Integer> { 

}

Build and Run the Application

Now it’s time to compile the code, run the application and see JPA Hibernate in action. Now go to the database and you’ll see the three tables. Because the property of creating the DDL is set to true, the tables are generated automatically.

Testing

For testing, let’s add a few of the records in our main Spring Boot Application class.

package com.manytomanyrelationship;

import com.manytomanyrelationship.domain.Role;
import com.manytomanyrelationship.domain.Users;
import com.manytomanyrelationship.repository.UsersRepository;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@SpringBootApplication
public class DemoApplication {

    static final Logger logger = LogManager.getLogger(DemoApplication.class);

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
        logger.info("hello!!");
        UsersRepository usersRepository = context.getBean(UsersRepository.class);

        usersRepository.save(createUserObject1());
        usersRepository.save(createUserObject2());
    }

    private static Users createUserObject1() {
        Users user = new Users();
        user.setUsername("user1");
        user.setPassword("testPassword");

        Role role = new Role();
        role.setName("Lab Staff");
        List<Role> roles = Collections.singletonList(role);
        user.setRoles(roles);
        return user;
    }

    private static Users createUserObject2() {
        Users user = new Users();
        user.setUsername("user2");
        user.setPassword("testPassword2");

        Role role1 = new Role();
        role1.setName("Teacher");

        Role role2 = new Role();
        role2.setName("Chairman");
        List<Role> roles = new ArrayList<>();
        roles.add(role1);
        roles.add(role2);
        user.setRoles(roles);
        return user;
    }
}

We have saved two Users records using UsersRepository and from the console, we observe the order of persistence of records. Firstly, the data is entered into the Users table. Secondly, the Role table’s records are inserted. Finally, the data in the user_roles table are populated with the relevant primary keys. Here is a  snapshot for console logs for better understanding:

We can now test the REST API that we have exposed.

Many to Many relationship in Spring Boot and JPA

Please notice that we have provided the password in plain text for the sake of simplicity. The best practice is not to store passwords in plain text and not to return them in plain text in the HTTP Response, especially in the Production Environment as this is not secure.  To discover how to encrypt user’s password using Spring Framework read out this article.

Conclusion

In this article, we have covered how to implement a Uni-directional Many to Many Relationship with JPA in a Spring Boot Application. If you find this article helpful, don’t forget to share your feedback or thoughts in the comments section.

If you want to learn more about the Spring Boot articles, stay tuned for some exciting stuff coming ahead!

Happy Coding!


Leave a Reply

Your email address will not be published. Required fields are marked *

Free Video Lessons

Enter your email and stay on top of things,

Subscribe!