AdBlock Detected

It looks like you're using an ad-blocker!

Our team work realy hard to produce quality content on this website and we noticed you have ad-blocking enabled. Advertisements and advertising enable us to continue working and provide high-quality content.

many-to-many with Hibernate in Spring Boot

In this post, we are going to show an example of a Many-to-Many relationship in Hibernate. We will demonstrate how to map and use JPA through Hibernate to achieve this type of relationship.

If you need to see how to create a One-to-Many relationship, you can take a look here.

In this article, we will also explore the most efficient way to establish this type of relationship using the @ManyToMany annotation.

How does a Many-to-Many relationship work?

A Many-to-Many relationship involves the creation of an intermediate table. For example, let’s consider two entities: “Employee” and “Department”. A department can have 1 to N employees, and an employee can belong to 1 to M departments. This results in an M to N relationship, which requires an extra table. This extra table is represented as a 1 to N relationship:

Example of Many-to-Many Relationship in Hibernate
RelationShip ManyToMany with Hibernate

This would be the representation in the database of an N to M relationship, where we would have the two source tables and a new table formed from the other two. This new table will have the IDs referencing the other two tables.

Depending on how we implement this relationship, we can obtain different results. In this article, we will explore the implementation of the Many-to-Many relationship in two different ways:

  • Implementation of Many-to-Many with List
  • Implementation of Many-to-Many with Set.

Many-to-Many Relationship in Hibernate with List

Now let’s see how to implement a Many-to-Many relationship with Hibernate using List.

@Entity
@NoArgsConstructor
@Getter
@Setter
public class Department {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private String code;

    @ManyToMany(cascade = {
            CascadeType.PERSIST,
            CascadeType.MERGE
    })
    @JoinTable(name = "department_employee",
            joinColumns = @JoinColumn(name = "department_id"),
            inverseJoinColumns = @JoinColumn(name = "employee_id")
    )
    private List<Employee> employees = new ArrayList<>();

    public void addEmployee(Employee employee) {
        employees.add(employee);
        employee.getDepartments().add(this);
    }

    public void removeEmployee(Employee employee) {
        employees.remove(employee);
        employee.getDepartments().remove(this);
    }

}
@Getter
@Setter
@Entity
@NoArgsConstructor
public class Employee {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "employees")
    public List<Department> departments = new ArrayList<>();
}

The previous two classes demonstrate the ManyToMany relationship between Department and Employee, mapped with a List.

Now let’s see the most notable points:

  • Similar to a @OneToMany relationship, we have created two methods, add and remove, to enable bidirectional association and maintain consistency.
  • In the Department table, we only use Merge and Persist operations. If we also add remove, it could lead to cascading deletion and delete both sides.
  • We use the Lombok annotation EqualsAndHashCode.Include to compare objects based on their Id, as we know the Id will always be unique.
  • Finally, to create the intermediate table, we use the @JoinTable annotation, specifying the name of the intermediate table and the foreign key IDs of the other tables. We have a bidirectional relationship where Department is the owner of the established relationship.

Let’s test the result of executing this relationship:

@SpringBootTest
public class DepartmentRepositoryTest {

    @Autowired
    private DepartmentListRepository departmentListRepository;

    @Test
    public void when_save_new_department_with_multiples_employees_then_department_is_saved_correctly() {

        Department department = new Department();
        department.setName("IT");

        var employee = new Employee();
        employee.setName("Noel");

        department.getEmployees().add(employee);

        departmentListRepository.save(department);
    }
}
Hibernate: 
    insert 
    into
        Department
        (code, name, id) 
    values
        (?, ?, ?)
