Monday, 26 December 2022

Hibernate 6: Bidirectional One to Many association

One to Many association links a parent entity with one or more child entities.

 

For example, an employee has one or more bank accounts.

 

There are two types of One to Many associations.

a.   Unidirectional one to many association: It do not have mirroring @ManyToOne (many to one) association on child entity.

b.   Bidirectional one to many association: It has mirroring @ManyToOne (many to one) association on child entity.

 

Bidirectional one to many association example

@Entity
@Table(name = "bank_account_details")
public class BankAccount {

	@Id
	private int id;

	private String accountNumber;

	private String branch;

	private String ifscCode;

	private String address;

	@ManyToOne
	@JoinColumn(name = "employee_id", foreignKey = @ForeignKey(name = "bank_acc_emp_id_fk"))
	private Employee employee;

	...........
	...........
}

@Entity
@Table(name = "employees")
public class Employee {
	@Id
	private int id;

	private String firstName;

	private String lastName;

	@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "employee")
	private Set<BankAccount> bankAccounts = new HashSet<>();

	public void addBankAccount(BankAccount bankAccount) {
		bankAccounts.add(bankAccount);
		bankAccount.setEmployee(this);
	}

	public void removeBankAccount(BankAccount bankAccount) {
		bankAccounts.remove(bankAccount);
		bankAccount.setEmployee(this);
	}

	.............
	.............
}

 

Above snippet generates following DDL definition.

    create table bank_account_details (
       id integer not null,
        accountNumber varchar(255),
        address varchar(255),
        branch varchar(255),
        ifscCode varchar(255),
        employee_id integer,
        primary key (id)
    )

    create table employees (
       id integer not null,
        firstName varchar(255),
        lastName varchar(255),
        primary key (id)
    )
    
    alter table if exists bank_account_details 
       add constraint bank_acc_emp_id_fk 
       foreign key (employee_id) 
       references employees

addBankAccount() and removeBankAccount() methods are utility methods that synchronize both ends whenever a child element is added or removed.

 

Find the below working application.

 

Step 1: Create new maven project ‘hibernate-bi-directional-one-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-one-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.

 

BankAccount.java

package com.sample.app.entity;

import java.util.Objects;

import jakarta.persistence.Entity;
import jakarta.persistence.*;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;

@Entity
@Table(name = "bank_account_details")
public class BankAccount {

	@Id
	private int id;

	private String accountNumber;

	private String branch;

	private String ifscCode;

	private String address;

	@ManyToOne
	@JoinColumn(name = "employee_id", foreignKey = @ForeignKey(name = "bank_acc_emp_id_fk"))
	private Employee employee;

	public String getAccountNumber() {
		return accountNumber;
	}

	public void setAccountNumber(String accountNumber) {
		this.accountNumber = accountNumber;
	}

	public String getBranch() {
		return branch;
	}

	public void setBranch(String branch) {
		this.branch = branch;
	}

	public String getIfscCode() {
		return ifscCode;
	}

	public void setIfscCode(String ifscCode) {
		this.ifscCode = ifscCode;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public Employee getEmployee() {
		return employee;
	}

	public void setEmployee(Employee employee) {
		this.employee = employee;
	}

	@Override
	public String toString() {
		return "BankAccount [id=" + id + ", accountNumber=" + accountNumber + ", branch=" + branch + ", ifscCode="
				+ ifscCode + ", address=" + address + "]";
	}

	@Override
	public int hashCode() {
		return Objects.hash(id);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		BankAccount other = (BankAccount) obj;
		return id == other.id;
	}

}

Employee.java

package com.sample.app.entity;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;

@Entity
@Table(name = "employees")
public class Employee {
	@Id
	private int id;

	private String firstName;

	private String lastName;

	@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "employee")
	private Set<BankAccount> bankAccounts = new HashSet<>();

	public void addBankAccount(BankAccount bankAccount) {
		bankAccounts.add(bankAccount);
		bankAccount.setEmployee(this);
	}

	public void removeBankAccount(BankAccount bankAccount) {
		bankAccounts.remove(bankAccount);
		bankAccount.setEmployee(this);
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public Set<BankAccount> getBankAccounts() {
		return bankAccounts;
	}

	public void setBankAccounts(Set<BankAccount> bankAccounts) {
		this.bankAccounts = bankAccounts;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", bankAccounts="
				+ bankAccounts + "]";
	}

	@Override
	public int hashCode() {
		return Objects.hash(id);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Employee other = (Employee) obj;
		return id == other.id;
	}

}

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.BankAccount" />

	</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.BankAccount;
import com.sample.app.entity.Employee;

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();

			BankAccount account1 = new BankAccount();
			account1.setAccountNumber("number1");
			account1.setAddress("address1");
			account1.setBranch("branch1");
			account1.setId(1);
			account1.setIfscCode("ifsc1");

			BankAccount account2 = new BankAccount();
			account2.setAccountNumber("number2");
			account2.setAddress("address2");
			account2.setBranch("branch2");
			account2.setId(2);
			account2.setIfscCode("ifsc2");

			session.persist(account1);
			session.persist(account2);

			session.getTransaction().commit();

			session.beginTransaction();

			Employee emp1 = new Employee();
			emp1.setFirstName("Krishna");
			emp1.setLastName("Gurram");
			emp1.setId(1);

			BankAccount persistedAccount1 = session.find(BankAccount.class, 1);
			BankAccount persistedAccount2 = session.find(BankAccount.class, 2);
			emp1.addBankAccount(persistedAccount1);
			emp1.addBankAccount(persistedAccount2);

			session.persist(emp1);
			session.flush();
			session.getTransaction().commit();

