Hibernate 5 - Many to Many association example

Posted on April 2, 2017


Technologies used:   JDK 1.8.0_121 | Hibernate 5.2.6.Final | Maven 3.3.9 | MySQL 5.7.12 | Eclipse Neon.3

In many-to-many association, source entity has a field that stores collection of target entities. The @ManyToMany JPA annotation is used to link the source entity with the target entity.

A many-to-many association always uses an intermediate join table to store the association that joins two entities. The join table is defined using the @JoinTable JPA annotation.

The many-to-many association can be either unidirectional or bidirectional.

 In unidirectional association, only source entity has a relationship field that refers to the target entities. We can navigate this type of association from one side.

In bidirectional association, each entity (i.e. source and target) has a relationship field that refers to each other. We can navigate this type of association from both sides.

Unidirectional many-to-many association example

Consider the following domain model and relational model diagrams of many-to-many unidirectional association.

many-to-many-uni-domain-model.png

many-to-many-uni.png

According to the models diagrams, an employee can have any number of addresses and an address can belong to any number of employees.

Jar dependencies

Add the following jar dependencies for Hibernate and MySQL driver in pom.xml file.

<dependencies>
  <!-- Mysql Connector -->
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.5</version>
  </dependency>
  <!-- Hibernate 5.2.6 Final -->
  <dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.2.6.Final</version>
  </dependency>
</dependencies>

Entity class

Create two @Entity classes - Employee and Address, to map with EMPLOYEE and ADDRESS tables respectively.

Employee.java

package com.boraji.tutorial.hibernate.entity;

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

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

/**
 * @author imssbora
 */
@Entity
@Table(name = "EMPLOYEE")
public class Employee {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "EMP_ID")
   private long id;

   @Column(name = "NAME", nullable = false, unique = true)
   private String name;

   @Column(name = "DESIGNATION")
   private String designation;

   @Column(name = "SALARY")
   private double salary;

   @ManyToMany(cascade = CascadeType.ALL)
   @JoinTable(name = "EMPLOYEE_ADDRESS", 
         joinColumns = { @JoinColumn(name = "EMP_ID") }, 
         inverseJoinColumns = { @JoinColumn(name = "ADDR_ID") })
   private List<Address> addresses = new ArrayList<>();

   public Employee() { }
   
   public Employee(String name, String designation, double salary) {
      this.name = name;
      this.designation = designation;
      this.salary = salary;
   }
   // Setter and Getter methods
}

Address.java

package com.boraji.tutorial.hibernate.entity;

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

/**
 * @author imssbora
 */
@Entity
@Table(name = "ADDRESS")
public class Address {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "ADDR_ID")
   private long id;

   @Column(name = "CITY")
   private String city;

   @Column(name = "STATE")
   private String state;

   @Column(name = "COUNTRY")
   private String country;

   @Column(name = "ZIP")
   private String zip;

   public Address() { }
   
   public Address(String city, String state, String country, String zip) {
      this.city = city;
      this.state = state;
      this.country = country;
      this.zip = zip;
   }
   // Setter and Getter methods
}

The @JoinTable annotation is used to create the EMPLOYEE_ADDRESS join table. This table defines an EMP_ID foreign key to the source entity’s table primary key and an ADDR_ID foreign key to the target entity’s table primary key.

Hibernate utility class

Create a helper class HibernateUtil to bootstrap hibernate.

Map the Employee and Address entities using the #MetadataSources.addAnnotatedClass() method.

HibernateUtil.java

package com.boraji.tutorial.hibernate;

import java.util.HashMap;
import java.util.Map;

import org.hibernate.SessionFactory;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;

import com.boraji.tutorial.hibernate.entity.Employee;
import com.boraji.tutorial.hibernate.entity.Address;

public class HibernateUtil {
   private static StandardServiceRegistry registry;
   private static SessionFactory sessionFactory;