Hibernate: 
    insert 
    into
        Employee
        (name, id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        department_employee
        (department_id, employee_id) 
    values
        (?, ?)

As we can see, we perform three inserts, one for each table.

The problem with this type of relationship arises when we want to delete a row in the database. In that case, instead of deleting a single row, it deletes all the associated rows from the associated department_employee table and then inserts the remaining ones. Obviously, this is not optimal in terms of performance, which is why using a List for a ManyToMany relationship is not the best approach.

Let’s analyze the operations performed with a delete in a test:

    @Test
    @Sql("classpath:createDepartment.sql")
    public void when_remove_employee_from_department_then_employee_is_removed_correctly() {

        var departmentList = departmentRepository.findAll();

        var department = departmentList.stream().findFirst().orElseThrow();
        var employee = department.getEmployees().stream().findFirst().orElseThrow();

        department.removeEmployee(employee);
        departmentRepository.save(department);

    }

We have a previously made insert using a file named “createDepartment” in our resources folder, which we load using the @Sql annotation. In the test, we delete an employee and update. And we can see that we have more operations than necessary:

Hibernate: 
    select
        department0_.id as id1_0_,
        department0_.code as code2_0_,
        department0_.name as name3_0_ 
    from
        Department department0_
Hibernate: 
    select
        employees0_.department_id as departme1_1_0_,
        employees0_.employee_id as employee2_1_0_,
        employee1_.id as id1_2_1_,
        employee1_.name as name2_2_1_ 
    from
        department_employee employees0_ 
    inner join
        Employee employee1_ 
            on employees0_.employee_id=employee1_.id 
    where
        employees0_.department_id=?
Hibernate: 
    select
        department0_.employee_id as employee2_1_0_,
        department0_.department_id as departme1_1_0_,
        department1_.id as id1_0_1_,
        department1_.code as code2_0_1_,
        department1_.name as name3_0_1_ 
    from
        department_employee department0_ 
    inner join
        Department department1_ 
            on department0_.department_id=department1_.id 
    where
        department0_.employee_id=?
Hibernate: 
    select
        department0_.id as id1_0_1_,
        department0_.code as code2_0_1_,
        department0_.name as name3_0_1_,
        employees1_.department_id as departme1_1_3_,
        employee2_.id as employee2_1_3_,
        employee2_.id as id1_2_0_,
        employee2_.name as name2_2_0_ 
    from
        Department department0_ 
    left outer join
        department_employee employees1_ 
            on department0_.id=employees1_.department_id 
    left outer join
        Employee employee2_ 
            on employees1_.employee_id=employee2_.id 
    where
        department0_.id=?
Hibernate: 
    delete 
    from
        department_employee 
    where
        department_id=?
Hibernate: 
    insert 
    into
        department_employee
        (department_id, employee_id) 
    values
        (?, ?)

We can see in the last two operations that we perform a delete and then an insert, which is not very optimal. This lack of performance is due to using a List instead of a Set.

ManyToMany Relationship in Hibernate with Set

Next, we are going to solve the performance issue we encountered when creating collections with List.

To do this, we will use a Set in our Entity classes, which is the recommended way to handle a ManyToMany relationship.

@Entity
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Department {

    @Id
    @GeneratedValue
    @EqualsAndHashCode.Include()
    private Long id;

    private String name;

    private String code;

    @ManyToMany(cascade = {
            CascadeType.PERSIST,
            CascadeType.MERGE
    })
    @JoinTable(name = "department_employee",
            joinColumns = @JoinColumn(name = "department_id"),
            inverseJoinColumns = @JoinColumn(name = "employee_id")
    )
    private Set<Employee> employees = new HashSet<>();

    public void addEmployee(Employee employee) {
        employees.add(employee);
        employee.getDepartments().add(this);
    }

    public void removeEmployee(Employee employee) {
        employees.remove(employee);
        employee.getDepartments().remove(this);
    }

}
@Getter
@Setter
@Entity
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Employee {

    @Id
    @GeneratedValue
    @EqualsAndHashCode.Include()
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "employees")
    public Set<Department> departments = new HashSet<>();
}

We are going to execute the same test that we previously executed to delete an employee from a department, and we will see what Hibernate has done:

