How to use a JVM or database auto-generated UUID identifier with JPA and Hibernate

(Last Updated On: June 2, 2018)

Introduction

In this article, we are going to see how to use a UUID entity identifier that is auto-generated by Hibernate either in the JVM or using database-specific UUID functions.

Our Post entity looks as follows:

The Post entity has a UUID identifier and a title. Now, let’s see how we can map the Post entity so that the UUID identifier be auto-generated for us.

GenerationType.AUTO

When using JPA, we can use the GenerationType.AUTO strategy of the @GeneratedValue annotation to assign numerical identifier automatically, based on an IDENTITY column, a SEQUENCE or the infamous TABLE generator.

However, few might know that GenerationType.AUTO can be used for UUID identifiers as well:

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

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID id;

    private String title;

    //Getters and setters omitted for brevity
}

Now, when persisting a Post entity:

Post post = new Post();
post.setTitle("High-Performance Java Persistence");

entityManager.persist(post);

Hibernate generates the following SQL INSERT statement:

INSERT INTO post (
    title, 
    id
) 
VALUES (
    'High-Performance Java Persistence', 
    'b5607d38-8fc1-43ef-b44e-34967083c80a'
)

Even batching works as expected:

for (int i = 0; i < 3; i++) {
    Post post = new Post();
    post.setTitle(
        String.format(
            "High-Performance Java Persistence, Part %d", 
            i + 1
        )
    );

    entityManager.persist(post);
}

Hibernate generating a single SQL INSERT statement with 3 bind parameter value sets:

Query:[
    "insert into post (title, id) values (?, ?)"
], 
Params:[
    (High-Performance Java Persistence, Part 1, 7176589b-a3ca-472f-bf00-c253c351ddcc), 
    (High-Performance Java Persistence, Part 2, a4269fb4-07c9-447a-9d65-f443c074de20), 
    (High-Performance Java Persistence, Part 3, e33962a0-d841-48b1-8f43-caf98116f3ee)
]

Database-generated UUID

Now, if we don’t want the UUID to be generated by the JVM, and want to use database-specific functions, we need to provide an implementation of the org.hibernate.id.UUIDGenerationStrategy interface:

public class PostgreSQLUUIDGenerationStrategy 
    implements UUIDGenerationStrategy {

    @Override
    public int getGeneratedVersion() {
        return 4;
    }

    @Override
    public UUID generateUUID(
            SharedSessionContractImplementor session) {
        return ((Session) session).doReturningWork(connection -> {
            try(
                Statement statement = connection.createStatement();
                ResultSet resultSet = statement.executeQuery(
                    "select uuid_generate_v4()" 
                )
            ) {
                while (resultSet.next()) {
                    return (UUID) resultSet.getObject(1);
                }
            }
            throw new IllegalArgumentException("Can't fetch a new UUID");
        });
    }
}

The getGeneratedVersion method defines what type of UUID we are generating, according to the IETF RFC 4122 standard. In our case, 4 stands for variant 4 (random) generator strategy.

Because we are using PostgreSQL, we also need to create the uuid-ossp extension before using the UUID-specific functions:

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

Now, we can call the uuid_generate_v4 function to get a variant 4 UUID based on IETF RFC 4122 specification.

To provide the PostgreSQLUUIDGenerationStrategy to our Post entity, we have to use the Hibernate-specific @GenericGenerator:

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

    @Id
    @GeneratedValue(
        strategy = GenerationType.AUTO, 
        generator = "pg-uuid"
    )
    @GenericGenerator(
        name = "pg-uuid", 
        strategy = "uuid2",
        parameters = @Parameter(
            name = "uuid_gen_strategy_class",
            value = "com.vladmihalcea.book.hpjp.hibernate.identifier.uuid.PostgreSQLUUIDGenerationStrategy"
        )
    )
    private UUID id;

    private String title;

    //Getters and setters omitted for brevity
}

The uuid2 strategy stands for org.hibernate.id.UUIDGenerator which we want to use instead of the legacy org.hibernate.id.UUIDHexGenerator that’s registered under the uuid name in Hibernate.

The @Parameter attribute is used to customize the UUID generation strategy via the uuid_gen_strategy_class parameter value which takes the fully-qualified class name of the org.hibernate.id.UUIDGenerationStrategy interface implementation.

And, that’s it!

Now, when persisting 3 Post entities, Hibernate generates the following SQL statements:

select uuid_generate_v4()
select uuid_generate_v4()
select uuid_generate_v4()

Query:[
    "insert into post (title, id) values (?, ?)"
], 
Params:[
    (High-Performance Java Persistence, Part 1, 9eb52a9b-fb81-4930-b0cd-079a447ed2ba), 
    (High-Performance Java Persistence, Part 2, 2a69ec7d-a147-4c71-8a20-9ba760de0149), 
    (High-Performance Java Persistence, Part 3, e7616832-bb4e-470a-8df4-0534ab56d960)
]

Notice the calls to the uuid_generate_v4 PostgreSQL function which is used to assign the UUID identifier values.

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

Conclusion

Therefore, auto-generating a UUID identifier when using Hibernate is fairly easy.

You can either allow Hibernate to use the Java-based UUID generation strategy or you can delegate this task to the database. The latter option requires providing an implementation of the org.hibernate.id.UUIDGenerationStrategy which is rather straightforward.

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

11 thoughts on “How to use a JVM or database auto-generated UUID identifier with JPA and Hibernate

  1. What about populating the id with UUID.randomUUID() within the entity code, similar to what you showed in the “assigned” example in your previous post?

    This way, we could even use the id field in equals() and hashCode(), if our entity does not have a natural (business) key.

    All the generators populate the id field too late for it to be usable in hashCode, don’t they?

    1. Yes, you can do that too. For using auto-generated Ids in equals and hashCode properly, I have this article which shows how to do that without breaking consistency.

    2. Depends.

      When you are using plain JPA (EntityManager.persist), it might can be possible. I’m not sure about this.

      When you are using spring-data (SimpleJpaRepository.save), you have to be careful. This method delegates to EntityManager.persist if the “entity is new” otherwise it delegates to EntityManager.merge.

      Short definition: An “entity is new” if it’s ID is not empty.
      For details see: https://github.com/spring-projects/spring-data-commons/blob/master/src/main/java/org/springframework/data/repository/core/support/AbstractEntityInformation.java

      1. There is more.

        When “your entity” is new and you call EntityManager.persist, then the “persistent entity” is the same as “your entity”.

        When “your entity” is new and you call SimpleJpaRepository.save, then the “persistent entity” is the object returned by SimpleJpaRepository.save and not “your entity”.

      2. That’s not true. If the entity is really new, Spring will call persist too. Only for assigned identifiers without @Version it assumes this is a detached entity.

      3. At some point, Spring Data was checking the version as well. If they removed that check, then it’s much more difficult to handle assigned identifiers properly.

    1. It is indeed more efficient, but only because you don’t have to generate the UUID using a separate DB roundtrip. If that doesn’t happen too often, it’s not going to be a performance issue either.

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.