   public static SessionFactory getSessionFactory() {
      if (sessionFactory == null) {
         try {
            StandardServiceRegistryBuilder registryBuilder = 
                  new StandardServiceRegistryBuilder();

            Map<String, String> settings = new HashMap<>();
            settings.put("hibernate.connection.driver_class", "com.mysql.cj.jdbc.Driver");
            settings.put("hibernate.connection.url", "jdbc:mysql://localhost:3306/BORAJI");
            settings.put("hibernate.connection.username", "root");
            settings.put("hibernate.connection.password", "admin");
            settings.put("hibernate.show_sql", "true");
            settings.put("hibernate.hbm2ddl.auto", "update");

            registry = registryBuilder.applySettings(settings).build();

            MetadataSources sources = new MetadataSources(registry)
                  .addAnnotatedClass(Employee.class)
                  .addAnnotatedClass(Address.class);

            Metadata metadata = sources.getMetadataBuilder().build();

            sessionFactory = metadata.getSessionFactoryBuilder().build();
         } catch (Exception e) {
            System.out.println("SessionFactory creation failed");
            if (registry != null) {
               StandardServiceRegistryBuilder.destroy(registry);
            }
         }
      }
      return sessionFactory;
   }

   public static void shutdown() {
      if (registry != null) {
         StandardServiceRegistryBuilder.destroy(registry);
      }
   }
}

 

Main class 

Create the MainApp class to run the application.

MainApp.java

package com.boraji.tutorial.hibernate;

import org.hibernate.Session;
import org.hibernate.Transaction;

import com.boraji.tutorial.hibernate.entity.Address;
import com.boraji.tutorial.hibernate.entity.Employee;

/**
 * @author imssbora
 */
public class MainApp {
   public static void main(String[] args) {
      Session session = null;
      Transaction transaction = null;
      try {
         session = HibernateUtil.getSessionFactory().openSession();
         transaction = session.beginTransaction();
         transaction.begin();

         Address address1 = new Address("Bengaluru", "Karnataka", "India", "560016");
         Address address2 = new Address("Jaipur", "Rajasthan", "India", "302017");

         // Employee1 have 2 addresses
         Employee employee1 = new Employee("Ravindra Singh", "Sales Manager", 450000);
         employee1.getAddresses().add(address1);
         employee1.getAddresses().add(address2);

         // Employee2 have 1 address
         Employee employee2 = new Employee("Mohit Sharma", "Software Engineer", 850000);
         employee2.getAddresses().add(address1);

         session.save(employee1);
         session.save(employee2);
         transaction.commit();

         System.out.println("Records saved successfully");

      } catch (Exception e) {
         if (transaction != null) {
            System.out.println("Transaction is being rolled back.");
            transaction.rollback();
         }
         e.printStackTrace();
      } finally {
         if (session != null) {
            session.close();
         }
      }
      HibernateUtil.shutdown();
   }
}

Output

Hibernate: create table ADDRESS (ADDR_ID bigint not null auto_increment, CITY varchar(255), COUNTRY varchar(255), STATE varchar(255), ZIP varchar(255), primary key (ADDR_ID))
Hibernate: create table EMPLOYEE (EMP_ID bigint not null auto_increment, DESIGNATION varchar(255), NAME varchar(255) not null, SALARY double precision, primary key (EMP_ID))
Hibernate: create table EMPLOYEE_ADDRESS (EMP_ID bigint not null, ADDR_ID bigint not null)
Hibernate: alter table EMPLOYEE drop constraint UK_tbmwadt7avqfm0yn9c2yd3gf3
Hibernate: alter table EMPLOYEE add constraint UK_tbmwadt7avqfm0yn9c2yd3gf3 unique (NAME)
Hibernate: alter table EMPLOYEE_ADDRESS add constraint FKs5w9xwsvms7rjkfahuaqd6x0d foreign key (ADDR_ID) references ADDRESS (ADDR_ID)
Hibernate: alter table EMPLOYEE_ADDRESS add constraint FK9bbne3bq7xbgvqq8esq8umtnp foreign key (EMP_ID) references EMPLOYEE (EMP_ID)
Hibernate: insert into EMPLOYEE (DESIGNATION, NAME, SALARY) values (?, ?, ?)
Hibernate: insert into ADDRESS (CITY, COUNTRY, STATE, ZIP) values (?, ?, ?, ?)
Hibernate: insert into ADDRESS (CITY, COUNTRY, STATE, ZIP) values (?, ?, ?, ?)
Hibernate: insert into EMPLOYEE (DESIGNATION, NAME, SALARY) values (?, ?, ?)
Hibernate: insert into EMPLOYEE_ADDRESS (EMP_ID, ADDR_ID) values (?, ?)
Hibernate: insert into EMPLOYEE_ADDRESS (EMP_ID, ADDR_ID) values (?, ?)
Hibernate: insert into EMPLOYEE_ADDRESS (EMP_ID, ADDR_ID) values (?, ?)
Records saved successfully