Hibernate: 
    select
        department0_.id as id1_2_,
        department0_.code as code2_2_,
        department0_.name as name3_2_ 
    from
        DepartmentSet department0_
Hibernate: 
    select
        employees0_.department_id as departme1_3_0_,
        employees0_.employee_id as employee2_3_0_,
        employeese1_.id as id1_5_1_,
        employeese1_.name as name2_5_1_ 
    from
        departmentSet_employeeSet employees0_ 
    inner join
        EmployeeSet employeese1_ 
            on employees0_.employee_id=employeese1_.id 
    where
        employees0_.department_id=?
Hibernate: 
    select
        department0_.employee_id as employee2_3_0_,
        department0_.department_id as departme1_3_0_,
        department1_.id as id1_2_1_,
        department1_.code as code2_2_1_,
        department1_.name as name3_2_1_ 
    from
        departmentSet_employeeSet department0_ 
    inner join
        DepartmentSet department1_ 
            on department0_.department_id=department1_.id 
    where
        department0_.employee_id=?
Hibernate: 
    select
        department0_.id as id1_2_1_,
        department0_.code as code2_2_1_,
        department0_.name as name3_2_1_,
        employees1_.department_id as departme1_3_3_,
        employeese2_.id as employee2_3_3_,
        employeese2_.id as id1_5_0_,
        employeese2_.name as name2_5_0_ 
    from
        DepartmentSet department0_ 
    left outer join
        departmentSet_employeeSet employees1_ 
            on department0_.id=employees1_.department_id 
    left outer join
        EmployeeSet employeese2_ 
            on employees1_.employee_id=employeese2_.id 
    where
        department0_.id=?
Hibernate: 
    delete 
    from
        departmentSet_employeeSet 
    where
        department_id=? 
        and employee_id=?

As we can see in the previous logs, we only performed a delete to remove the employee from the department, so the performance with a Set is much better than with a List in a ManyToMany relationship.

Example of ManyToMany Relationship with Set in Spring Boot

Next, we are going to define an example of Many-to-Many Relationship in Hibernate using Spring Boot. This example will use Set to map the collections, as it provides better performance. We will start by creating the project through the initializr page.

In the following sections, we will gradually create the necessary layers of our application.

  • The dependencies we will select are Spring Web, Spring Data, Lombok, and H2.
  • Spring Data includes all the persistence part, incorporating Hibernate + JPA.
  • Lombok will help us reduce the boilerplate code.

H2 is an in-memory database perfect for testing purposes, not to be used in production environments.

You can directly download the example from here.

Maven Dependencies

		<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>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>

H2 Database Configuration

Next, we are going to add the necessary configuration to make use of our in-memory H2 database. It is important to note that in order to access the H2 console, it needs to be enabled using the property h2.console.enabled.

spring:
  application:
    name: one-to-many
  datasource:
    url: jdbc:h2:mem:testdb
    driverClassName: org.h2.Driver
    username: sa
    password: password
    initialize: true
    initialization-mode: always
    hikari:
      connection-timeout: 6000
      initialization-fail-timeout: 0
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: update
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    properties:
      hibernate:
        show_sql: true
        format_sql: true
        enable_lazy_load_no_trans: true


  h2:
    console:
      enabled: true

Additionally, we are going to perform data import. For that, we add a file named import.sql in our resources folder.

INSERT INTO DEPARTMENT VALUES (1,'IT', 'CO');

INSERT INTO EMPLOYEE VALUES (1,'Noel');
INSERT INTO EMPLOYEE VALUES (2,'PEPE');

INSERT INTO DEPARTMENT_EMPLOYEE  VALUES (1,1);
INSERT INTO DEPARTMENT_EMPLOYEE VALUES (1,2);

Entity Creation and ManyToMany Relationship

For our example, we are going to create two entities (located in the domain package). These two entities will form a ManyToMany relationship with each other: Department and Employee.

