The best way to generate a TSID entity identifier with JPA and Hibernate

Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Wouldn’t that be just awesome?

Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.

So, enjoy spending your time on the things you love rather than fixing performance issues in your production system on a Saturday night!

Introduction

In this article, I will show you the best way to generate a TSID entity identifier with JPA and Hibernate.

Prior to continuing, if you are not familiar with the advantages of using compact Time-Sorted Identifiers, like TSID, check out this article first.

Hypersistence Utils

As promised, the Hypersistence Utils OSS library keeps on evolving, adding more and more utilities to help you implement a high-performance data access layer.

Starting with the 3.2 version, Hypersistence Utils provides a TSID entity identifier generator that can automatically assign time-sorted identifiers when you persist a given entity.

Depending on the Hibernate version you are using, there are two ways you can use this TSID entity identifier generator.

Generate a TSID entity identifier with JPA and Hibernate 6

If you’re using Hibernate 6, then you first need to add the hypersistence-utils-hibernate-60 dependency to your Maven pom.xml file:

<dependency>
    <groupId>io.hypersistence</groupId>
    <artifactId>hypersistence-utils-hibernate-60</artifactId>
    <version>${hypersistence-utils.version}</version>
</dependency>

Afterward, you can use the @Tsid annotation right next to the @Id annotation, as illustrated by the following entity mapping:

@Entity(name = "Post")
@Table(name = "post")
public class Post {

    @Id @Tsid
    private Long id;

    private String title;

    public Long getId() {
        return id;
    }

    public Post setId(Long id) {
        this.id = id;
        return this;
    }

    public String getTitle() {
        return title;
    }

    public Post setTitle(String title) {
        this.title = title;
        return this;
    }
}

And, when we persist the Post entity:

entityManager.persist(
    new Post()
        .setTitle("High-Performance Java Persistence")
);

Hibernate will generate the following SQL INSERT statement:

INSERT INTO post (
    title, 
    id
) 
VALUES (
    'High-Performance Java Persistence', 
    414142131136227703
)

The @Tsid annotation works on Long, String, or TSID entity identifier types and generates the appropriate time-sorted Object type.

Generate a TSID entity identifier with JPA and Hibernate 5

If you’re using Hibernate 5, then you need to add one of the following dependencies to your project:

  • hypersistence-utils-hibernate-55 for Hibernate 5.6 and 5.5
  • hypersistence-utils-hibernate-52 for Hibernate 5.4, 5.3, and 5.2
  • hypersistence-utils-hibernate-5 for Hibernate 5.1 and 5.0

For Hibernate 5.6, we will first add the hypersistence-utils-hibernate-55 dependency like this:

<dependency>
    <groupId>io.hypersistence</groupId>
    <artifactId>hypersistence-utils-hibernate-55</artifactId>
    <version>${hypersistence-utils.version}</version>
</dependency>

Afterward, you can use the TsidGenerator identifier generator, as illustrated by the following entity mapping:

@Entity(name = "Post")
@Table(name = "post")
public class Post {

    @Id
    @GeneratedValue(generator = "tsid")
    @GenericGenerator(
        name = "tsid",
        strategy = "io.hypersistence.utils.hibernate.id.TsidGenerator"
    )
    private Long id;

    private String title;

    public Long getId() {
        return id;
    }

    public Post setId(Long id) {
        this.id = id;
        return this;
    }

    public String getTitle() {
        return title;
    }

    public Post setTitle(String title) {
        this.title = title;
        return this;
    }
}

And, when we persist the Post entity:

entityManager.persist(
    new Post()
        .setTitle("High-Performance Java Persistence")
);

Hibernate will generate the following SQL INSERT statement:

INSERT INTO post (
    title, 
    id
) 
VALUES (
    'High-Performance Java Persistence', 
    414147868608769831
)

The TsidGenerator generator can assign time-sorted values for Long, String, or TSID entity identifier types.

How to customize the TSID value generator

If you want to customize the TSID generation, then you can provide a class that extends the Supplier<TSID.Factory>, like this one:

public static class CustomTsidSupplier 
    implements Supplier<TSID.Factory> {

    @Override
    public TSID.Factory get() {
        return TSID.Factory.builder()
            .withNodeBits(1)
            .build();
    }
}

For Hibernate 6, the CustomTsidSupplier can be provided via the @Tsid annotation:

@Id
@Tsid(CustomTsidSupplier.class)
private Long id;

And for Hibernate 5, like this:

@Id
@GeneratedValue(generator = "tsid")
@GenericGenerator(
    name = "tsid",
    strategy = "io.hypersistence.utils.hibernate.id.TsidGenerator",
    parameters = @Parameter(
        name = TsidGenerator.TSID_FACTORY_SUPPLIER_PARAM,
        value = "io.hypersistence.utils.hibernate.id.CustomTsidSupplier"
    )
)
private Long id;

That’s it!

I'm running an online workshop on the 11th of October about High-Performance SQL.

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

While you can also assign the TSID identifier value manually prior to persisting the JPA or Hibernate entity, using the Hypersistence Utils @Tsid or the TsidGenerator is even more convenient.

If you want to give it a try, check out the TsidIdentifierTest integration test in the Hypersistence Utils project.

Transactions and Concurrency Control eBook

6 Comments on “The best way to generate a TSID entity identifier with JPA and Hibernate

  1. Another thing that I se is that to some extent you need to configure a node ID. In an auto scaling cluster that will problematic. Best would be if that is taken care of by the library.

      • Hi

        I was really meaning that it is not feasible to set a node ID regardless of system property or not in a cluster environment which can auto scale up and down.
        It would best if the lib works in a cluster without the need to manage a node ID at all.

        So if all nodes are using the same node ID I suppose the probability of conflicts will be higher and if as you suggested to use a @Retry will mitigate it . that is a good candidate.

  2. Hi

    I notice that your implementation in io.hypersistence.utils.hibernate.id.TsidGenerator in
    hypersistence-utils-hibernate-55-3.2.0.jar is using TSID.Factory.THREAD_LOCAL_RANDOM_FUNCTION.

    But in https://fillumina.wordpress.com/2023/01/19/how-to-not-use-tsid-factories/ the author recommends that:

    “Within the same node all threads should use the same shared factory.
    In case of a multi node system nodes should use TsidFactory.newInstance1024(nodeId) (or equivalent depending on the desired maximum nodes) while within the same node all threads should use the same shared factory with the same node-id.
    Because the factory TSID generator method is synchronized (for good reasons) there is no need to use a ThreadLocalRandom custom generator (as hinted in the docs) that, running inside a synchronized block, will not improve the performances in any way (case 7).”

    So what are your comments about that?

    • The original library has a synchonized method so all threads will bottleneck there, as explained by this issue.

      My fork takes a different approach. It favors performance at the cost of a tiny probability of ID conflicts, which could be addressed via the @Retry annotation offered by Hypersistence Utils.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.