Hibernate and UUID identifiers

(Last Updated On: January 29, 2018)

Introduction

In my previous post I talked about UUID surrogate keys and the use cases when there are more appropriate than the more common auto-incrementing identifiers.

A UUID database type

There are several ways to represent a 128-bit UUID, and whenever in doubt I like to resort to Stack Exchange for an expert advice.

Because table identifiers are usually indexed, the more compact the database type the less space will the index require. From the most efficient to the least, here are our options:

  1. Some databases (PostgreSQL, SQL Server) offer a dedicated UUID storage type
  2. Otherwise we can store the bits as a byte array (e.g. RAW(16) in Oracle or the standard BINARY(16) type)
  3. Alternatively we can use 2 bigint (64-bit) columns, but a composite identifier is less efficient than a single column one
  4. We can store the hex value in a CHAR(36) column (e.g 32 hex values and 4 dashes), but this will take the most amount of space, hence it’s the least efficient alternative

Hibernate offers many identifier strategies to choose from and for UUID identifiers we have three options:

  • the assigned generator accompanied by the application logic UUID generation
  • the hexadecimal “uuid” string generator
  • the more flexible “uuid2” generator, allowing us to use java.lang.UUID, a 16 byte array or a hexadecimal String value

The assigned generator

The assigned generator allows the application logic to control the entity identifier generation process. By simply omitting the identifier generator definition, Hibernate will consider the assigned identifier. This example uses a BINARY(16) column type, since the target database is HSQLDB.

@Entity(name = "assignedIdentifier")
public class AssignedIdentifier {

    @Id
    @Column(columnDefinition = "BINARY(16)")
    private UUID uuid;

    public AssignedIdentifier() {
    }

    public AssignedIdentifier(UUID uuid) {
        this.uuid = uuid;
    }
}

Persisting an Entity:

session.persist(new AssignedIdentifier(UUID.randomUUID()));

Generates exactly one INSERT statement:

Query:{[insert into assignedIdentifier (uuid) values (?)][[B@76b0f8c3]}

Let’s see what happens when issuing a merge instead:

session.merge(new AssignedIdentifier(UUID.randomUUID()));

We get both a SELECT and an INSERT this time:

Query:{[select assignedid0_.uuid as uuid1_0_0_ from assignedIdentifier assignedid0_ where assignedid0_.uuid=?][[B@23e9436c]} 

Query:{[insert into assignedIdentifier (uuid) values (?)][[B@2b37d486]} 

The persist method takes a transient entity and attaches it to the current Hibernate session. If there is an already attached entity or if the current entity is detached we’ll get an exception.

The merge operation will copy the current object state into the existing persisted entity (if any). This operation works for both transient and detached entities, but for transient entities persist is much more efficient than the merge operation.

For assigned identifiers, a merge will always require a select since Hibernate cannot know if there is already a persisted entity having the same identifier. For other identifier generators, Hibernate looks for a null identifier to figure out if the entity is in the transient state.

That’s why the Spring Data SimpleJpaRepository#save(S entity) method is not the best choice for Entities using an assigned identifier:

@Transactional
public <S extends T> S save(S entity) {
    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

For assigned identifiers, this method will always pick merge instead of persisting, hence you will get both a SELECT and an INSERT for every newly inserted entity.

The UUID generators

This time, we won’t assign the identifier ourselves but have Hibernate generate it on our behalf. When a null identifier is encountered, Hibernate assumes a transient entity, for whom it generates a new identifier value. This time, the merge operation won’t require a select query prior to inserting a transient entity.

The UUIDHexGenerator

The UUID hex generator is the oldest UUID identifier generator and it’s registered under the “uuid” type. It can generate a 32 hexadecimal UUID string value (it can also use a separator) having the following pattern: 8{sep}8{sep}4{sep}8{sep}4.

This generator is not IETF RFC 4122 compliant, which uses the 8-4-4-4-12 digit representation.

@Entity(name = "uuidIdentifier")
public class UUIDIdentifier {

    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid")
    @Column(columnDefinition = "CHAR(32)")
    @Id
    private String uuidHex;
}

Persisting or merging a transient entity:

session.persist(new UUIDIdentifier());
session.merge(new UUIDIdentifier());

Generates one INSERT statement per operation:

Query:{[insert into uuidIdentifier (uuidHex) values (?)][2c929c6646f02fda0146f02fdbfa0000]} 

Query:{[insert into uuidIdentifier (uuidHex) values (?)][2c929c6646f02fda0146f02fdbfc0001]} 

You can check out the string parameter value sent to the SQL INSERT queries.

The UUIDGenerator

The newer UUID generator is IETF RFC 4122 compliant (variant 2) and it offers pluggable generation strategies. It’s registered under the “uuid2” type and it offers a broader type range to choose from:

@Entity(name = "uuid2Identifier")
public class UUID2Identifier {

    @GeneratedValue(generator = "uuid2")
    @GenericGenerator(name = "uuid2", strategy = "uuid2")
    @Column(columnDefinition = "BINARY(16)")
    @Id
    private UUID uuid;
}

Persisting or merging a transient entity:

session.persist(new UUID2Identifier());
session.merge(new UUID2Identifier());

Generates one INSERT statement per operation:

Query:{[insert into uuid2Identifier (uuid) values (?)][[B@68240bb]} 

Query:{[insert into uuid2Identifier (uuid) values (?)][[B@577c3bfa]}

These SQL INSERT queries are using a byte array as we configured the @Id column definition.

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

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

4 thoughts on “Hibernate and UUID identifiers

  1. you mention UUIDGenerator/uuid2 can support all three of
    java.lang.UUID
    16 byte array
    hexadecimal String value.

    How can I get hexadecimal String value of 32 chars instead of 36 when using uuid2.

    1. The uuid generators transforms the UUID object to a String like this:

      public String transform(UUID uuid) {
      	return uuid.toString();
      }
      

      If you want a different representation, you need to extend the UUIDGenerator and provide your own implementation. Check out this article for more details about extending a Hibernate Type.

      Nevertheless, using a String column for UUID is not very efficient, so you are better off using the byte[] storage instead, especially if the column is a PK with FKs in other tables.

  2. Hi,
    lead this not to an error?
    @Column(columnDefinition = “CHAR(32)”)

    For UUID as string you need an column with min. 36 characters.
    32 chars + 4 separators

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.