How to implement a custom String-based sequence identifier generator with Hibernate

(Last Updated On: January 3, 2018)

Introduction

One of my blog readers bumped into the assigned generator with a sequence or an identity column post and wondered if it was possible to generate String-based identifiers instead.

I accepted the challenge and answered his question on StackOverflow. However, this post is going to explain this topic in greater detail, so there we go.

The custom identifier generator

We need a Hibernate identifier generator that can take any value that we manually assign, and it can also automatically generate a unique identifier when the entity identifier is null. However, the user does not want to use a UUID-like random identifier. Instead, the user needs to generate a String value that combines a prefix and a numerical value that is obtained from a database sequence.

Our custom identifier generator looks like this:

public class StringSequenceIdentifier 
        implements IdentifierGenerator, Configurable {

    public static final String SEQUENCE_PREFIX = "sequence_prefix";

    private String sequencePrefix;

    private String sequenceCallSyntax;

    @Override
    public void configure(
            Type type, Properties params, ServiceRegistry serviceRegistry)
            throws MappingException {
        final JdbcEnvironment jdbcEnvironment =
                serviceRegistry.getService(JdbcEnvironment.class);
        final Dialect dialect = jdbcEnvironment.getDialect();

        sequencePrefix = ConfigurationHelper.getString(
                SEQUENCE_PREFIX,
                params,
                "SEQ_");

        final String sequencePerEntitySuffix = ConfigurationHelper.getString(
                SequenceStyleGenerator.CONFIG_SEQUENCE_PER_ENTITY_SUFFIX,
                params,
                SequenceStyleGenerator.DEF_SEQUENCE_SUFFIX);

        final String defaultSequenceName = ConfigurationHelper.getBoolean(
                SequenceStyleGenerator.CONFIG_PREFER_SEQUENCE_PER_ENTITY,
                params,
                false)
                ? params.getProperty(JPA_ENTITY_NAME) + sequencePerEntitySuffix
                : SequenceStyleGenerator.DEF_SEQUENCE_NAME;

        sequenceCallSyntax = dialect.getSequenceNextValString(
                ConfigurationHelper.getString(
                        SequenceStyleGenerator.SEQUENCE_PARAM,
                        params,
                        defaultSequenceName));
    }

    @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;
            }
        }
        long seqValue = ((Number) Session.class.cast(session)
            .createSQLQuery(sequenceCallSyntax)
            .uniqueResult()).longValue();

        return sequencePrefix + String.format("%011d%s", 0 ,seqValue);
    }
}

The sequenceCallSyntax holds the underlying database-specific way of calling a sequence. When the generate method is called, we only generate an identifier if the user hasn’t provided a non-nullable value. To construct the String-based identifier, we fetch a new sequence value from the database and concatenate it with the given prefix.

Both the database sequence name and the prefix are configurable, as demonstrated by the entity mapping:

@Entity(name = "Post") @Table(name = "post")
public class Post implements Identifiable<String> {

    @Id
    @GenericGenerator(
        name = "assigned-sequence",
        strategy = "com.vladmihalcea.book.hpjp.hibernate.identifier.StringSequenceIdentifier",
        parameters = {
            @org.hibernate.annotations.Parameter(
                name = "sequence_name", value = "hibernate_sequence"),
            @org.hibernate.annotations.Parameter(
                name = "sequence_prefix", value = "CTC_"),
        }
    )
    @GeneratedValue(
        generator = "assigned-sequence", 
        strategy = GenerationType.SEQUENCE)
    private String id;

    @Version
    private Integer version;

    public Post() {
    }

    public Post(String id) {
        this.id = id;
    }

    @Override
    public String getId() {
        return id;
    }
}

Testing time

With this mapping in place, it’s time to persist several entities and see what identifiers are saved in the database:

doInJPA(entityManager -> {
    entityManager.persist(new Post());
    entityManager.persist(new Post("ABC"));
    entityManager.persist(new Post());
    entityManager.persist(new Post("DEF"));
    entityManager.persist(new Post());
    entityManager.persist(new Post());
});

When running the test case above on PostgreSQL, Hibernate generates the following statements:

SELECT nextval ('hibernate_sequence')
SELECT nextval ('hibernate_sequence')
SELECT nextval ('hibernate_sequence')
SELECT nextval ('hibernate_sequence')

INSERT INTO post (version, id) VALUES (0, 'CTC_000000000001')
INSERT INTO post (version, id) VALUES (0, 'ABC')             
INSERT INTO post (version, id) VALUES (0, 'CTC_000000000002')
INSERT INTO post (version, id) VALUES (0, 'DEF')             
INSERT INTO post (version, id) VALUES (0, 'CTC_000000000003')
INSERT INTO post (version, id) VALUES (0, 'CTC_000000000004')

When running the same test case on Oracle, the sequence syntax changes appropriately:

SELECT hibernate_sequence.nextval FROM dual
SELECT hibernate_sequence.nextval FROM dual
SELECT hibernate_sequence.nextval FROM dual
SELECT hibernate_sequence.nextval FROM dual

INSERT INTO post (version, id) VALUES (0, 'CTC_000000000001')
INSERT INTO post (version, id) VALUES (0, 'ABC')             
INSERT INTO post (version, id) VALUES (0, 'CTC_000000000002')
INSERT INTO post (version, id) VALUES (0, 'DEF')             
INSERT INTO post (version, id) VALUES (0, 'CTC_000000000003')
INSERT INTO post (version, id) VALUES (0, 'CTC_000000000004')

If you enjoyed this article, I bet you are going to love my book as well.

Conclusion

Hibernate is highly extendable, and the identifier generators are no different. From custom types to identifier generators, @Formula, @Where, or @Any mappings, Hibernate allows you to tackle any data binding requirement you may have.

Code available on GitHub.

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

19 thoughts on “How to implement a custom String-based sequence identifier generator with Hibernate

Leave a Reply

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