Monday, 26 December 2022

Hibernate 6: bidirectional one to one mapping

In a one-to-one relationship, each row in one database table is linked to 1 and only 1 other row in another table.

 

For example, there is one to one relationship between employee and employee_details.

 

Types of One to One association

There are two types of one to one association.

a.   Unidirectional one to one association

b.   Bidirectional one to one association

 

Unidirectional one to one association

@Entity
@Table(name = "employee_details")
public class EmployeeDetails {

	@Id
	private int id;

	private String departmentName;

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

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

	private String name;

	@OneToOne
	@JoinColumn(name = "employee_details_id")
	private EmployeeDetails employeeDetails;

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

 

Above snippet generates below DDL.

create table employee (
       id integer not null,
        name varchar(255),
        employee_details_id integer,
        primary key (id)
    )

    create table employee_details (
       id integer not null,
        departmentName varchar(255),
        primary key (id)
    )

     alter table if exists employee 
       add constraint FKsg6w2kdo2j1t3wt0vxta131it 
       foreign key (employee_details_id) 
       references employee_details

 

In one to one association, employee is considered as client side and employee_details is considered as parent side. But a much more natural mapping would be employee from parent side and pushing the foreign key to employee_details side. This can be achieved using bidirectional one to one association.

 

Bidirectional one to one association

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

	private String name;

	@OneToOne(mappedBy = "emp", 
			cascade = CascadeType.ALL, 
			orphanRemoval = true, 
			fetch = FetchType.LAZY)
	private EmployeeDetails employeeDetails;

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

@Entity
@Table(name = "employee_details")
public class EmployeeDetails {

	@Id
	private int id;

	private String departmentName;

	@OneToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "emp_id")
	private Employee emp;

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

 

Above snippet generates below DDL.

    create table employee (
       id integer not null,
        name varchar(255),
        primary key (id)
    )

    create table employee_details (
       id integer not null,
        departmentName varchar(255),
        emp_id integer,
        primary key (id)
    )

    alter table if exists employee_details 
       add constraint FKjwhhv402ja2w9ioxfbmbouv 
       foreign key (emp_id) 
       references employee

 

Find the below working application.

 

Step 1: Create new maven project ‘hibernate-bi-directional-one-to-one’.

 

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-one</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.

 

EmployeeDetails.java

 

package com.sample.app.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;

@Entity
@Table(name = "employee_details")
public class EmployeeDetails {

    @Id
    private int id;

    private String departmentName;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "emp_id")
    private Employee emp;

    public EmployeeDetails() {
    }

    public EmployeeDetails(int id, String departmentName, Employee emp) {
        this.id = id;
        this.departmentName = departmentName;
        this.emp = emp;
    }

    public int getId() {
        return id;
    }

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

    public String getDepartmentName() {
        return departmentName;
    }

    public void setDepartmentName(String departmentName) {
        this.departmentName = departmentName;
    }

    public Employee getEmp() {
        return emp;
    }

    public void setEmp(Employee emp) {
        this.emp = emp;
    }

    @Override
    public String toString() {
        return "EmployeeDetails [id=" + id + ", departmentName=" + departmentName + "]";
    }

}

 

Employee.java

package com.sample.app.entity;

import org.hibernate.annotations.LazyToOne;
import org.hibernate.annotations.LazyToOneOption;

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

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

    private String name;

    @OneToOne(mappedBy = "emp", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
    @LazyToOne(LazyToOneOption.NO_PROXY)
    private EmployeeDetails employeeDetails;

    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 EmployeeDetails getEmployeeDetails() {
        return employeeDetails;
    }

    public void setEmployeeDetails(EmployeeDetails employeeDetails) {
        this.employeeDetails = employeeDetails;
    }

    @Override
    public String toString() {
        return "Employee [id=" + id + ", name=" + name + ", employeeDetails=" + employeeDetails + "]";
    }

}

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

	</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.EmployeeDetails;

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

            Employee emp = new Employee(1, "Krishna");

            session.beginTransaction();
            session.persist(emp);
            session.flush();
            session.getTransaction().commit();

            session.beginTransaction();
            Employee persistedEmployee = session.find(Employee.class, 1);

            EmployeeDetails empDetails1 = new EmployeeDetails(123, "Aerospace", persistedEmployee);
            persistedEmployee.setEmployeeDetails(empDetails1);
            session.persist(empDetails1);
            session.flush();
            session.getTransaction().commit();

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

        }

    }
}

 

