Saturday, 24 December 2022

Hibernate 6: Composite identifiers with associations

In this post, I am going to explain how to define a composite key using entity associations.

 

For example, a book primary key is defined with

a.   Book title

b.   Author id

c.    Publisher Id

 

Example

@Entity
@Table(name = "book")
public class Book {

	@Id
	private String title;

	@Id
	@ManyToOne(fetch = FetchType.LAZY)
	private Author author;

	@Id
	@ManyToOne(fetch = FetchType.LAZY)
	private Publisher publisher;

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

@Entity
@Table(name = "author")
public class Author {

	@Id
	private Integer authorId;

	private String name;

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


@Entity
@Table(name = "publisher")
public class Publisher {

	@Id
	private Integer publisherId;

	private String name;

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

 

Find the below working application.

 

Step 1: Create new maven project ‘hibernate-composite-id-with-associations’.

 

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-composite-id-with-associations</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.

 

Author.java

package com.sample.app.entity;

import java.util.Objects;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "author")
public class Author {

	@Id
	private Integer authorId;

	private String name;

	public Author(Integer authorId, String name) {
		this.authorId = authorId;
		this.name = name;
	}

	public Integer getAuthorId() {
		return authorId;
	}

	public void setAuthorId(Integer authorId) {
		this.authorId = authorId;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Author [empId=" + authorId + ", name=" + name + "]";
	}

	@Override
	public int hashCode() {
		return Objects.hash(authorId, name);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Author other = (Author) obj;
		return Objects.equals(authorId, other.authorId) && Objects.equals(name, other.name);
	}

}

 

Publisher.java

package com.sample.app.entity;

import java.util.Objects;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "publisher")
public class Publisher {

	@Id
	private Integer publisherId;

	private String name;

	public Publisher(Integer publisherId, String name) {
		this.publisherId = publisherId;
		this.name = name;
	}

	public Integer getPublisherId() {
		return publisherId;
	}

	public void setPublisherId(Integer publisherId) {
		this.publisherId = publisherId;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Publisher [publisherId=" + publisherId + ", name=" + name + "]";
	}

	@Override
	public int hashCode() {
		return Objects.hash(name, publisherId);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Publisher other = (Publisher) obj;
		return Objects.equals(name, other.name) && Objects.equals(publisherId, other.publisherId);
	}

}

 

Book.java

package com.sample.app.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;

@Entity
@Table(name = "book")
public class Book {

	@Id
	private String title;

	@Id
	@ManyToOne(fetch = FetchType.LAZY)
	private Author author;

	@Id
	@ManyToOne(fetch = FetchType.LAZY)
	private Publisher publisher;
	
	public Book() {}

	public Book(String title, Author author, Publisher publisher) {
		this.title = title;
		this.author = author;
		this.publisher = publisher;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public Author getAuthor() {
		return author;
	}

	public void setAuthor(Author author) {
		this.author = author;
	}

	public Publisher getPublisher() {
		return publisher;
	}

	public void setPublisher(Publisher publisher) {
		this.publisher = publisher;
	}

	@Override
	public String toString() {
		return "Book [title=" + title + "]";
	}

}

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">false</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.Author" />
		<mapping class="com.sample.app.entity.Publisher" />
		<mapping class="com.sample.app.entity.Book" />

	</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.Author;
import com.sample.app.entity.Book;
import com.sample.app.entity.Publisher;

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[]) {

		Author author1 = new Author(1, "Krishna");
		Publisher publisher1 = new Publisher(1, "blogger");

		try (Session session = sessionFactory.openSession()) {
			session.beginTransaction();
			session.persist(author1);
			session.persist(publisher1);
			session.flush();
			session.getTransaction().commit();

			Author persistedAuthor = session.find(Author.class, 1);
			Publisher persistedPublisher = session.find(Publisher.class, 1);

			session.beginTransaction();
			Book book = new Book("Programming for beginners", persistedAuthor, persistedPublisher);
			session.persist(book);
			session.flush();
			session.getTransaction().commit();

			List<Book> books = loadAllData(Book.class, session);
			for (Book book1 : books) {
				System.out.println(book1);
				System.out.println(book1.getAuthor());
				System.out.println(book1.getPublisher());
			}
		}

	}
}

Total project structure looks like below.

 


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

