Hibernate default entity sequence
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
In this article, we are going to see how the default entity sequence changes when migrating from Hibernate 5 to Hibernate 6.
Domain Model
Let’s assume we have a Post
parent entity that has a one-to-many bidirectional association with the PostComment
child entity.
The Post
parent entity looks like this:
@Entity(name = "Post") @Table(name = "post") public class Post { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; private String title; @Column(name = "created_on") private LocalDate createdOn; @OneToMany(mappedBy = "post", cascade = CascadeType.ALL) private List<PostComment> comments = new ArrayList<>(); public Post addComment(PostComment comment) { comment.setPost(this); comments.add(comment); return this; } }
And the PostComment
looks as follows:
@Entity(name = "PostComment") @Table(name = "post_comment") public class PostComment { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; @ManyToOne(fetch = FetchType.LAZY) private Post post; private String review; }
Notice that both entities use the GenerationType.SEQUENCE
automatic identifier generation strategy.
Hibernate 5 default entity sequence
On Hibernate 5, if you use the hbm2ddl
tool to generate the database schema from the JPA entity mappings, the following database sequence will be created:
CREATE SEQUENCE hibernate_sequence START 1 INCREMENT 1
This is the default database sequence that Hibernate will use when you don’t specify the sequence name explicitly using the @SequenceGenerator
annotation.
So, if we create the following Post
and PostComment
entities:
entityManager.persist( new Post() .setTitle( "High-Performance Java Persistence " + "eBook has been released!") .setCreatedOn(LocalDate.of(2016, 8, 30)) .addComment( new PostComment() .setReview("Best book on JPA and Hibernate!") ) .addComment( new PostComment() .setReview("A must-read for every Java developer!") ) ); entityManager.persist( new Post() .setTitle( "Hypersistence Optimizer has been released!") .setCreatedOn(LocalDate.of(2019, 3, 19)) .addComment( new PostComment() .setReview( "Hypersistence Optimizer feels like " + "an additional team member" ) ) );
Hibernate 5 will execute the following SQL statements:
SELECT nextval ('hibernate_sequence') SELECT nextval ('hibernate_sequence') SELECT nextval ('hibernate_sequence') SELECT nextval ('hibernate_sequence') SELECT nextval ('hibernate_sequence') INSERT INTO post ( created_on, title, id ) VALUES ( '2016-08-30', 'High-Performance Java Persistence eBook has been released!', 1 ) INSERT INTO post_comment ( post_id, review, id ) VALUES ( 1, 'Best book on JPA and Hibernate!', 2 ) INSERT INTO post_comment ( post_id, review, id ) VALUES ( 1, 'A must-read for every Java developer!', 3 ) INSERT INTO post ( created_on, title, id ) VALUES ( '2019-03-19', 'Hypersistence Optimizer has been released!', 4 ) INSERT INTO post_comment ( post_id, review, id ) VALUES ( 4, 'Hypersistence Optimizer feels like an additional team member', 5 )
You can see that the default hibernate_sequence
is used to generate the Primary Key values for both the post
and post_comment
table records.
Hibernate 6 default entity sequence
When using Hibernate 6, the hbm2ddl
tool will the following database sequence for our Post
and PostComment
entities:
CREATE SEQUENCE post_SEQ START WITH 1 INCREMENT BY 50 CREATE SEQUENCE post_comment_SEQ START WITH 1 INCREMENT BY 50
So, comparing to Hibernate 5, there are two major differences in Hibernate 5:
First, each entity gets its own database sequence instead of using a global sequence for all entities.
Second, the allocation size is 50
, not 1
. This is because the default allocationSize
of the JPA @SequenceGenerator
is 50
:
@Repeatable(SequenceGenerators.class) @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) public @interface SequenceGenerator { String name(); String sequenceName() default ""; String catalog() default ""; String schema() default ""; int initialValue() default 1; int allocationSize() default 50; }
So, when persisting the same Post
and PostComment
entities as we did previously in our Hibernate 5 section, then Hibernate 6 will execute the following SQL statements:
SELECT nextval('post_SEQ') SELECT nextval('post_comment_SEQ') SELECT nextval('post_comment_SEQ') SELECT nextval('post_SEQ') INSERT INTO post ( created_on, title, id ) VALUES ( '2016-08-30', 'High-Performance Java Persistence eBook has been released!', 1 ) INSERT INTO post_comment ( post_id, review, id ) VALUES ( 1, 'Best book on JPA and Hibernate!', 1 ) INSERT INTO post_comment ( post_id, review, id ) VALUES ( 1, 'A must-read for every Java developer!', 2 ) INSERT INTO post ( created_on, title, id ) VALUES ( '2019-03-19', 'Hypersistence Optimizer has been released!', 2 ) INSERT INTO post_comment ( post_id, review, id ) VALUES ( 2, 'Hypersistence Optimizer feels like an additional team member', 3 )
By default, Hibernate uses the pooled
optimizer when the sequence allocationSize
is greater than 1
.
The reason why there are two sequence calls for both entity sequences is that the pooled
optimizer needs to get the first identifier value that’s greater than 1
, and for that, it has to call the sequence twice because the first call returns the initial value of 1
.
However, even if we created 3
post_comment
records, the post_comment_SEQ
sequence was not called 3
times since the default allocation size is 50
.
We can see this in practice when creating 5 more Post
entities in a successive transaction:
for (int i = 1; i <= 5; i++) { entityManager.persist( new Post() .setTitle(String.format("Post number %d", i)) .setCreatedOn(LocalDate.now()) ); }
As Hibernate 6 will generate the following statements:
INSERT INTO post (created_on, title, id) VALUES ('2023-05-09', 'Post number 1', 3) INSERT INTO post (created_on, title, id) VALUES ('2023-05-09', 'Post number 2', 4) INSERT INTO post (created_on, title, id) VALUES ('2023-05-09', 'Post number 3', 5) INSERT INTO post (created_on, title, id) VALUES ('2023-05-09', 'Post number 4', 6) INSERT INTO post (created_on, title, id) VALUES ('2023-05-09', 'Post number 5', 7)
Until we generate 50
Post
entities from multiple transactions, the post_SEQ
won’t be called again.
Migrating to Hibernate 6
When migrating to Hibernate 6, if you relied on the default Hibernate 5 sequence behavior, then you might have some issues since now Hibernate expects to find entity-specific sequences that you might be missing.
So, there are two ways to do this default sequence migration.
The best way is to use entity-specific sequences since they allow you to reduce the number of calls to the database sequence.
If you don’t want to do that and you want to share the same database hibernate_sequence
for all your entities, then you must change your entity identifier mapping to use it explicitly, like this:
@Id @GeneratedValue( strategy = GenerationType.SEQUENCE, generator = "hibernate_sequence" ) @SequenceGenerator( name = "hibernate_sequence", allocationSize = 1 ) private Long id;
That’s it!
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
When migrating to Spring Boot 3 or Spring 6, you will automatically migrate to Hibernate 6 as well, and knowing what’s changed in Hibernate can be very useful.
If you’re interested in more tips about this migration, then check out this article as well.
