When defining a primary key of a table in the database, we usually add AUTO_INCREMENT. With this definition, we do not need to care about the value of the primary key column every time we add a new data to the table, which will be assigned automatically and unique. In JPA, to do this, we have to declare to the primary key attribute of the entity an annotation called @GeneratedValue. How is it in details? Let’s find out about @GeneratedValue annotation in JPA in this tutorial.
First, I will create a Maven project as an example:
Maven dependencies:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.2.12.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>5.2.12.Final</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>6.0.6</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.18</version> <scope>provided</scope> </dependency> |
In this tutorial, I will use the MySQL database with a table named clazz, which is defined with the following structure:
1 2 3 4 5 |
CREATE TABLE `clazz` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(45) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
Entity of this table is defined initially as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package com.huongdanjava.jpageneratedvalue; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import lombok.Getter; import lombok.Setter; @Entity @Table @Getter @Setter public class Clazz { @Id @GeneratedValue private Long id; @Column private String name; } |
JPA configuration file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence_2_1.xsd"> <persistence-unit name="jpaexample" transaction-type="RESOURCE_LOCAL"> <class>com.huongdanjava.jpageneratedvalue.Clazz</class> <exclude-unlisted-classes>true</exclude-unlisted-classes> <properties> <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" /> <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa_example" /> <property name="javax.persistence.jdbc.user" value="root" /> <property name="javax.persistence.jdbc.password" value="123456" /> <property name="hibernate.format_sql" value="true" /> <property name="hibernate.show_sql" value="true" /> <property name="hibernate.use_sql_comments" value="true" /> <property name="hibernate.id.new_generator_mappings" value="false" /> </properties> </persistence-unit> </persistence> |
Application class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package com.huongdanjava.jpageneratedvalue; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; public class Application { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpaexample"); EntityManager em = emf.createEntityManager(); EntityTransaction transaction = em.getTransaction(); transaction.begin(); Clazz clazz = new Clazz(); clazz.setName("Class A"); em.persist(clazz); transaction.commit(); } } |
OK, so everything is ready, now we will go into the main topic of this tutorial.
As I said before, in JPA, let the primary keys of a table in the database automatically be assigned values each time we insert a new record and this value is unique, we will use the @GeneratedValue annotation when declaring those primary key columns. There are four different ways we can do this, depending on the value of the “strategy” attribute that we declare in the @GeneratedValue annotation.
The first strategy we want to talk is the GenerationType.TABLE strategy
You declare this strategy in the Clazz entity as follows:
1 2 3 |
@Id @GeneratedValue(strategy = GenerationType.TABLE) private Long id; |
With this strategy, as the name implies, we will need to define another table to store information for generating the value for the primary key column.
By default, if you use Hibernate as the implementation of JPA, the name of this table is hibernate_sequences. There are currently two versions that define the structure of this table, depending on the value of the “hibernate.id.new_generator_mappings” property.
If you do not declare the “hibernate.id.new_generator_mappings” property in the JPA configuration file or have declared it and its value is:
1 |
<property name="hibernate.id.new_generator_mappings" value="true" /> |
The structure of the table hibernate_sequences will be as follows:
1 2 3 4 5 |
CREATE TABLE `hibernate_sequences` ( `sequence_name` VARCHAR(255) NOT NULL, `next_val` INT(19), PRIMARY KEY (`sequence_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
If the value of the property “hibernate.id.new_generator_mappings” is:
1 |
<property name="hibernate.id.new_generator_mappings" value="false" /> |
The structure of the table hibernate_sequences will be as follows:
1 2 3 4 5 |
CREATE TABLE `hibernate_sequences` ( `sequence_name` VARCHAR(255) NOT NULL, `sequence_next_hi_value` INT(11), PRIMARY KEY (`sequence_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
In this example, I set the value of the “hibernate.id.new_generator_mappings” property to false and when running, you will see the following result:
As you can see, I ran 3 times and for each run, JPA will retrieve the current value of the sequence_next_hi_value column in the hibernate_sequences table, then increment this value by 1, then update the sequence_next_hi_value column with the new value for sequence_name is Clazz (Clazz is our entity name) and finally inserts a new record for the clazz table. On the first run, since there are no records in hibernate_sequences table, then the value of column sequence_next_hi_value will be 1. Getting the current value of the sequence_next_hi_value is to determine the value to generate for the primary key column in the clazz table.
The value of the id column in the clazz table, at the first when no record in clazz table then its value will be 1 but after that, it is incremented by 32768 for each run. The value of the primary key column in the clazz table for the n times insertion will be the value of the sequence_next_hi_value in the hibernate_sequences table at that time multiple with 32768. This number 32768 is called the default allocationSize of Hibernate. Actually, it is the result of an algorithm, called the hi/lo algorithm. You can find out more about this algorithm on the internet if you want.
In case you do not want to use default table or allocationSize of Hibernate, you can define it to use a different table by using the @TableGenerator annotation. Specifically, we can declare as follows:
1 2 3 4 5 6 7 8 9 10 |
@Id @TableGenerator( name = "clazz_gen", table = "id_gen", pkColumnName = "gen_name", valueColumnName = "gen_val", allocationSize = 1 ) @GeneratedValue(strategy = GenerationType.TABLE, generator = "clazz_gen") private Long id; |
In the above declaration, I have defined to use a table named id_gen also with 2 columns are gen_name, gen_val:
1 2 3 4 5 |
CREATE TABLE `id_gen` ( `gen_name` VARCHAR(255) NOT NULL, `gen_val` INT(11), PRIMARY KEY (`gen_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
and allocationSize is 1.
The most important part is that you need to declare the generator attribute for the @GeneratedValue annotation with the value of the name attribute in the @TableGenerator annotation.
Then, run the example, you will see the results as follows:
As you can see, the value of the id column in the clazz table is now incremented by 1, unlike when we used the default Hibernate table.
In case the value of the “hibernate.id.new_generator_mappings” property is true, when running with the default Hibernate table:
1 2 3 |
@Id @GeneratedValue(strategy = GenerationType.TABLE) private Long id; |
You will see the following results:
Here, I also ran 3 times and unlike the case of the value of the “hibernate.id.new_generator_mappings” property in false, in this case, there is only a default value for the sequence_name column in the hibernate_sequences table. Every entity that is inserted into the database tables will query to the value of next_val column of the sequence_name column with the default value in the hibernate_sequences table to retrieve the value for the primary key column.
The value of the next_val column per run will increase to one unit. This is also the default allocationSize number in this case. For the first time, when there are no records in the hibernate_sequences table, a new record will be added with next_val being 0, then it will increment to 1.
For example, now I add another table, named student, with the following structure:
1 2 3 4 5 |
CREATE TABLE `student` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(45) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
Entity Student:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package com.huongdanjava.jpageneratedvalue; import lombok.Getter; import lombok.Setter; import javax.persistence.*; @Entity @Table @Getter @Setter public class Student { @Id @GeneratedValue(strategy = GenerationType.TABLE) private Long id; @Column private String name; } |
Then run the following example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package com.huongdanjava.jpageneratedvalue; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; public class Application { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpaexample"); EntityManager em = emf.createEntityManager(); EntityTransaction transaction = em.getTransaction(); transaction.begin(); Clazz clazz = new Clazz(); clazz.setName("Class B"); em.persist(clazz); Student student = new Student(); student.setName("Khanh"); em.persist(student); transaction.commit(); } } |
You will see the results as follows:
Obviously, in this case, all entities use the same default value for sequence_name to generate the value for their primary key column.
Just like the case of the value of the property “hibernate.id.new_generator_mappings” by false, we can also define another table to use instead of using the default Hibernate table:
1 2 3 4 5 6 7 8 9 10 |
@Id @TableGenerator( name = "clazz_gen", table = "id_gen", pkColumnName = "gen_name", valueColumnName = "gen_val", allocationSize = 2 ) @GeneratedValue(strategy = GenerationType.TABLE, generator = "clazz_gen") private Long id; |
In the above declaration, I also defined to use a table named id_gen with 2 columns is gen_name, gen_val:
1 2 3 4 5 |
CREATE TABLE `id_gen` ( `gen_name` VARCHAR(255) NOT NULL, `gen_val` INT(11), PRIMARY KEY (`gen_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
and the allocationSize is 2.
The most important part is that you need to declare the generator attribute for the @GeneratedValue annotation with the value is same with the value of the name attribute in the @TableGenerator annotation.
Then, run the example with the newly created database, you will see the results as follows:
Now, gen_name is not the default value, it will be the name of the entity.
The first run, without any record in id_gen table, the value of gen_val will be 5, and the value of the primary key column in the clazz table will be 1. In the next run and insert record, the value of the gen_val column will be updated with the current value of this column plus allocationSize. The value of the primary key column in the clazz table will be equal to the current value before the update of the gen_val column in the id_gen table minus 1.