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

(Last Updated On: January 29, 2018)

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.

Subscribe to our Newsletter

* indicates required
10 000 readers have found this blog worth following!

If you subscribe to my newsletter, you'll get:
  • A free sample of my Video Course about running Integration tests at warp-speed using Docker and tmpfs
  • 3 chapters from my book, High-Performance Java Persistence, 
  • a 10% discount coupon for my book. 
Get the most out of your persistence layer!

Advertisements

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

  1. I am using IdentityGenerator approach but, in that case, the super.generate function returns “POST_INSERT_INDICATOR”, and I get a SQLException with this message: Field 'id' doesn't have a default value.

  2. It appears that with hibernate 5.2.8 the assigned identity generator sample you have hear no longer works. I have been looking around for a way to accomplish this without success. I have an application with 2 databases that i’d like to share my jpa model across. However, only one of them is the master where we generate the identity ids. The second we populate from entities passed from the master that already have an id.

      1. There is no generate method to override in org.hibernate.id.IdentityGenerator in the hiberate-core-5.2.8.Final.jar

      2. Thanks for the great sample. I suspect that my issue has to do with using Spring Boot and the CrudRepository. The save of the entity doesn’t seem to utilize my GenericGenerator. The SQL output always contains default in the Id value.

Leave a Reply

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