The persistence provider picks the most appropriate identifier strategy supported by the underlying database
IDENTITY
Identifiers are assigned by a database IDENTITY column
SEQUENCE
The persistence provider uses a database sequence for generating identifiers
TABLE
The persistence provider uses a separate database table to emulate a sequence object
In my previous post I exampled the pros and cons of all these surrogate identifier strategies.
Identifier optimizers
While there’s not much application-side IDENTITY generator optimization (other than configuring database identity preallocation), the sequence identifiers offer much more flexibility in this regard. One of the most common optimization strategy is based on the hi/lo allocation algorithm.
A database table is used for generating the hi values. This generator is deprecated in favour of the MultipleHiLoPerTableGenerator, the enhanced TableGenerator or the SequenceStyleGenerator.
It’s an enhanced version of the previous sequence generator. It uses a sequence if the underlying database supports them. If the current database doesn’t support sequences it switches to using a table for generating sequence values. While the previous generators were having a predefined optimization algorithm, the enhanced generators can be configured with an optimizer strategy:
none: there is no optimizing strategy applied, so every identifier is fetched from the database
hi/lo: it uses the original hi/lo algorithm. This strategy makes it difficult for other systems to share the same identifier sequence, requiring other systems to implement the same identifier generation logic.
pooled: This optimizer uses a hi/lo optimization strategy, but instead of saving the current hi value it stores the current range upper boundary (or lower boundary – hibernate.id.optimizer.pooled.prefer_lo).
Like MultipleHiLoPerTableGenerator it may use one single table for multiple identifier generators, while offering configurable optimizer strategies.
Pooled is the default optimizer strategy.
JPA to Hibernate identifier mapping
Having such an abundant generator offer, we cannot help asking which of those is being used as the default JPA generators.
While the JPA specification doesn’t imply any particular optimization, Hibernate will prefer an optimized generator over one that always hit the database for every new identifier.
We’ll define one entity configured with the SEQUENCE JPA identifier generator. A unit test is going to persist five such entities.
@Entity(name = "sequenceIdentifier")
public class SequenceIdentifier {
@Id
@GeneratedValue(generator = "sequence", strategy=GenerationType.SEQUENCE)
@SequenceGenerator(name = "sequence", allocationSize = 10)
private Long id;
}
@Test
public void testSequenceIdentifierGenerator() {
LOGGER.debug("testSequenceIdentifierGenerator");
doInTransaction(new TransactionCallable<Void>() {
@Override
public Void execute(Session session) {
for (int i = 0; i < 5; i++) {
session.persist(new SequenceIdentifier());
}
session.flush();
return null;
}
});
}
Running this test we’ll give us the following output
Query:{[call next value for hibernate_sequence][]}
Generated identifier: 10, using strategy: org.hibernate.id.SequenceHiLoGenerator
Generated identifier: 11, using strategy: org.hibernate.id.SequenceHiLoGenerator
Generated identifier: 12, using strategy: org.hibernate.id.SequenceHiLoGenerator
Generated identifier: 13, using strategy: org.hibernate.id.SequenceHiLoGenerator
Generated identifier: 14, using strategy: org.hibernate.id.SequenceHiLoGenerator
Query:{[insert into sequenceIdentifier (id) values (?)][10]}
Query:{[insert into sequenceIdentifier (id) values (?)][11]}
Query:{[insert into sequenceIdentifier (id) values (?)][12]}
Query:{[insert into sequenceIdentifier (id) values (?)][13]}
Query:{[insert into sequenceIdentifier (id) values (?)][14]}
Hibernate chooses to use the legacy SequenceHiLoGenerator for backward compatibility with all those applications that were developed prior to releasing the enhanced generators. Migrating a legacy application to the new generators is not an easy process, so the enhanced generators are a better alternative for new applications instead.
Hibernate prefers using the “seqhilo” generator by default, which is not an intuitive assumption since many might expect the raw “sequence” generator (always calling the database sequence for every new identifier value).
To enable the enhanced generators we need to set the following Hibernate property:
Query:{[call next value for hibernate_sequence][]}
Query:{[call next value for hibernate_sequence][]}
Generated identifier: 1, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
Generated identifier: 2, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
Generated identifier: 3, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
Generated identifier: 4, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
Generated identifier: 5, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
Query:{[insert into sequenceIdentifier (id) values (?)][1]}
Query:{[insert into sequenceIdentifier (id) values (?)][2]}
Query:{[insert into sequenceIdentifier (id) values (?)][3]}
Query:{[insert into sequenceIdentifier (id) values (?)][4]}
Query:{[insert into sequenceIdentifier (id) values (?)][5]}
The new SequenceStyleGenerator generates other identifier values than the legacy SequenceHiLoGenerator. The reason why the update statements differ between the old and the new generators is because the new generators default optimizer strategy is “pooled” while the old generators can only use the “hi/lo” strategy.
Query:{[select tbl.next_val from hibernate_sequences tbl where tbl.sequence_name=? for update][tableIdentifier]}
Query:{[insert into hibernate_sequences (sequence_name, next_val) values (?,?)][tableIdentifier,1]}
Query:{[update hibernate_sequences set next_val=? where next_val=? and sequence_name=?][11,1,tableIdentifier]}
Query:{[select tbl.next_val from hibernate_sequences tbl where tbl.sequence_name=? for update][tableIdentifier]}
Query:{[update hibernate_sequences set next_val=? where next_val=? and sequence_name=?][21,11,tableIdentifier]}
Generated identifier: 1, using strategy: org.hibernate.id.enhanced.TableGenerator
Generated identifier: 2, using strategy: org.hibernate.id.enhanced.TableGenerator
Generated identifier: 3, using strategy: org.hibernate.id.enhanced.TableGenerator
Generated identifier: 4, using strategy: org.hibernate.id.enhanced.TableGenerator
Generated identifier: 5, using strategy: org.hibernate.id.enhanced.TableGenerator
Query:{[insert into tableIdentifier (id) values (?)][1]}
Query:{[insert into tableIdentifier (id) values (?)][2]}
Query:{[insert into tableIdentifier (id) values (?)][3]}
Query:{[insert into tableIdentifier (id) values (?)][4]}
Query:{[insert into tableIdentifier (id) values (?)][5]}
You can see that the new enhanced TableGenerator was used this time.
The extended identifier generators are very useful as they allow us to reduce the number of database roundtrips when persisting entities using sequence-based identifiers.