The Department entity will be the parent relationship and will use a Set to establish the mapping and bidirectional relationship with Employee.

@Getter
@Setter
@Entity
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Employee {

    @Id
    @GeneratedValue
    @EqualsAndHashCode.Include()
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "employees")
    public List<Department> departments = new ArrayList<>();
}
@Entity
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Department {

    @Id
    @GeneratedValue
    @EqualsAndHashCode.Include()
    private Long id;

    private String name;

    private String code;

    @ManyToMany(cascade = {
            CascadeType.PERSIST,
            CascadeType.MERGE
    })
    @JoinTable(name = "department_employee",
            joinColumns = @JoinColumn(name = "department_id"),
            inverseJoinColumns = @JoinColumn(name = "employee_id")
    )
    private List<Employee> employees = new ArrayList<>();

    public void addEmployee(Employee employee) {
        employees.add(employee);
        employee.getDepartments().add(this);
    }

    public void removeEmployee(Employee employee) {
        employees.remove(employee);
        employee.getDepartments().remove(this);
    }

}

As mentioned earlier, we add two additional methods, add and remove, to maintain integrity between both tables. Additionally, the allowed methods will be Persist and Merge, as adding remove could potentially cause instability in the database by deleting objects from both sides.

Repository Creation

For creating a repository for the bank, we will make use of JpaRepository, which is provided by Spring Data. This class will allow us to use all the necessary methods and actions for performing operations in our database.

public interface DepartmentRepository extends JpaRepository<Departmen, Long> {

}

Service Creation

Next, we will create a service layer that will act as a pass-through between the controller and the repository. In other words, it will only be responsible for passing and propagating information from the controller to the repository.

@Service
@RequiredArgsConstructor
public class DepartmentService {

    private final DepartmentRepository departmentRepository;

    public List<Department> findAll() {

        return departmentRepository.findAll();
    }

    public Department findById(Long id) {

        return departmentRepository.findById(id).orElseThrow();
    }

    public Department save (Department department) {
        
        return departmentRepository.save(department);
    }
    
    public void delete(Department department) {
        
        departmentRepository.delete(department);
    }
}

Controller Creation for Department

Next, we are going to create four endpoints to perform basic operations on the application. For simplicity, we won’t create DTOs, so we will use the Domain classes for the controller.

@RestController
@RequiredArgsConstructor
@RequestMapping("/departments")
public class DepartmentController {


    private final DepartmentService departmentService;

    @GetMapping
    public ResponseEntity<List<Department>> getDepartments() {

        var departments = departmentService.findAll();

        return new ResponseEntity<>(departments, HttpStatus.OK);
    }

    @GetMapping("/{id}")
    public ResponseEntity<Department> getDepartment(@PathVariable Long id) {

        var department = departmentService.findById(id);

        return new ResponseEntity<>(department, HttpStatus.OK);

    }

    @PostMapping()
    public ResponseEntity<Department> saveDepartment(@RequestBody Department department) {

        var departmentSaved = departmentService.save(department);

        return new ResponseEntity<>(departmentSaved, HttpStatus.OK);

    }

    @DeleteMapping()
    public ResponseEntity<Department> deleteDepartment(@RequestBody Department department) {

        departmentService.delete(department);

        return new ResponseEntity<>(department, HttpStatus.OK);

    }

}

Conclusion

In this post about an example of Many-to-Many Relationship in Hibernate, we have seen two different approaches we can take for a @ManyToMany relationship in Hibernate: using Set and using List. As we have seen, using Set provides better performance and optimization, as it reduces the number of operations during deletion.

If you want to see the complete example of a ManyToMany relationship with Hibernate in Spring Boot, you can find it on our GitHub page.

If you need more information, you can leave us a comment or send an email to refactorizando.web@gmail.com You can also contact us through our social media channels on Facebook or twitter and we will be happy to assist you!!

Leave a Reply

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