Saturday, 24 December 2022

Hibernate 6: Generate identifiers using TABLE strategy

When you use simple identifiers in an entity, then you can auto generate the identifiers.

 

How to specify that this identifier is auto generated?

Annotate the identifier with @GeneratedValue annotation.

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;

 

‘strategy’ attribute specifies the logic to generate identifiers. At the time of writing this post, Hibernate support 4 identifier generation strategies.

 

a.   AUTO: Indicates that the persistence provider should pick an appropriate strategy for the particular database.

b.   IDENTITY: Indicates that the persistence provider must assign primary keys for the entity using a database identity column.

c.    SEQUENCE: Indicates that the persistence provider must assign primary keys for the entity using a database sequence.

d.   TABLE: Indicates that the persistence provider must assign primary keys for the entity using an underlying database table to ensure uniqueness.

 

Unnamed table generator

@Entity
@Table(name = "employees")
public class Employee {

	@Id
	@GeneratedValue(strategy = GenerationType.TABLE)
	private Integer id;

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

 

Since in the above example, I do not specify any table name to generate identifiers, so Hibernate assumes an implicit name of hibernate_sequences.

create table hibernate_sequences (
	sequence_name varchar(255) not null,
	next_val bigint,
	primary key (sequence_name)
)

 

Configure table identifier using @TableGenerator annotation

@Entity
@Table(name = "employees")
public class Employee {
	
	@Id
	@GeneratedValue(
		strategy = GenerationType.TABLE,
		generator = "my-table-generator"
	)
	@TableGenerator(
		name =  "my-table-generator",
		table = "my_table_identifier",
		pkColumnName = "table_name",
		valueColumnName = "record_id",
		allocationSize = 5
	)
	private Integer id;

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

 

Above snippet generate below table to generate identifiers.

create table my_table_identifier (
    table_name varchar(255) not null,
    record_id bigint,
    primary key (table_name)
)

You can use the same identifier table for more than one entity.

@Entity
@Table(name = "persons")
public class Person {
	
	@Id
	@GeneratedValue(
		strategy = GenerationType.TABLE,
		generator = "my-table-generator"
	)
	@TableGenerator(
		name =  "my-table-generator",
		table = "my_table_identifier",
		pkColumnName = "table_name",
		valueColumnName = "record_id",
		allocationSize = 5
	)
	private Integer id;

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

Follow below step-by-step procedure to build complete application.

 

Step 1: Create new maven project ‘hibernate-table-strategy-demo’.

 

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-table-strategy-demo</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.

 

Employee.java

package com.sample.app.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.TableGenerator;

@Entity
@Table(name = "employees")
public class Employee {
	
	@Id
	@GeneratedValue(
		strategy = GenerationType.TABLE,
		generator = "my-table-generator"
	)
	@TableGenerator(
		name =  "my-table-generator",
		table = "my_table_identifier",
		pkColumnName = "table_name",
		valueColumnName = "record_id",
		allocationSize = 5
	)
	private Integer id;

	private String name;

	public Employee(String name) {
		this.name = name;
	}

	public Integer getId() {
		return id;
	}

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

	public String getName() {
		return name;
	}

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

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

}

Person.java

package com.sample.app.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.TableGenerator;

@Entity
@Table(name = "persons")
public class Person {
	
	@Id
	@GeneratedValue(
		strategy = GenerationType.TABLE,
		generator = "my-table-generator"
	)
	@TableGenerator(
		name =  "my-table-generator",
		table = "my_table_identifier",
		pkColumnName = "table_name",
		valueColumnName = "record_id",
		allocationSize = 5
	)
	private Integer id;

	private String name;

	public Person(String name) {
		this.name = name;
	}

	public Integer getId() {
		return id;
	}

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

	public String getName() {
		return name;
	}

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

