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

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.

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

Advertisements

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

  1. Hello,

    I ever wondered if there is a way to create sequence values for a list of objects only once and using it during the “generate” method.

    E.g calling “select level,seq1.nextval from dual connect by level<=10;” for the next 10 objects.

    In other words, is there a way to use only one round-trip to the database for a list of inserts?

    Regards
    Danny

      1. Thanks, i will take a closer look. A hidden gem … nice wording.

        Mhhh, the increment size seems to be hard-coded as annotation, right?

        I was thinking about a way to make the call for ids only if i need to, without holding Ids in memory. E.g. if there are 10 to be (batched) inserted, get the next 10 Ids … if there is one record to be inserted, only get a single id.

        Maybe i must really take a closer look at the hidden gems.

        Danny

  2. Vlad, is there any way to configure the prefix “(i.e., CTC_)” outside of the Entity (For Example: ID- would be prefixed instead of CTC_ so the ID would be ID-00000000)? I have customers that would need to change the prefix for their environment, or even add the year as a prefix before the custom generated value.

    1. Sure, you can!

      For year, it’s very simple since you can just add the logic to the StringSequenceIdentifier.

      As for the global entity identifier use case, you can find an example on GitHub.

      Basically, assuming you have defined the following Hibernate property:

      <property name="entity.identifier.prefix" value="ID_"/>
      

      In the StringSequenceIdentifier, you can load it as follows:

      String globalEntityIdentifierPrefix = configurationService.getSetting( 
          "entity.identifier.prefix", 
          String.class, 
          "SEQ_" 
      );
      

      Then, you can use it whenever you haven’t overridden it on a per entity basis:

      sequencePrefix = ConfigurationHelper.getString(
          SEQUENCE_PREFIX,
          params,
          globalEntityIdentifierPrefix
      );
      

      Brilliant, right?

      1. Thank you for the quick response. What version are you using? I don’t have the following configure method, so I don’t have access to the ServiceRegistry.

        @Override
        public void configure(
        Type type, Properties params, ServiceRegistry serviceRegistry)
        throws MappingException {

      1. Actually I wanted to use user input to create an id using IdentifierGenerator. I found out that the entity is passed on to the generate method as object. So I could get the data from there. Thanks!

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