How to combine the Hibernate assigned generator with a sequence or an identity column

Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. Hypersistence Optimizer is that tool!

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)

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.

FREE EBOOK

6 Comments on “How to combine the Hibernate assigned generator with a sequence or an identity column

  1. Hi Vlad, thanks for the article! It’s been very helpful. One thing I can’t seem to get working is having the generated value pull from a specific sequence in my database. I’ve followed your examples above and tried swapping in the name of my database sequence where you used “post_sequence” and/or “sequence_name”, but neither works–I keep getting the “Schema-validation: missing sequence [hibernate_sequence]” error that indicates Hibernate is looking for “hibernate_sequence” and not the one I created in my database. Actually creating a sequence in my database called “hibernate_sequence” makes the problem go away, but I need to be able to customize the sequence name so that I can use more than one sequence in my database. Any ideas?

    • First, you should not rely on hbm2ddl. You should use FlywayDB.

      Second, the hibernate_sequence is the default schema, meaning that Hibernate expects that unless you specify the sequence name explicitly.

      The code is on GitHub, so it’s easy to test and modify it.

  2. Identifiable does not appear to be in java 7… I have a generated value for the ID using strategy “uuid2”. I want to be able to only generate it if I don’t provide it. Can you help me out with this?

    • You can find all the cide in my High-Performance Java Persistence GitHub repository. Yes, I can help. If you buy my High-Performance Java Persistence Mach 2 video course, I’ll offer you a 30 minutes consulting session for free and help you with your problem.

  3. which strategy to use with mysql in case of batch processing?
    Thanks

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.