Saturday, 24 December 2022

Hibernate 6: Generate identifiers using SEQUENCE strategy

When you use a 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.

 

Sequence generation strategy

You can specify the sequence strategy by setting the strategy attribute to SEQUENCE. To generate identifiers using SEQUENCE strategy, Hibernate use org.hibernate.id.enhanced.SequenceStyleGenerator id generator. One advantage of SequenceStyleGenerator is that it can work against the databases that do not support sequences, which gives a portability while migrating the data from one database to other.

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

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

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

In the above example, I do not specify the sequence name explicitly. In this case, hibernate assume the sequence name based on table name like employees_seq.

 

Specify the sequence name explicitly

By setting ‘generator’ attribute of  @GeneratedValue annotation, we can set the sequence name.

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

    @Id
    @GeneratedValue(
        strategy = GenerationType.SEQUENCE, 
        generator = "my_employees_seq"
    )
    private Integer id;

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

Above snippet use "my_employees_seq" as the sequence name.

 

Use @SequenceGenerator for more advanced configuration

@Entity
@Table(name = "employees")
public class Employee {
    @Id
    @GeneratedValue(
            strategy = GenerationType.SEQUENCE, 
            generator = "my_employees_seq_generator"
    )
    @SequenceGenerator(
            name = "my_employees_seq_generator", 
            sequenceName = "my_employees_seq",
            initialValue = 5,
            allocationSize = 10
    )
    private Integer id;

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

Find the below working application.

 

Step 1: Create new maven project ‘hibernate-sequence-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-sequence-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 class.

 

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.SequenceGenerator;
import jakarta.persistence.Table;

@Entity
@Table(name = "employees")
public class Employee {
    @Id
    @GeneratedValue(
            strategy = GenerationType.SEQUENCE, 
            generator = "my_employees_seq_generator"
    )
    @SequenceGenerator(
            name = "my_employees_seq_generator", 
            sequenceName = "my_employees_seq",
            initialValue = 5,
            allocationSize = 10
    )
    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 + "]";
    }

}

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

    </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 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");

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

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

            }

        }

    }
}

Total project structure looks like below.




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


Aug 22, 2022 4:55:09 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate ORM core version 6.1.2.Final
Aug 22, 2022 4:55:09 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using built-in connection pool (not intended for production use)
Aug 22, 2022 4:55:09 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: Loaded JDBC driver class: org.postgresql.Driver
Aug 22, 2022 4:55:09 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001012: Connecting with JDBC URL [jdbc:postgresql://127.0.0.1:5432/test]
Aug 22, 2022 4:55:09 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {password=****, user=postgres}
Aug 22, 2022 4:55:09 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Aug 22, 2022 4:55:09 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections <init>
INFO: HHH10001115: Connection pool size: 20 (min=1)
Aug 22, 2022 4:55:09 PM org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl logSelectedDialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.PostgreSQLDialect
Aug 22, 2022 4:55:10 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)
Hibernate: 
    
    drop table if exists employees cascade
Aug 22, 2022 4:55:10 PM org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@7c2dfa2] 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 22, 2022 4:55:10 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Aug 22, 2022 4:55:10 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: table "employees" does not exist, skipping
Hibernate: 
    
    drop sequence if exists my_employees_seq
Aug 22, 2022 4:55:10 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: SQL Warning Code: 0, SQLState: 00000
Aug 22, 2022 4:55:10 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler logWarning
WARN: sequence "my_employees_seq" does not exist, skipping
Hibernate: create sequence my_employees_seq start with 5 increment by 10
Aug 22, 2022 4:55:10 PM org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@58437801] 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,
        name varchar(255),
        primary key (id)
    )
Aug 22, 2022 4:55:10 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
        nextval('my_employees_seq')
Hibernate: 
    select
        nextval('my_employees_seq')
Hibernate: 
    insert 
    into
        employees
        (name, id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        employees
        (name, id) 
    values
        (?, ?)
Hibernate: 
    select
        e1_0.id,
        e1_0.name 
    from
        employees e1_0
Employee [id=5, name=Krishna]
Employee [id=6, name=Ram]

From the console output, you can confirm that a sequence name my_employees_seq  is created (create sequence my_employees_seq start with 5 increment by 10).

 

Query PostgreSQL database and confirm the same.

test=# \d
                List of relations
 Schema |       Name       |   Type   |  Owner   
--------+------------------+----------+----------
 public | employees        | table    | postgres
 public | my_employees_seq | sequence | postgres
(2 rows)

test=# 
test=# 
test=# 
test=# \d+ my_employees_seq
                      Sequence "public.my_employees_seq"
  Type  | Start | Minimum |       Maximum       | Increment | Cycles? | Cache 
--------+-------+---------+---------------------+-----------+---------+-------
 bigint |     5 |       1 | 9223372036854775807 |        10 | no      |     1

test=# 
test=# 
test=# \d+ employees
                                                Table "public.employees"
 Column |          Type          | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
--------+------------------------+-----------+----------+---------+----------+-------------+--------------+-------------
 id     | integer                |           | not null |         | plain    |             |              | 
 name   | character varying(255) |           |          |         | extended |             |              | 
Indexes:
    "employees_pkey" PRIMARY KEY, btree (id)
Access method: heap

You can download this application from this link.



Previous                                                    Next                                                    Home

No comments:

Post a Comment