			List<Employee> emps = loadAllData(Employee.class, session);
			for (Employee empTemp : emps) {
				System.out.println(empTemp);
			}

		}

	}
}

Total project structure looks like below.


 

Run App.java, you will see below messages in the console.

Sep 06, 2022 12:41:49 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate ORM core version 6.1.2.Final
Sep 06, 2022 12:41:49 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using built-in connection pool (not intended for production use)
Sep 06, 2022 12:41:49 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: Loaded JDBC driver class: org.postgresql.Driver
Sep 06, 2022 12:41:49 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001012: Connecting with JDBC URL [jdbc:postgresql://127.0.0.1:5432/test]
Sep 06, 2022 12:41:49 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {password=****, user=postgres}
Sep 06, 2022 12:41:49 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Sep 06, 2022 12:41:49 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections <init>
INFO: HHH10001115: Connection pool size: 20 (min=1)
Sep 06, 2022 12:41:49 PM org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl logSelectedDialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.PostgreSQLDialect
Hibernate: 
    
    alter table if exists bank_account_details 
       drop constraint if exists bank_acc_emp_id_fk
Sep 06, 2022 12:41:50 PM org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@3f018494] 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 06, 2022 12:41:50 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Sep 06, 2022 12:41:50 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: relation "bank_account_details" does not exist, skipping
Hibernate: 
    
    drop table if exists bank_account_details cascade
Sep 06, 2022 12:41:50 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Sep 06, 2022 12:41:50 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: table "bank_account_details" does not exist, skipping
Hibernate: 
    
    drop table if exists employees cascade
Sep 06, 2022 12:41:50 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Sep 06, 2022 12:41:50 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: table "employees" does not exist, skipping
Hibernate: 
    
    create table bank_account_details (
       id integer not null,
        accountNumber varchar(255),
        address varchar(255),
        branch varchar(255),
        ifscCode varchar(255),
        employee_id integer,
        primary key (id)
    )
Sep 06, 2022 12:41:50 PM org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@17410c07] 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 employees (
       id integer not null,
        firstName varchar(255),
        lastName varchar(255),
        primary key (id)
    )
Hibernate: 
    
    alter table if exists bank_account_details 
       add constraint bank_acc_emp_id_fk 
       foreign key (employee_id) 
       references employees
Sep 06, 2022 12:41:50 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
        bank_account_details
        (accountNumber, address, branch, employee_id, ifscCode, id) 
    values
        (?, ?, ?, ?, ?, ?)
Hibernate: 
    insert 
    into
        bank_account_details
        (accountNumber, address, branch, employee_id, ifscCode, id) 
    values
        (?, ?, ?, ?, ?, ?)
Hibernate: 
    insert 
    into
        employees
        (firstName, lastName, id) 
    values
        (?, ?, ?)
Hibernate: 
    update
        bank_account_details 
    set
        accountNumber=?,
        address=?,
        branch=?,
        employee_id=?,
        ifscCode=? 
    where
        id=?
Hibernate: 
    update
        bank_account_details 
    set
        accountNumber=?,
        address=?,
        branch=?,
        employee_id=?,
        ifscCode=? 
    where
        id=?
Hibernate: 
    select
        e1_0.id,
        e1_0.firstName,
        e1_0.lastName 
    from
        employees e1_0
Employee [id=1, firstName=Krishna, lastName=Gurram, bankAccounts=[BankAccount [id=1, accountNumber=number1, branch=branch1, ifscCode=ifsc1, address=address1], BankAccount [id=2, accountNumber=number2, branch=branch2, ifscCode=ifsc2, address=address2]]]

Query the database to get DDL.

test=# \d+ employees;
                                                 Table "public.employees"
  Column   |          Type          | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
-----------+------------------------+-----------+----------+---------+----------+-------------+--------------+-------------
 id        | integer                |           | not null |         | plain    |             |              | 
 firstname | character varying(255) |           |          |         | extended |             |              | 
 lastname  | character varying(255) |           |          |         | extended |             |              | 
Indexes:
    "employees_pkey" PRIMARY KEY, btree (id)
Referenced by:
    TABLE "bank_account_details" CONSTRAINT "bank_acc_emp_id_fk" FOREIGN KEY (employee_id) REFERENCES employees(id)
Access method: heap

test=# 
test=# 
test=# 
test=# \d+ bank_account_details
                                              Table "public.bank_account_details"
    Column     |          Type          | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
---------------+------------------------+-----------+----------+---------+----------+-------------+--------------+-------------
 id            | integer                |           | not null |         | plain    |             |              | 
 accountnumber | character varying(255) |           |          |         | extended |             |              | 
 address       | character varying(255) |           |          |         | extended |             |              | 
 branch        | character varying(255) |           |          |         | extended |             |              | 
 ifsccode      | character varying(255) |           |          |         | extended |             |              | 
 employee_id   | integer                |           |          |         | plain    |             |              | 
Indexes:
    "bank_account_details_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
    "bank_acc_emp_id_fk" FOREIGN KEY (employee_id) REFERENCES employees(id)
Access method: heap

Query tables to see the data.

test=# SELECT * FROM employees;
 id | firstname | lastname 
----+-----------+----------
  1 | Krishna   | Gurram
(1 row)

test=# 
test=# 
test=# SELECT * FROM bank_account_details;
 id | accountnumber | address  | branch  | ifsccode | employee_id 
----+---------------+----------+---------+----------+-------------
  1 | number1       | address1 | branch1 | ifsc1    |           1
  2 | number2       | address2 | branch2 | ifsc2    |           1
(2 rows)

You can download this application from this link.



  

Previous                                                    Next                                                    Home

No comments:

Post a Comment