Hibernate, @SequenceGenerator and allocationSize
We all know the default behaviour of Hibernate when using @SequenceGenerator
- it increases real database sequence by one, multiple this value by 50 (default allocationSize
value) - and then uses this value as entity ID.
This is incorrect behaviour and conflicts with specification which says:
allocationSize - (Optional) The amount to increment by when allocating sequence numbers from the sequence.
To be clear: I do not bother about gaps between generated IDs.
I care about IDs that are not consistent with underlying database sequence. For example: any other application (that e.g. uses plain JDBC) may want to insert new rows under IDs obtained from sequence - but all those values may be already used by Hibernate! Madness.
Do somebody know any solution to this problem (without setting allocationSize=1
and thus degrading performance)?
EDIT:
To make things clear.
If last inserted record had ID = 1
, then HB use values 51, 52, 53...
for its new entities BUT at the same time: sequence's value in database will be set to 2
. Which can easily leads to errors when other applications are using that sequence.
On the othe hand: specification says (in my understanding) that database sequence should have been set to 51
and in the meantime HB should use values from range 2, 3 ... 50
UPDATE:
As Steve Ebersole mentioned below: the behaviour described by me (and also the most intuitive for many) can be enabled by setting hibernate.id.new_generator_mappings=true
.
Thanks all of You.
UPDATE 2:
For future readers, below you can find a working example.
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USERS_SEQ")
@SequenceGenerator(name = "USERS_SEQ", sequenceName = "SEQUENCE_USERS")
private Long id;
}
persistence.xml
<persistence-unit name="testPU">
<properties>
<property name="hibernate.id.new_generator_mappings" value="true" />
</properties>
</persistence-unit>
Solution 1:
To be absolutely clear... what you describe does not conflict with the spec in any way. The spec talks about the values Hibernate assigns to your entities, not the values actually stored in the database sequence.
However, there is the option to get the behavior you are looking for. First see my reply on Is there a way to dynamically choose a @GeneratedValue strategy using JPA annotations and Hibernate? That will give you the basics. As long as you are set up to use that SequenceStyleGenerator, Hibernate will interpret allocationSize
using the "pooled optimizer" in the SequenceStyleGenerator. The "pooled optimizer" is for use with databases that allow an "increment" option on the creation of sequences (not all databases that support sequences support an increment). Anyway, read up about the various optimizer strategies there.
Solution 2:
allocationSize=1
It is a micro optimization before getting query Hibernate tries to assign value in the range of allocationSize and so try to avoid querying database for sequence. But this query will be executed every time if you set it to 1. This hardly makes any difference since if your data base is accessed by some other application then it will create issues if same id is used by another application meantime .
Next generation of Sequence Id is based on allocationSize.
By defualt it is kept as 50
which is too much. It will also only help if your going to have near about 50
records in one session which are not persisted and which will be persisted using this particular session and transation.
So you should always use allocationSize=1
while using SequenceGenerator
. As for most of underlying databases sequence is always incremented by 1
.
Solution 3:
I would check the DDL for the sequence in the schema. JPA Implementation is responsible only creation of the sequence with the correct allocation size. Therefore, if the allocation size is 50 then your sequence must have the increment of 50 in its DDL.
This case may typically occur with the creation of a sequence with allocation size 1 then later configured to allocation size 50 (or default) but the sequence DDL is not updated.
Solution 4:
Steve Ebersole & other members,
Would you kindly explain the reason for an id with a larger gap(by default 50)?
I am using Hibernate 4.2.15 and found the following code in org.hibernate.id.enhanced.OptimizerFactory cass.
if ( lo > maxLo ) {
lastSourceValue = callback.getNextValue();
lo = lastSourceValue.eq( 0 ) ? 1 : 0;
hi = lastSourceValue.copy().multiplyBy( maxLo+1 );
}
value = hi.copy().add( lo++ );
Whenever it hits the inside of the if statement, hi value is getting much larger. So, my id during the testing with the frequent server restart generates the following sequence ids:
1, 2, 3, 4, 19, 250, 251, 252, 400, 550, 750, 751, 752, 850, 1100, 1150.
I know you already said it didn't conflict with the spec, but I believe this will be very unexpected situation for most developers.
Anyone's input will be much helpful.
Jihwan
UPDATE:
ne1410s: Thanks for the edit.
cfrick: OK. I will do that. It was my first post here and wasn't sure how to use it.
Now, I understood better why maxLo was used for two purposes: Since the hibernate calls the DB sequence once, keep increase the id in Java level, and saves it to the DB, the Java level id value should consider how much was changed without calling the DB sequence when it calls the sequence next time.
For example, sequence id was 1 at a point and hibernate entered 5, 6, 7, 8, 9 (with allocationSize = 5). Next time, when we get the next sequence number, DB returns 2, but hibernate needs to use 10, 11, 12... So, that is why "hi = lastSourceValue.copy().multiplyBy( maxLo+1 )" is used to get a next id 10 from the 2 returned from the DB sequence. It seems only bothering thing was during the frequent server restart and this was my issue with the larger gap.
So, when we use the SEQUENCE ID, the inserted id in the table will not match with the SEQUENCE number in DB.