Welcome to the Hibernate Tutorial Series. In previous tutorial we saw how to implement One to Many Annotation mapping as well as XML mapping. In this tutorial we will understand How to implement Bi-directional Many-to-Many relationship in Hibernate using XML mappings.
First let us see the formal definition of Many-to-Many relationship:
Many-To-Many Relationship:
A logical data relationship in which the value of one data element can exist in combination with many values of another data element, and vice versa.
Let us see how to implement Many-to-Many relationship in Hibernate using XML mapping.
For this example, we will MySQL database. We are using Employee-Meeting relationship as a many to many relationship example. Each Employee can attain more than one meetings and each meetings can have more than one employee.
CREATE TABLE `employee` (
`employee_id` BIGINT(10) NOT NULL AUTO_INCREMENT,
`firstname` VARCHAR(50) NULL DEFAULT NULL,
`lastname` VARCHAR(50) NULL DEFAULT NULL,
PRIMARY KEY (`employee_id`)
)
CREATE TABLE `meeting` (
`meeting_id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`subject` VARCHAR(50) NOT NULL,
`meeting_date` DATE NOT NULL,
PRIMARY KEY (`meeting_id`)
)
CREATE TABLE `employee_meeting` (
`employee_id` BIGINT(20) NOT NULL,
`meeting_id` BIGINT(20) NOT NULL,
PRIMARY KEY (`employee_id`, `meeting_id`),
INDEX `FK_MEETING` (`meeting_id`),
CONSTRAINT `FK_EMPLOYEE` FOREIGN KEY (`employee_id`) REFERENCES `employee` (`employee_id`),
CONSTRAINT `FK_MEETING` FOREIGN KEY (`meeting_id`) REFERENCES `meeting` (`meeting_id`)
)
Code language: SQL (Structured Query Language) (sql)
We are using Maven for dependency management. Copy following in the pom.xml.
File: pom.xml
<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 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.viralpatel.hibernate</groupId>
<artifactId>HibernateHelloWorldXML</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>HibernateHelloWorldXML</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.10</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>3.2.6.ga</version>
</dependency>
</dependencies>
</project>
Code language: HTML, XML (xml)
Employee and Department model classes are created which maps to the corresponding database tables.
File: Employee.java
package net.viralpatel.hibernate;
import java.util.HashSet;
import java.util.Set;
public class Employee {
private Long employeeId;
private String firstname;
private String lastname;
private Set<Meeting> meetings = new HashSet<Meeting>();
public Employee() {
}
public Employee(String firstname, String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
// Getter and Setter methods
}
Code language: Java (java)
File: Meeting.java
package net.viralpatel.hibernate;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
public class Meeting {
private Long meetingId;
private String subject;
private Date meetingDate;
private Set<Employee> employees = new HashSet<Employee>();
public Meeting(String subject) {
this.subject = subject;
this.meetingDate = new Date();
}
// Getter and Setter methods
}
Code language: Java (java)
To access Hibernate API, we will create a wrapper utility class which provides us with SessionFactory.
File: HibernateUtil.java
package net.viralpatel.hibernate;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
private static final SessionFactory sessionFactory = buildSessionFactory();
private static SessionFactory buildSessionFactory() {
try {
// Create the SessionFactory from hibernate.cfg.xml
return new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}
Code language: Java (java)
Following are the hibernate mapping files for each enitity Employee and Department.
File: Employee.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="net.viralpatel.hibernate">
<class name="Employee" table="EMPLOYEE">
<id name="employeeId" column="EMPLOYEE_ID">
<generator class="native" />
</id>
<property name="firstname" />
<property name="lastname" column="lastname" />
<set name="meetings" table="EMPLOYEE_MEETING"
inverse="false" lazy="true" fetch="select" cascade="all">
<key column="EMPLOYEE_ID" />
<many-to-many column="MEETING_ID" class="Meeting" />
</set>
</class>
</hibernate-mapping>
Code language: HTML, XML (xml)
File: Meeting.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="net.viralpatel.hibernate">
<class name="Meeting" table="MEETING">
<id name="meetingId" type="java.lang.Long"
column="MEETING_ID">
<generator class="native" />
</id>
<property name="subject" column="SUBJECT" />
<property name="meetingDate" type="date" column="MEETING_DATE" />
<set name="employees" table="EMPLOYEE_MEETING"
inverse="true" lazy="true" fetch="select">
<key column="EMPLOYEE_ID" />
<many-to-many column="MEETING_ID" class="Meeting" />
</set>
</class>
</hibernate-mapping>
Code language: HTML, XML (xml)
One thing is worth noting here is that we have mentioned keyword inverse=”true” in Meeting
class which makes Employee
as relationship owner. Thus Employee model takes care of updating referential keys in dependent models.
Add following hibernate.cfg.xml file in your project.
File: hibernate.cfg.xml
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/tutorial</property>
<property name="connection.username">root</property>
<property name="connection.password"></property>
<property name="connection.pool_size">1</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="current_session_context_class">thread</property>
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<property name="show_sql">true</property>
<property name="hbm2ddl.auto">validate</property>
<mapping resource="net/viralpatel/hibernate/Employee.hbm.xml"/>
<mapping resource="net/viralpatel/hibernate/Meeting.hbm.xml"/>
</session-factory>
</hibernate-configuration>
Code language: HTML, XML (xml)
Note that we have used SET to map meetings with employee and vice versa. A <set> is similar to except that it can only store unique objects. That means no duplicate elements can be contained in a set. When you add the same element to a set for second time, it will replace the old one. A set is unordered by default but we can ask it to be sorted. The corresponding type of a <set> in Java is java.util.Set
.
File: Main.java
package net.viralpatel.hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
public class Main {
public static void main(String[] args) {
SessionFactory sf = HibernateUtil.getSessionFactory();
Session session = sf.openSession();
session.beginTransaction();
Meeting meeting1 = new Meeting("Quaterly Sales meeting");
Meeting meeting2 = new Meeting("Weekly Status meeting");
Employee employee1 = new Employee("Sergey", "Brin");
Employee employee2 = new Employee("Larry", "Page");
employee1.getMeetings().add(meeting1);
employee1.getMeetings().add(meeting2);
employee2.getMeetings().add(meeting1);
session.save(employee1);
session.save(employee2);
session.getTransaction().commit();
session.close();
}
}
Code language: Java (java)
Output:
Hibernate: insert into EMPLOYEE (firstname, lastname) values (?, ?)
Hibernate: insert into MEETING (SUBJECT, MEETING_DATE) values (?, ?)
Hibernate: insert into MEETING (SUBJECT, MEETING_DATE) values (?, ?)
Hibernate: insert into EMPLOYEE (firstname, lastname) values (?, ?)
Hibernate: insert into EMPLOYEE_MEETING (EMPLOYEE_ID, MEETING_ID) values (?, ?)
Hibernate: insert into EMPLOYEE_MEETING (EMPLOYEE_ID, MEETING_ID) values (?, ?)
Hibernate: insert into EMPLOYEE_MEETING (EMPLOYEE_ID, MEETING_ID) values (?, ?)
Code language: SQL (Structured Query Language) (sql)
Hibernate-many-to-many-set-xml.zip (9 KB)
Java URL Encoder/Decoder Example - In this tutorial we will see how to URL encode/decode…
Show Multiple Examples in OpenAPI - OpenAPI (aka Swagger) Specifications has become a defecto standard…
Local WordPress using Docker - Running a local WordPress development environment is crucial for testing…
1. JWT Token Overview JSON Web Token (JWT) is an open standard defines a compact…
GraphQL Subscription provides a great way of building real-time API. In this tutorial we will…
1. Overview Spring Boot Webflux DynamoDB Integration tests - In this tutorial we will see…
View Comments
Hi there! I mapped my tables based on above. They works I can load or save. But I can't delete. When i tried to say something like this:
Employee e = (Employee) session.load(Employee.class,1);
session.delete(e);
I get the SEVERE: Cannot delete or update a parent row: a foreign key constraint fails error.
I tried to implements PreDeleteEventListener and delete the child(Employee_Metting) before delete a Employee row, but another exception is throwed - the Employee_Metting is not mapped. Must I map this table? Or could I do this without it?
Hi, i got this error, while executing the above example, can you please help me.
[code language="java"]
75 [main] INFO org.hibernate.cfg.Configuration - Configuration resource: /hibernate.cfg.xml
org.hibernate.HibernateException: Missing table: EMPLOYEE_MEETING
at org.hibernate.cfg.Configuration.validateSchema(Configuration.java:1127)
Caused by: org.hibernate.HibernateException: Missing table: EMPLOYEE_MEETING
[/code]
here my doubt is : whether we have to create table "EMPLOYEE_MEETING" or hibernate will create it. i want clarity in it. thanks for ur reply
Hi,
In this case, table Employee_Meeting is not created by Hibernate. You have to explicitly do this in your database. This is because in our hibernate.cfg.xml file, we have define value for key hibernate.hbm2ddl.auto to validate. Change it to create-drop or create to let Hibernate create schema for you. Read this article for more details.
Hope this helps.
Nice article Viral...
Keep on posting in simplified manner. It really help the beginers to understand actual scenarions... Thanks ...
Hi,
after running this example the employee-meeting containg only three rows of values.but it should contain the four rows of values.why because the employee and meeting tables conatins 2 rows of values in each table.can you clarify
Hi Suresh,
Please see the main() method. Employee1 is associated with two meeting object and Employee2 is associated with one meeting object so total number of associations is 3. This is the reason why we have 3 records in Employee_Meeting table
Hi Viral,
Cascade="all" is meaningless in the above case as in many to many association we can have multiple parents and an instance of object can't be deleted if one parent is deleted.It should be "save-update"
Nice article. Simple to understand.
How to make the Meeting class as owner in this case?
Hi,
I think your mapping in the Meeting.hbm.xml is wrong. It should be:
Thanks for the good tutorial. After having executed the example, I manually inserted another record into the employee table, and than updated the code in the main method with the following code (I was trying to read the many to many relationship records).
List empList = session.createQuery("from Employee").list();
if (empList!=null && empList.size()>0) {
for (Employee employee: empList) {
System.out.println("\n employee.getLastname(): " + employee.getLastname());
Set empMeetings = employee.getMeetings();
if (empMeetings!=null) {
if (empMeetings.size()==0) {
System.out.println("meeting subject for " + employee.getLastname() + " is 0");
} else {
Iterator mIterator = empMeetings.iterator();
while (mIterator.hasNext()) {
Meeting m = mIterator.next();
System.out.println("meeting subject for " + employee.getLastname() + ": " + m.getSubject());
}
}
} else {
System.out.println("meeting subject for " + employee.getLastname() + " is null");
}
}
}
The output was as the following. From the output, we can find out that 4 queries were executed to get all employees with the meetings.
emp.getLastname(): Brin
Hibernate: select meetings0_.EMPLOYEE_ID as EMPLOYEE1_1_, meetings0_.MEETING_ID as MEETING2_1_, meeting1_.MEETING_ID as MEETING1_2_0_, meeting1_.SUBJECT as SUBJECT2_0_, meeting1_.MEETING_DATE as MEETING3_2_0_ from EMPLOYEE_MEETING meetings0_ left outer join MEETING meeting1_ on meetings0_.MEETING_ID=meeting1_.MEETING_ID where meetings0_.EMPLOYEE_ID=?
meeting subject for Brin: Weekly Status meeting
meeting subject for Brin: Quaterly Sales meeting
emp.getLastname(): Page
Hibernate: select meetings0_.EMPLOYEE_ID as EMPLOYEE1_1_, meetings0_.MEETING_ID as MEETING2_1_, meeting1_.MEETING_ID as MEETING1_2_0_, meeting1_.SUBJECT as SUBJECT2_0_, meeting1_.MEETING_DATE as MEETING3_2_0_ from EMPLOYEE_MEETING meetings0_ left outer join MEETING meeting1_ on meetings0_.MEETING_ID=meeting1_.MEETING_ID where meetings0_.EMPLOYEE_ID=?
meeting subject for Page: Quaterly Sales meeting
emp.getLastname(): last_name1
Hibernate: select meetings0_.EMPLOYEE_ID as EMPLOYEE1_1_, meetings0_.MEETING_ID as MEETING2_1_, meeting1_.MEETING_ID as MEETING1_2_0_, meeting1_.SUBJECT as SUBJECT2_0_, meeting1_.MEETING_DATE as MEETING3_2_0_ from EMPLOYEE_MEETING meetings0_ left outer join MEETING meeting1_ on meetings0_.MEETING_ID=meeting1_.MEETING_ID where meetings0_.EMPLOYEE_ID=?
meeting subject for last_name1 is 0
Is it possible to get all employees with the meetings by executing a single query? If so, can you point out the direction about how to do it?
Thank you!
http://stackoverflow.com/questions/15560856/nhibernate-bidirectional-many-to-many-mapping-list-bag
But I would like to give you a hint. Try to change the approach and introduce the intermediate object. See the NHibernate documentation: Chapter 24. Best Practices. Quick summary:
Don't use exotic association mappings.
Good usecases for a real many-to-many associations are rare. Most of the time you need additional information stored in the "link table". In this case, it is much better to use two one-to-many associations to an intermediate link class. In fact, we think that most associations are one-to-many and many-to-one, you should be careful when using any other association style and ask yourself if it is really neccessary.
And I would say, that this is (could be) the case, because you really need the Order ...