On executing the MainApp class, you will see the following records in EMPLOYEE, ADDRESS and EMPLOYEE_ADDRESS tables.

mysql> select * from employee;
+--------+-------------------+----------------+--------+
| EMP_ID | DESIGNATION       | NAME           | SALARY |
+--------+-------------------+----------------+--------+
|      1 | Sales Manager     | Ravindra Singh | 450000 |
|      2 | Software Engineer | Mohit Sharma   | 850000 |
+--------+-------------------+----------------+--------+
2 rows in set (0.00 sec)

mysql> select * from address;
+---------+-----------+---------+-----------+--------+
| ADDR_ID | CITY      | COUNTRY | STATE     | ZIP    |
+---------+-----------+---------+-----------+--------+
|       1 | Bengaluru | India   | Karnataka | 560016 |
|       2 | Jaipur    | India   | Rajasthan | 302017 |
+---------+-----------+---------+-----------+--------+
2 rows in set (0.00 sec)

mysql> select * from employee_address;
+--------+---------+
| EMP_ID | ADDR_ID |
+--------+---------+
|      1 |       1 |
|      1 |       2 |
|      2 |       1 |
+--------+---------+
3 rows in set (0.00 sec)

 

Bidirectional many-to-many association example

Following are the domain model and relational model diagrams of many-to-many bidirectional association.

 many-to-many-bi-domain-model.png

many-to-many-bi.png

Entity class

To define a many-to-many bidirectional association, the @ManyToMany annotation must be used in the target and source entities. One entity must be defined as the owner and the other must use the mappedBy attribute to define its mapping.

Modify the code of Employee and Address  classes as follows.

Employee.java

package com.boraji.tutorial.hibernate.entity;

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

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

/**
 * @author imssbora
 */
@Entity
@Table(name = "EMPLOYEE")
public class Employee {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "EMP_ID")
   private long id;

   @Column(name = "NAME", nullable = false, unique = true)
   private String name;

   @Column(name = "DESIGNATION")
   private String designation;

   @Column(name = "SALARY")
   private double salary;

   @ManyToMany(cascade = CascadeType.ALL, fetch=FetchType.LAZY)
   @JoinTable(name = "EMPLOYEE_ADDRESS", 
         joinColumns = { @JoinColumn(name = "EMP_ID") }, 
         inverseJoinColumns = { @JoinColumn(name = "ADDR_ID") })
   private List<Address> addresses = new ArrayList<>();

   public Employee() { }
   
   public Employee(String name, String designation, double salary) {
      this.name = name;
      this.designation = designation;
      this.salary = salary;
   }
   // Getter and Setter methods
}

Address.java

import javax.persistence.Table;

/**
 * @author imssbora
 */
