In the previous tutorials part 1 and part 2 about the @GeneratedValue annotation in JPA, I mentioned the allocationSize attribute when using the GenerationType.TABLE and GenerationType.SEQUENCE strategies. Notice that they are not detailed enough for you to understand the allocationSize attribute, so I added this article to discuss more about this attribute.
I will still take the example in the previous tutorial about @GeneratedValue annotation as an example for this tutorial.
To see the effect of the allocationSize attribute, I will modify the code of the Application class a little bit like below:
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.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(); for (int i = 0 ; i < 5; i++) { Clazz clazz = new Clazz(); clazz.setName("Class B"); em.persist(clazz); } transaction.commit(); } } |
As you can see, compared to the previous code, instead of just inserting a record in a transaction, I insert 5 records at a time.
Now, we will discuss the allocationSize attribute with the GenerationType.TABLE strategy first.
1 2 3 |
@Id @GeneratedValue(strategy = GenerationType.TABLE) private Long id; |
As you know, we have two versions to define the structure of the table containing the information that will be used to generate the value for the primary key column, depending on the value of the “hibernate.id.new.generator_mappings” property.
In case the value of this property is false, if you run the example again, you will see the following result:
As you can see, the value of the id column in the clazz table is generated in a different way than the way it was generated in this tutorial.
Instead of inserting one record into the clazz table, the value of the id column of this table is increased to a number, called default allocationSize, at this time in a run with inserting 5 records, the value of the id column increases 1 unit.
What are the causes?
You already know that the column sequence_next_hi_value in the hibernate_sequences table is the column that holds the information to generate the value for the id column in the clazz table. What is the problem if every time a new record was inserted into the clazz table, you would have to query the hibernate_sequence table to retrieve the value to generate for the id column in the clazz table? The performance will be very low, right?
To solve this problem, JPA uses the term allocationSize to eliminate the need for more queries to the hibernate_sequences table.
In one application run, JPA will hold all database related information.
On the first run and insert the first record into the clazz table as shown above, the hibernate_sequences table will not have the value for the Clazz entity, the id value in the clazz table will begin with 1. At the second insert into the clazz table of this run, JPA will not query the table hibernate_sequences to get the value to generate for the id column anymore. Since the default allocation value of Hibernate is up to 32768, JPA only needs to increase the value of the id column in the clazz table by 1. So until the value of the id column in the clazz table reaches the allocationSize level, then JPA queries the hibernate_sequence table again to retrieve the new value to generate for the id column. allocationSize will not change the value so the generated values for the id column in the clazz table keep repeating until the applications turn off.
If you look at the SQL statement to the hibernate_sequences table, you will see the results for this run 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 |
Hibernate: select sequence_next_hi_value from hibernate_sequences where sequence_name = 'Clazz' for update Hibernate: insert into hibernate_sequences (sequence_name, sequence_next_hi_value) values ('Clazz', ?) Hibernate: update hibernate_sequences set sequence_next_hi_value = ? where sequence_next_hi_value = ? and sequence_name = 'Clazz' |
Obviously, JPA only queries the hibernate_sequences table once, and since there is no record for the Clazz entity, it inserts new and updates the value for this table.
At the second run, JPA does not hold the information of the previous run, so it still queries the hibernate_sequences table to get the value to generate for the id column in the clazz table at the first insert record in the table. Then increment the value of the id column each time the new record is inserted, one by one until the allocationSize threshold is reached.
Take a look at the SQL statement for the hibernate_sequences table:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Hibernate: select sequence_next_hi_value from hibernate_sequences where sequence_name = 'Clazz' for update Hibernate: update hibernate_sequences set sequence_next_hi_value = ? where sequence_next_hi_value = ? and sequence_name = 'Clazz' |
You will see the effect of the allocationSize attribute when using the customize table with the allocationSize value smaller than the default value of Hibernate.
For example, we define a customized table for the Clazz entity 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 = 2 ) @GeneratedValue(strategy = GenerationType.TABLE, generator = "clazz_gen") private Long id; |
Then the number of queries to the id_gen table will be greater than at the first run:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
Hibernate: select gen_val from id_gen where gen_name = 'Clazz' for update Hibernate: insert into id_gen (gen_name, gen_val) values ('Clazz', ?) Hibernate: update id_gen set gen_val = ? where gen_val = ? and gen_name = 'Clazz' Hibernate: select gen_val from id_gen where gen_name = 'Clazz' for update Hibernate: update id_gen set gen_val = ? where gen_val = ? and gen_name = 'Clazz' Hibernate: select gen_val from id_gen where gen_name = 'Clazz' for update Hibernate: update id_gen set gen_val = ? where gen_val = ? and gen_name = 'Clazz' |
Note that we only discussed the case where the value of the “hibernate.id.new_generator_mappings” property is false.
In case the value of the “hibernate.id.new_generator_mappings” property is true, run the example again and you will see the result:
Because default allocationSize in this case of Hibernate is only 1, you will see the number of times querying to default Hibernate table (hibernate_sequences) (5 times) 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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
Hibernate: select tbl.next_val from hibernate_sequences tbl where tbl.sequence_name=? for update Hibernate: insert into hibernate_sequences (sequence_name, next_val) values (?,?) Hibernate: update hibernate_sequences set next_val=? where next_val=? and sequence_name=? Hibernate: select tbl.next_val from hibernate_sequences tbl where tbl.sequence_name=? for update Hibernate: update hibernate_sequences set next_val=? where next_val=? and sequence_name=? Hibernate: select tbl.next_val from hibernate_sequences tbl where tbl.sequence_name=? for update Hibernate: update hibernate_sequences set next_val=? where next_val=? and sequence_name=? Hibernate: select tbl.next_val from hibernate_sequences tbl where tbl.sequence_name=? for update Hibernate: update hibernate_sequences set next_val=? where next_val=? and sequence_name=? Hibernate: select tbl.next_val from hibernate_sequences tbl where tbl.sequence_name=? for update Hibernate: update hibernate_sequences set next_val=? where next_val=? and sequence_name=? |
Now, let’s try with the customized 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 = 3 ) @GeneratedValue(strategy = GenerationType.TABLE, generator = "clazz_gen") private Long id; |
Result:
The number of queries to the table id_gen will be a little bit (3 times) than the default allocationSize:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
Hibernate: select tbl.gen_val from id_gen tbl where tbl.gen_name=? for update Hibernate: insert into id_gen (gen_name, gen_val) values (?,?) Hibernate: update id_gen set gen_val=? where gen_val=? and gen_name=? Hibernate: select tbl.gen_val from id_gen tbl where tbl.gen_name=? for update Hibernate: update id_gen set gen_val=? where gen_val=? and gen_name=? Hibernate: select tbl.gen_val from id_gen tbl where tbl.gen_name=? for update Hibernate: update id_gen set gen_val=? where gen_val=? and gen_name=? |
Now, let’s see how the GenerationType.SEQUENCE strategy is different.
1 2 3 |
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; |
In this case, we only consider that the value of the “hibernate.id.new_generator_mappings” property is true.
Run the example again, you will see the results as follows:
And the number of queries to hibernate_sequence table will be 5 times 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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
Hibernate: select next_val as id_val from hibernate_sequence for update Hibernate: update hibernate_sequence set next_val= ? where next_val=? Hibernate: select next_val as id_val from hibernate_sequence for update Hibernate: update hibernate_sequence set next_val= ? where next_val=? Hibernate: select next_val as id_val from hibernate_sequence for update Hibernate: update hibernate_sequence set next_val= ? where next_val=? Hibernate: select next_val as id_val from hibernate_sequence for update Hibernate: update hibernate_sequence set next_val= ? where next_val=? Hibernate: select next_val as id_val from hibernate_sequence for update Hibernate: update hibernate_sequence set next_val= ? where next_val=? |
What about the customized table?
1 2 3 4 |
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence_gen") @SequenceGenerator(name = "sequence_gen", sequenceName = "sequence", allocationSize = 3) private Long id; |
The results is as follows:
And the number of queries to the table sequence is 3 times 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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
Hibernate: select next_val as id_val from sequence for update Hibernate: update sequence set next_val= ? where next_val=? Hibernate: select next_val as id_val from sequence for update Hibernate: update sequence set next_val= ? where next_val=? Hibernate: select next_val as id_val from sequence for update Hibernate: update sequence set next_val= ? where next_val=? |
BRIEFLY, as we have discussed, in the case where you use the GenerationType.TABLE and GenerationType.SEQUENCE strategies to generate values for the primary key columns in the tables in the database, the use of the allocationSize attribute should be emphasized to increase the performance for our application. The higher the value of this attribute will be higher the performance, but what the value of this attribute makes sense, that need to be considered.