How to combine the Hibernate assigned generator with a sequence or an identity column
Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Wouldn’t that be just awesome?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.
So, enjoy spending your time on the things you love rather than fixing performance issues in your production system on a Saturday night!
Introduction
The entity identifier can either be manually assigned, or it can be automatically generated by an identity column or a database sequence.
In this post, I’ll show you how you can mix the assigned generator with an identity column or a database sequence.
Identifier generators
The assigned generator doesn’t take a @GeneratedValue
annotation, and the identifier mapping looks like this:
@Id private Long id;
To use an identity column, the @GeneratedValue
annotation must be supplied:
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
The same goes for using a database sequence:
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id;
None of the built-in identifier generators allows you to mix a manually-assigned identifier with an automatic assigning strategy.
Let’s assume that our current entity takes a positive identifier value when the primary key is assigned by the database, while the negative values are reserved for a batch process that imports data from a legacy system.
The assigned-identity generator
First, all our entities implement the following interface:
public interface Identifiable<T extends Serializable> { T getId(); }
To combine an identity column with the assigned identifier strategy, we need to create the following custom identifier strategy:
public class AssignedIdentityGenerator extends IdentityGenerator { @Override public Serializable generate(SessionImplementor session, Object obj) { if(obj instanceof Identifiable) { Identifiable identifiable = (Identifiable) obj; Serializable id = identifiable.getId(); if(id != null) { return id; } } return super.generate(session, obj); } }
To use this identifier generator, the entity mapping looks as follows:
@Entity( public class Post implements Identifiable<Long> { @Id @GenericGenerator( name = "assigned-identity", strategy = "com.vladmihalcea.book.hpjp.hibernate.identifier.AssignedIdentityGenerator" ) @GeneratedValue( generator = "assigned-identity", strategy = GenerationType.IDENTITY ) private Long id; @Version private Integer version; public Post() { } public Post(Long id) { this.id = id; } @Override public Long getId() { return id; } }
With this mapping in place, when running the following test case:
doInJPA(entityManager -> { entityManager.persist(new Post()); entityManager.persist(new Post(-1L)); entityManager.persist(new Post()); entityManager.persist(new Post(-2L)); });
Hibernate generates the following SQL statements:
INSERT INTO post (id, version) VALUES (DEFAULT, 0) INSERT INTO post (version, id) VALUES (0, -1) INSERT INTO post (id, version) VALUES (DEFAULT, 0) INSERT INTO post (version, id) VALUES (0, -2)
The assigned-sequence generator
We can, of course, do the same with a sequence generator.
The custom sequence generator that can also accommodate manually assigned identifier values looks as follows:
public class AssignedSequenceStyleGenerator extends SequenceStyleGenerator { @Override public Serializable generate(SessionImplementor session, Object obj) { if(obj instanceof Identifiable) { Identifiable identifiable = (Identifiable) obj; Serializable id = identifiable.getId(); if(id != null) { return id; } } return super.generate(session, obj); } }
The only difference is that, this time, we are extending the SequenceStyleGenerator.
The entity mapping looks like this:
@Entity public class Post implements Identifiable<Long> { @Id @GenericGenerator( name = "assigned-sequence", strategy = "com.vladmihalcea.book.hpjp.hibernate.identifier.AssignedSequenceStyleGenerator", parameters = @org.hibernate.annotations.Parameter( name = "sequence_name", value = "post_sequence" ) ) @GeneratedValue( generator = "assigned-sequence", strategy = GenerationType.SEQUENCE ) private Long id; @Version private Integer version; public Post() { } public Post(Long id) { this.id = id; } @Override public Long getId() { return id; } }
When running the previous test case, Hibernate generates the following SQL statements:
CALL NEXT VALUE FOR post_sequence CALL NEXT VALUE FOR post_sequence INSERT INTO post (version, id) VALUES (0, 1) INSERT INTO post (version, id) VALUES (0, -1) INSERT INTO post (version, id) VALUES (0, 2) INSERT INTO post (version, id) VALUES (0, -2)
I'm running an online workshop on the 11th of October about High-Performance SQL.If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
Although you might not bump into such a requirement, it’s important to know that Hibernate is highly extensible, allowing you to customize the built-in mapping strategies.