@Entity
@Table(name = "ADDRESS")
public class Address {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "ADDR_ID")
   private long id;

   @Column(name = "CITY")
   private String city;

   @Column(name = "STATE")
   private String state;

   @Column(name = "COUNTRY")
   private String country;

   @Column(name = "ZIP")
   private String zip;

   @ManyToMany(fetch=FetchType.LAZY, mappedBy = "addresses")
   private List<Employee> employees = new ArrayList<>();

   public Address() {  }
   
   public Address(String city, String state, String country, String zip) {
      this.city = city;
      this.state = state;
      this.country = country;
      this.zip = zip;
   }
   //Setter and Getter methods
}

 

Hibernate utility class

No changes are needed in the HibernateUtil class. We can use the same class as used in the many-to-many unidirectional association example.

Main class 

No changes are needed in the  MainApp class.We can use existing main class to test bidirectional association example.

The output of bidirectional association example will look like as follows.

Hibernate: create table ADDRESS (ADDR_ID bigint not null auto_increment, CITY varchar(255), COUNTRY varchar(255), STATE varchar(255), ZIP varchar(255), primary key (ADDR_ID))
Hibernate: create table EMPLOYEE (EMP_ID bigint not null auto_increment, DESIGNATION varchar(255), NAME varchar(255) not null, SALARY double precision, primary key (EMP_ID))
Hibernate: create table EMPLOYEE_ADDRESS (EMP_ID bigint not null, ADDR_ID bigint not null)
Hibernate: alter table EMPLOYEE drop constraint UK_tbmwadt7avqfm0yn9c2yd3gf3
Hibernate: alter table EMPLOYEE add constraint UK_tbmwadt7avqfm0yn9c2yd3gf3 unique (NAME)
Hibernate: alter table EMPLOYEE_ADDRESS add constraint FKs5w9xwsvms7rjkfahuaqd6x0d foreign key (ADDR_ID) references ADDRESS (ADDR_ID)
Hibernate: alter table EMPLOYEE_ADDRESS add constraint FK9bbne3bq7xbgvqq8esq8umtnp foreign key (EMP_ID) references EMPLOYEE (EMP_ID)
Hibernate: insert into EMPLOYEE (DESIGNATION, NAME, SALARY) values (?, ?, ?)
Hibernate: insert into ADDRESS (CITY, COUNTRY, STATE, ZIP) values (?, ?, ?, ?)
Hibernate: insert into ADDRESS (CITY, COUNTRY, STATE, ZIP) values (?, ?, ?, ?)
Hibernate: insert into EMPLOYEE (DESIGNATION, NAME, SALARY) values (?, ?, ?)
Hibernate: insert into EMPLOYEE_ADDRESS (EMP_ID, ADDR_ID) values (?, ?)
Hibernate: insert into EMPLOYEE_ADDRESS (EMP_ID, ADDR_ID) values (?, ?)
Hibernate: insert into EMPLOYEE_ADDRESS (EMP_ID, ADDR_ID) values (?, ?)
Records saved successfully

On executing the MainApp class, you will see the following records in EMPLOYEE, ADDRESS and EMPLOYEE_ADDRESS tables.

mysql> select * from employee;
+--------+-------------------+----------------+--------+
| EMP_ID | DESIGNATION       | NAME           | SALARY |
+--------+-------------------+----------------+--------+
|      1 | Sales Manager     | Ravindra Singh | 450000 |
|      2 | Software Engineer | Mohit Sharma   | 850000 |
+--------+-------------------+----------------+--------+
2 rows in set (0.01 sec)

mysql> select * from address;
+---------+-----------+---------+-----------+--------+
| ADDR_ID | CITY      | COUNTRY | STATE     | ZIP    |
+---------+-----------+---------+-----------+--------+
|       1 | Bengaluru | India   | Karnataka | 560016 |
|       2 | Jaipur    | India   | Rajasthan | 302017 |
+---------+-----------+---------+-----------+--------+
2 rows in set (0.00 sec)

mysql> select * from employee_address;
+--------+---------+
| EMP_ID | ADDR_ID |
+--------+---------+
|      1 |       1 |
|      1 |       2 |
|      2 |       1 |
+--------+---------+
3 rows in set (0.00 sec)
Download Sources