@ManyToMany association maintain a link table to join two entities. Like the @OneToMany, @OneToOne associations, @ManyToMany can be either unidirectional or bidirectional.
Unidirectional @ManyToMany association
@Entity
@Table(name = "project")
public class Project {
@Id
private int projectId;
private String projectName;
...........
...........
}
@Entity
@Table(name = "employee")
public class Employee {
@Id
private int id;
private String name;
@ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private List<Project> projects = new ArrayList<>();
..........
...........
}
Above snippet generates following DDL.
create table employee (
id integer not null,
name varchar(255),
primary key (id)
)
create table employee_project (
Employee_id integer not null,
projects_projectId integer not null
)
create table project (
projectId integer not null,
projectName varchar(255),
primary key (projectId)
)
alter table if exists employee_project
add constraint FK84if10lpuqoq270kixummx39k
foreign key (projects_projectId)
references project
alter table if exists employee_project
add constraint FKcfge3ley7bbpogw8dur1p5q35
foreign key (Employee_id)
references employee
Bidirectional @ManyToMany association
A bidirectional @ManyToMany association has an owning and a mappedBy side.
@Entity
@Table(name = "employee")
public class Employee {
@Id
private int id;
private String name;
@ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private List<Project> projects = new ArrayList<>();
public void addProject(Project project) {
projects.add(project);
project.getEmployees().add(this);
}
public void removeAddress(Project project) {
projects.remove(project);
project.getEmployees().remove(this);
}
}
@Entity
@Table(name = "project")
public class Project {
@Id
private int projectId;
private String projectName;
@ManyToMany(mappedBy = "projects")
private List<Employee> employees = new ArrayList<>();
}
Above entities generate below DDL.
create table employee (
id integer not null,
name varchar(255),
primary key (id)
)
create table employee_project (
employees_id integer not null,
projects_projectId integer not null
)
create table project (
projectId integer not null,
projectName varchar(255),
primary key (projectId)
)
alter table if exists employee_project
add constraint FK84if10lpuqoq270kixummx39k
foreign key (projects_projectId)
references project
alter table if exists employee_project
add constraint FK79jew72qqettaqpet0s7h9rda
foreign key (employees_id)
references employee
To preserve synchronicity between both sides, it’s good practice to provide helper methods (addProject, removeProject) for adding or removing child entities.
Find the below working application.
Step 1: Create new maven project ‘hibernate-bi-directional-many-to-many’.
Step 2: Update pom.xml with maven dependencies.
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sample.app</groupId>
<artifactId>hibernate-bi-directional-many-to-many</artifactId>
<version>1</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>15</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.4.1</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.1.2.Final</version>
</dependency>
</dependencies>
</project>
Step 3: Define entity classes.
Project.java
package com.sample.app.entity;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
@Entity
@Table(name = "project")
public class Project {
@Id
private int projectId;
private String projectName;
public Project() {
}
public Project(int projectId, String projectName) {
this.projectId = projectId;
this.projectName = projectName;
}
@ManyToMany(mappedBy = "projects")
private List<Employee> employees = new ArrayList<>();
public int getProjectId() {
return projectId;
}
public void setProjectId(int projectId) {
this.projectId = projectId;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public List<Employee> getEmployees() {
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
@Override
public int hashCode() {
return Objects.hash(projectId);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Project other = (Project) obj;
return projectId == other.projectId;
}
@Override
public String toString() {
return "Project [projectId=" + projectId + ", projectName=" + projectName + "]";
}
}
Employee.java
package com.sample.app.entity;
import java.util.ArrayList;
import java.util.List;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
@Entity
@Table(name = "employee")
public class Employee {
@Id
private int id;
private String name;
@ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private List<Project> projects = new ArrayList<>();
public Employee() {
}
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Project> getProjects() {
return projects;
}
public void setProjects(List<Project> projects) {
this.projects = projects;
}
public void addProject(Project project) {
projects.add(project);
project.getEmployees().add(this);
}
public void removeProject(Project project) {
projects.remove(project);
project.getEmployees().remove(this);
}
@Override
public String toString() {
return "Employee [id=" + id + ", name=" + name + "]";
}
}
Step 4: Create hibernate.cfg.xml file under src/main/resources folder.
hibernate.cfg.xml
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database Connection settings -->
<property name="connection.driver_class">org.postgresql.Driver</property>
<property name="connection.url">jdbc:postgresql://127.0.0.1:5432/test</property>
<property name="connection.username">postgres</property>
<property name="connection.password">postgres</property>
<!-- Enable the logging of all the generated SQL statements to the console -->
<property name="show_sql">true</property>
<!-- Format the generated SQL statement to make it more readable, -->
<property name="format_sql">true</property>
<!-- Hibernate will put comments inside all generated SQL statements to
hint what’s the generated SQL trying to do -->
<property name="use_sql_comments">false</property>
<!-- This property makes Hibernate generate the appropriate SQL for the
chosen database. -->
<property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create</property>
<!-- mappings for annotated classes -->
<mapping class="com.sample.app.entity.Employee" />
<mapping class="com.sample.app.entity.Project" />
</session-factory>
</hibernate-configuration>
Step 5: Define main application class.
App.java
package com.sample.app;
import java.util.List;
import org.hibernate.Session;
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.sample.app.entity.Employee;
import com.sample.app.entity.Project;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
public class App {
private static SessionFactory sessionFactory = buildSessionFactory();
private static <T> List<T> loadAllData(Class<T> clazz, Session session) {
final CriteriaBuilder builder = session.getCriteriaBuilder();
final CriteriaQuery<T> criteria = builder.createQuery(clazz);
criteria.from(clazz);
return session.createQuery(criteria).getResultList();
}
private static SessionFactory buildSessionFactory() {
try {
if (sessionFactory == null) {
StandardServiceRegistry standardRegistry = new StandardServiceRegistryBuilder()
.configure("hibernate.cfg.xml").build();
Metadata metaData = new MetadataSources(standardRegistry).getMetadataBuilder().build();
sessionFactory = metaData.getSessionFactoryBuilder().build();
}
return sessionFactory;
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
public static void main(String args[]) {
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
Employee emp = new Employee(1, "Krishna");
emp.addProject(new Project(11, "Chat server"));
emp.addProject(new Project(12, "Intelligent Sync"));
session.persist(emp);
session.flush();
session.getTransaction().commit();
List<Employee> employees = loadAllData(Employee.class, session);
for (Employee emp1 : employees) {
System.out.println(emp1);
List<Project> projects = emp.getProjects();
for(Project pjt: projects) {
System.out.println(pjt);
}
}
}
}
}
Total project structure looks like below.
Run App.java, you will see below messages in the console.
Sep 12, 2022 1:13:08 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate ORM core version 6.1.2.Final
Sep 12, 2022 1:13:08 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using built-in connection pool (not intended for production use)
Sep 12, 2022 1:13:08 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: Loaded JDBC driver class: org.postgresql.Driver
Sep 12, 2022 1:13:08 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001012: Connecting with JDBC URL [jdbc:postgresql://127.0.0.1:5432/test]
Sep 12, 2022 1:13:08 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {password=****, user=postgres}
Sep 12, 2022 1:13:08 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Sep 12, 2022 1:13:08 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections <init>
INFO: HHH10001115: Connection pool size: 20 (min=1)
Sep 12, 2022 1:13:08 PM org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl logSelectedDialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.PostgreSQLDialect
Hibernate:
alter table if exists employee_project
drop constraint if exists FK84if10lpuqoq270kixummx39k
Sep 12, 2022 1:13:09 PM org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@77c10a5f] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
Sep 12, 2022 1:13:09 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Sep 12, 2022 1:13:09 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: relation "employee_project" does not exist, skipping
Hibernate:
alter table if exists employee_project
drop constraint if exists FK79jew72qqettaqpet0s7h9rda
Sep 12, 2022 1:13:09 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Sep 12, 2022 1:13:09 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: relation "employee_project" does not exist, skipping
Hibernate:
drop table if exists employee cascade
Sep 12, 2022 1:13:09 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Sep 12, 2022 1:13:09 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: table "employee" does not exist, skipping
Hibernate:
drop table if exists employee_project cascade
Sep 12, 2022 1:13:09 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Sep 12, 2022 1:13:09 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: table "employee_project" does not exist, skipping
Hibernate:
drop table if exists project cascade
Sep 12, 2022 1:13:09 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Sep 12, 2022 1:13:09 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: table "project" does not exist, skipping
Hibernate:
create table employee (
id integer not null,
name varchar(255),
primary key (id)
)
Sep 12, 2022 1:13:09 PM org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@727320fa] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
Hibernate:
create table employee_project (
employees_id integer not null,
projects_projectId integer not null
)
Hibernate:
create table project (
projectId integer not null,
projectName varchar(255),
primary key (projectId)
)
Hibernate:
alter table if exists employee_project
add constraint FK84if10lpuqoq270kixummx39k
foreign key (projects_projectId)
references project
Hibernate:
alter table if exists employee_project
add constraint FK79jew72qqettaqpet0s7h9rda
foreign key (employees_id)
references employee
Sep 12, 2022 1:13:09 PM org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
Hibernate:
insert
into
employee
(name, id)
values
(?, ?)
Hibernate:
insert
into
project
(projectName, projectId)
values
(?, ?)
Hibernate:
insert
into
project
(projectName, projectId)
values
(?, ?)
Hibernate:
insert
into
employee_project
(employees_id, projects_projectId)
values
(?, ?)
Hibernate:
insert
into
employee_project
(employees_id, projects_projectId)
values
(?, ?)
Hibernate:
select
e1_0.id,
e1_0.name
from
employee e1_0
Employee [id=1, name=Krishna]
Project [projectId=11, projectName=Chat server]
Project [projectId=12, projectName=Intelligent Sync]
Query database to confirm DDL.
test=# \d+ employee;
Table "public.employee"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+------------------------+-----------+----------+---------+----------+-------------+--------------+-------------
id | integer | | not null | | plain | | |
name | character varying(255) | | | | extended | | |
Indexes:
"employee_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE "employee_project" CONSTRAINT "fk79jew72qqettaqpet0s7h9rda" FOREIGN KEY (employees_id) REFERENCES employee(id)
Access method: heap
test=#
test=#
test=#
test=# \d+ project
Table "public.project"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
-------------+------------------------+-----------+----------+---------+----------+-------------+--------------+-------------
projectid | integer | | not null | | plain | | |
projectname | character varying(255) | | | | extended | | |
Indexes:
"project_pkey" PRIMARY KEY, btree (projectid)
Referenced by:
TABLE "employee_project" CONSTRAINT "fk84if10lpuqoq270kixummx39k" FOREIGN KEY (projects_projectid) REFERENCES project(projectid)
Access method: heap
test=#
test=#
test=#
test=#
test=# \d+ employee_project
Table "public.employee_project"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------------------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
employees_id | integer | | not null | | plain | | |
projects_projectid | integer | | not null | | plain | | |
Foreign-key constraints:
"fk79jew72qqettaqpet0s7h9rda" FOREIGN KEY (employees_id) REFERENCES employee(id)
"fk84if10lpuqoq270kixummx39k" FOREIGN KEY (projects_projectid) REFERENCES project(projectid)
Access method: heap
Query tables to confirm the data.
test=# SELECT * FROM employee;
id | name
----+---------
1 | Krishna
(1 row)
test=#
test=#
test=#
test=# SELECT * FROM project;
projectid | projectname
-----------+------------------
11 | Chat server
12 | Intelligent Sync
(2 rows)
test=#
test=#
test=# SELECT * FROM employee_project;
employees_id | projects_projectid
--------------+--------------------
1 | 11
1 | 12
(2 rows)
You can download this application from this link.
No comments:
Post a Comment