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 onLong
,String
, orTSID
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.5hypersistence-utils-hibernate-52
for Hibernate 5.4, 5.3, and 5.2hypersistence-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 forLong
,String
, orTSID
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.

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.
You don’t need to set the nodeId manually. The library can take it via the
tsidcreator.node
System property.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.
You can allocate the nodeId bits to the random part. That way, you won’t worry about how to set the nodeId.
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.