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 20-21 and 23-24 of November about High-Performance Java Persistence.

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.

Transactions and Concurrency Control eBook

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.