Tuesday 27 December 2022

Hibernate 6: bidirectional many to many association

@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.



 

Previous                                                    Next                                                    Home

No comments:

Post a Comment