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

6 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 🙂

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