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

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 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.

If you liked this article, you might want to subscribe to my newsletter too.

Advertisements

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

  1. Excellent, Vlad!

    Simple and elegant solution! I always learn a lot with your examples because I end up learning new possibilities with Hibernate and JPA!

  2. Hi, I’m trying to do this with String instead with a prefix to the id but I can’t get it work 😦 (I’m using H2 and liquibase) a little help will be welcome 🙂

  3. Hi. I’m trying to use the AssignedSequenceStyleGenerator in Wildfly Swarm 2017.6.1 with hibernate 5.0.10.Final.
    When I try to persist(entity) with assigned id I got this error: javax.ejb.EJBTransactionRolledbackException: org.hibernate.PersistentObjectException: detached entity passed to persist.
    If I call persist(entity) without assigned id the insertion is OK.
    If I call merge(entity) with assigned id the insertion is OK too.
    I see in tests in your repository that works with persist for the two cases. Is necessary any additional configuration to persist works with assigned id?

    1. My intuition tells me you haven’t configured it properly. Try to debug it and see if the breakpoint enters the custom identifier generator. Also, this API was set for 5.2 and you should adapt it for 5.0.

      1. Only enters in the breakpoint inside generate method of the AssignedSequenceStyleGenerator when I assign the id and call merge. If I call persist with assigned id don’t stop in the breakpoint.

      2. I checked here to confirm and I’m using the Wrapper (Long). The only difference that make call the generator with persist was the @Version field. I updated my project to use the hibernate 5.2 and got the same behavior. Debugging I found that in org.hibernate.event.internal.DefaultPersistEventListener the state without the version was always EntityState.DETACHED. Adding the @Version field the return was EntityState.PERSIST (on method onPersist).

      3. Hi Vlad. After much debug I found the difference that was generating the detached message. I’m not using the @Version field in my entity. When I put a field annotated with @Version the persist works. But It’s the expected behavior only works with a #Version field? Thanks for the help and the great material.

      4. See my other comment. I guess it’s because you use primitives and Hibernate can’t tel the difference between a transient and a managed entity.

    1. Yes, I could replicate it as well. Unfortunately, there’s nothing you can do about it so the @Version is mandatory for this custom assigned/generate-value identifier generator.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s