Sep 05, 2022 4:50:32 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate ORM core version 6.1.2.Final
Sep 05, 2022 4:50:32 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using built-in connection pool (not intended for production use)
Sep 05, 2022 4:50:32 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: Loaded JDBC driver class: org.postgresql.Driver
Sep 05, 2022 4:50:32 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001012: Connecting with JDBC URL [jdbc:postgresql://127.0.0.1:5432/test]
Sep 05, 2022 4:50:32 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {password=****, user=postgres}
Sep 05, 2022 4:50:32 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Sep 05, 2022 4:50:32 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections <init>
INFO: HHH10001115: Connection pool size: 20 (min=1)
Sep 05, 2022 4:50:32 PM org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl logSelectedDialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.PostgreSQLDialect
Sep 05, 2022 4:50:32 PM org.hibernate.mapping.RootClass checkCompositeIdentifier
WARN: HHH000038: Composite-id class does not override equals(): com.sample.app.entity.Book
Sep 05, 2022 4:50:32 PM org.hibernate.mapping.RootClass checkCompositeIdentifier
WARN: HHH000039: Composite-id class does not override hashCode(): com.sample.app.entity.Book
Sep 05, 2022 4:50:33 PM org.hibernate.metamodel.internal.EntityInstantiatorPojoStandard resolveConstructor
INFO: HHH000182: No default (no-argument) constructor for class: com.sample.app.entity.Author (class must be instantiated by Interceptor)
Sep 05, 2022 4:50:33 PM org.hibernate.metamodel.internal.EntityInstantiatorPojoStandard resolveConstructor
INFO: HHH000182: No default (no-argument) constructor for class: com.sample.app.entity.Publisher (class must be instantiated by Interceptor)
Hibernate: alter table if exists book drop constraint if exists FKphbqlkautxpp937p1gshnxcqv
Sep 05, 2022 4:50:33 PM org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@4d3c6593] 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 05, 2022 4:50:33 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Sep 05, 2022 4:50:33 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: relation "book" does not exist, skipping
Hibernate: alter table if exists book drop constraint if exists FKektypek2eiyeyfigr8kvudw9m
Sep 05, 2022 4:50:33 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Sep 05, 2022 4:50:33 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: relation "book" does not exist, skipping
Hibernate: drop table if exists author cascade
Sep 05, 2022 4:50:33 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Sep 05, 2022 4:50:33 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: table "author" does not exist, skipping
Hibernate: drop table if exists book cascade
Sep 05, 2022 4:50:33 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Sep 05, 2022 4:50:33 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: table "book" does not exist, skipping
Hibernate: drop table if exists publisher cascade
Sep 05, 2022 4:50:33 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Sep 05, 2022 4:50:33 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: table "publisher" does not exist, skipping
Hibernate: create table author (authorId integer not null, name varchar(255), primary key (authorId))
Sep 05, 2022 4:50:33 PM org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@54755dd9] 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 book (title varchar(255) not null, publisher_publisherId integer not null, author_authorId integer not null, primary key (author_authorId, publisher_publisherId, title))
Hibernate: create table publisher (publisherId integer not null, name varchar(255), primary key (publisherId))
Hibernate: alter table if exists book add constraint FKphbqlkautxpp937p1gshnxcqv foreign key (publisher_publisherId) references publisher
Hibernate: alter table if exists book add constraint FKektypek2eiyeyfigr8kvudw9m foreign key (author_authorId) references author
Sep 05, 2022 4:50:33 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 author (name, authorId) values (?, ?)
Hibernate: insert into publisher (name, publisherId) values (?, ?)
Hibernate: insert into book (author_authorId, publisher_publisherId, title) values (?, ?, ?)
Hibernate: select b1_0.author_authorId,b1_0.publisher_publisherId,b1_0.title from book b1_0
Book [title=Programming for beginners]
Author [empId=1, name=Krishna]
Publisher [publisherId=1, name=blogger]

 

Query PostgreSQL database for the DDL.

test=# \d+ author
                                                  Table "public.author"
  Column  |          Type          | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
----------+------------------------+-----------+----------+---------+----------+-------------+--------------+-------------
 authorid | integer                |           | not null |         | plain    |             |              | 
 name     | character varying(255) |           |          |         | extended |             |              | 
Indexes:
    "author_pkey" PRIMARY KEY, btree (authorid)
Referenced by:
    TABLE "book" CONSTRAINT "fkektypek2eiyeyfigr8kvudw9m" FOREIGN KEY (author_authorid) REFERENCES author(authorid)
Access method: heap

test=# 
test=# 
test=# \d+ publisher
                                                  Table "public.publisher"
   Column    |          Type          | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
-------------+------------------------+-----------+----------+---------+----------+-------------+--------------+-------------
 publisherid | integer                |           | not null |         | plain    |             |              | 
 name        | character varying(255) |           |          |         | extended |             |              | 
Indexes:
    "publisher_pkey" PRIMARY KEY, btree (publisherid)
Referenced by:
    TABLE "book" CONSTRAINT "fkphbqlkautxpp937p1gshnxcqv" FOREIGN KEY (publisher_publisherid) REFERENCES publisher(publisherid)
Access method: heap

test=# 
test=# 
test=# \d+ book
                                                          Table "public.book"
        Column         |          Type          | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
-----------------------+------------------------+-----------+----------+---------+----------+-------------+--------------+-------------
 title                 | character varying(255) |           | not null |         | extended |             |              | 
 publisher_publisherid | integer                |           | not null |         | plain    |             |              | 
 author_authorid       | integer                |           | not null |         | plain    |             |              | 
Indexes:
    "book_pkey" PRIMARY KEY, btree (author_authorid, publisher_publisherid, title)
Foreign-key constraints:
    "fkektypek2eiyeyfigr8kvudw9m" FOREIGN KEY (author_authorid) REFERENCES author(authorid)
    "fkphbqlkautxpp937p1gshnxcqv" FOREIGN KEY (publisher_publisherid) REFERENCES publisher(publisherid)
Access method: heap

You can download this application from this link.


Previous                                                    Next                                                    Home

No comments:

Post a Comment