	@Override
	public String toString() {
		return "Person [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.Person" />

	</session-factory>

</hibernate-configuration>

Step 5: Define main application class.

 

App.java

package com.sample.app;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

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.Person;

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

		Map<String, Object> config = new HashMap<>();
		config.put("maxThreads", 10);
		config.put("storagePath", "/Users/krishna/my-docs");

		Employee emp1 = new Employee("Krishna");
		Employee emp2 = new Employee("Ram");

		Person p1 = new Person("Bhadri");
		Person p2 = new Person("Venkata");

		try (Session session = sessionFactory.openSession()) {
			session.beginTransaction();
			session.persist(emp1);
			session.persist(emp2);

			session.persist(p1);
			session.persist(p2);

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

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

			}

			List<Person> persons = loadAllData(Person.class, session);
			for (Person person : persons) {
				System.out.println(person);

			}
		}

	}
}

Total project structure looks like below.




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

Aug 23, 2022 4:02:14 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate ORM core version 6.1.2.Final
Aug 23, 2022 4:02:15 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using built-in connection pool (not intended for production use)
Aug 23, 2022 4:02:15 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: Loaded JDBC driver class: org.postgresql.Driver
Aug 23, 2022 4:02:15 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001012: Connecting with JDBC URL [jdbc:postgresql://127.0.0.1:5432/test]
Aug 23, 2022 4:02:15 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {password=****, user=postgres}
Aug 23, 2022 4:02:15 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Aug 23, 2022 4:02:15 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections <init>
INFO: HHH10001115: Connection pool size: 20 (min=1)
Aug 23, 2022 4:02:15 PM org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl logSelectedDialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.PostgreSQLDialect
Aug 23, 2022 4:02:15 PM org.hibernate.id.enhanced.TableGenerator determineDefaultSegmentValue
INFO: HHH000398: Explicit segment value for id generator [my_table_identifier.table_name] suggested; using default [employees]
Aug 23, 2022 4:02:15 PM org.hibernate.id.enhanced.TableGenerator determineDefaultSegmentValue
INFO: HHH000398: Explicit segment value for id generator [my_table_identifier.table_name] suggested; using default [persons]
Aug 23, 2022 4:02:15 PM org.hibernate.metamodel.internal.EntityInstantiatorPojoStandard resolveConstructor
INFO: HHH000182: No default (no-argument) constructor for class: com.sample.app.entity.Employee (class must be instantiated by Interceptor)
Aug 23, 2022 4:02:15 PM org.hibernate.metamodel.internal.EntityInstantiatorPojoStandard resolveConstructor
INFO: HHH000182: No default (no-argument) constructor for class: com.sample.app.entity.Person (class must be instantiated by Interceptor)
Hibernate: 
    
    drop table if exists employees cascade
Aug 23, 2022 4:02:15 PM org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@35a0e495] 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.
Aug 23, 2022 4:02:15 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Aug 23, 2022 4:02:15 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: table "employees" does not exist, skipping
Hibernate: 
    
    drop table if exists my_table_identifier cascade
Aug 23, 2022 4:02:15 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Aug 23, 2022 4:02:15 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: table "my_table_identifier" does not exist, skipping
Hibernate: 
    
    drop table if exists persons cascade
Aug 23, 2022 4:02:15 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Aug 23, 2022 4:02:15 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: table "persons" does not exist, skipping
Hibernate: 
    
    create table employees (
       id integer not null,
        name varchar(255),
        primary key (id)
    )
Aug 23, 2022 4:02:15 PM org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@3206174f] 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 my_table_identifier (
       table_name varchar(255) not null,
        record_id bigint,
        primary key (table_name)
    )
Hibernate: 
    
    insert into my_table_identifier(table_name, record_id) values ('employees',0)
Hibernate: 
    
    insert into my_table_identifier(table_name, record_id) values ('persons',0)
Hibernate: 
    
    create table persons (
       id integer not null,
        name varchar(255),
        primary key (id)
    )
Aug 23, 2022 4:02:15 PM org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
Hibernate: 
    select
        tbl.record_id 
    from
        my_table_identifier tbl 
    where
        tbl.table_name=? for update of tbl
Hibernate: 
    update
        my_table_identifier 
    set
        record_id=?  
    where
        record_id=? 
        and table_name=?
Hibernate: 
    select
        tbl.record_id 
    from
        my_table_identifier tbl 
    where
        tbl.table_name=? for update of tbl
Hibernate: 
    update
        my_table_identifier 
    set
        record_id=?  
    where
        record_id=? 
        and table_name=?
Hibernate: 
    select
        tbl.record_id 
    from
        my_table_identifier tbl 
    where
        tbl.table_name=? for update of tbl
Hibernate: 
    update
        my_table_identifier 
    set
        record_id=?  
    where
        record_id=? 
        and table_name=?
Hibernate: 
    select
        tbl.record_id 
    from
        my_table_identifier tbl 
    where
        tbl.table_name=? for update of tbl
Hibernate: 
    update
        my_table_identifier 
    set
        record_id=?  
    where
        record_id=? 
        and table_name=?
Hibernate: 
    insert 
    into
        employees
        (name, id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        employees
        (name, id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        persons
        (name, id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        persons
        (name, id) 
    values
        (?, ?)
Hibernate: 
    select
        e1_0.id,
        e1_0.name 
    from
        employees e1_0
Employee [id=1, name=Krishna]
Employee [id=2, name=Ram]
Hibernate: 
    select
        p1_0.id,
        p1_0.name 
    from
        persons p1_0
Person [id=1, name=Bhadri]
Person [id=2, name=Venkata]

Query the table ‘my_table_identifier’, you can confirm that a record is created for every entity that is using this table for primary key generation.

test=# \d+ my_table_identifier;
                                             Table "public.my_table_identifier"
   Column   |          Type          | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
------------+------------------------+-----------+----------+---------+----------+-------------+--------------+-------------
 table_name | character varying(255) |           | not null |         | extended |             |              | 
 record_id  | bigint                 |           |          |         | plain    |             |              | 
Indexes:
    "my_table_identifier_pkey" PRIMARY KEY, btree (table_name)
Access method: heap

test=# 
test=# 
test=# SELECT * FROM my_table_identifier;
 table_name | record_id 
------------+-----------
 employees  |        10
 persons    |        10
(2 rows)

You can download this application from this link.



 

Previous                                                    Next                                                    Home

No comments:

Post a Comment