Total project structure looks like below.

 


 

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

 

Sep 11, 2022 4:24:03 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate ORM core version 6.1.2.Final
Sep 11, 2022 4:24:03 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using built-in connection pool (not intended for production use)
Sep 11, 2022 4:24:03 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: Loaded JDBC driver class: org.postgresql.Driver
Sep 11, 2022 4:24:03 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001012: Connecting with JDBC URL [jdbc:postgresql://127.0.0.1:5432/test]
Sep 11, 2022 4:24:03 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {password=****, user=postgres}
Sep 11, 2022 4:24:03 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Sep 11, 2022 4:24:03 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections <init>
INFO: HHH10001115: Connection pool size: 20 (min=1)
Sep 11, 2022 4:24:03 PM org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl logSelectedDialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.PostgreSQLDialect
Hibernate: 
    
    alter table if exists employee_details 
       drop constraint if exists FKjwhhv402ja2w9ioxfbmbouv
Sep 11, 2022 4:24:04 PM org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@439e3cb4] 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 11, 2022 4:24:04 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Sep 11, 2022 4:24:04 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: relation "employee_details" does not exist, skipping
Hibernate: 
    
    drop table if exists employee cascade
Sep 11, 2022 4:24:04 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Sep 11, 2022 4:24:04 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: table "employee" does not exist, skipping
Hibernate: 
    
    drop table if exists employee_details cascade
Sep 11, 2022 4:24:04 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Sep 11, 2022 4:24:04 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: table "employee_details" does not exist, skipping
Hibernate: 
    
    create table employee (
       id integer not null,
        name varchar(255),
        primary key (id)
    )
Sep 11, 2022 4:24:04 PM org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@521bb1a4] 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_details (
       id integer not null,
        departmentName varchar(255),
        emp_id integer,
        primary key (id)
    )
Hibernate: 
    
    alter table if exists employee_details 
       add constraint FKjwhhv402ja2w9ioxfbmbouv 
       foreign key (emp_id) 
       references employee
Sep 11, 2022 4:24:04 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
        employee_details
        (departmentName, emp_id, id) 
    values
        (?, ?, ?)
Hibernate: 
    select
        e1_0.id,
        e1_0.name 
    from
        employee e1_0
Hibernate: 
    select
        e1_0.id,
        e1_0.departmentName,
        e1_0.emp_id 
    from
        employee_details e1_0 
    where
        e1_0.emp_id=?
Employee [id=1, name=Krishna, employeeDetails=EmployeeDetails [id=123, departmentName=Aerospace]]

 

Query database to confirm the 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_details" CONSTRAINT "fkjwhhv402ja2w9ioxfbmbouv" FOREIGN KEY (emp_id) REFERENCES employee(id)
Access method: heap

test=# 
test=# 
test=# 
test=# 
test=# \d+ employee_details;
                                                Table "public.employee_details"
     Column     |          Type          | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
----------------+------------------------+-----------+----------+---------+----------+-------------+--------------+-------------
 id             | integer                |           | not null |         | plain    |             |              | 
 departmentname | character varying(255) |           |          |         | extended |             |              | 
 emp_id         | integer                |           |          |         | plain    |             |              | 
Indexes:
    "employee_details_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
    "fkjwhhv402ja2w9ioxfbmbouv" FOREIGN KEY (emp_id) REFERENCES employee(id)
Access method: heap

 

Query tables to confirm the data.

test=# SELECT * FROM employee;
 id |  name   
----+---------
  1 | Krishna
(1 row)

test=# 
test=# 
test=# SELECT * FROM employee_details;
 id  | departmentname | emp_id 
-----+----------------+--------
 123 | Aerospace      |      1
(1 row)

 

You can download the application from this link.


 

Previous                                                    Next                                                    Home

No comments:

Post a Comment