Heuristic Services     About     Archive     Feed

Implementing Postgres UUID primary keys with Spring Boot and Hibernate

An alternative to auto-increment sequential primary keys, is using a Universally Unique Identifier. Spring Boot and Hibernate support them and you can use them using Spring Data’s autogenerated SQL, JPQL and native queries.

As a demonstration project, you can clone spring-boot-hibernate-postgres-uuids.

To run the examples, you’ll need docker and docker-compose installed. You can setup the project by running docker-compose up in the docker directory. Doing this will create a new database, and automatically install the uuid-ossp extension.

Creating the Entity model

The project uses a User model as an example. You can setup Hibernate to use a UUID for its entity ID by using the annotations below.

@Entity
@Table(name = "users", schema = "spring_boot_uuids", uniqueConstraints = {
        @UniqueConstraint(columnNames = "email", name = "UNIQUE_EMAIL")
})
public class User
{
    @Id
    @Column
    @Type(type="pg-uuid")
    @GenericGenerator(name = "uuid-gen", strategy = "uuid2")
    @GeneratedValue(generator = "uuid-gen")
    private UUID id;

    [snip]
}

Creating the Spring Data Repository

You can create a CrudRepository using a UUID as the identifier.

@Repository
public interface UserRepository extends CrudRepository<User, UUID>
{
    [snip]
}

Now you can use the CrudRepository autogenerated retrieve functions.

    @Test
    public void savesUserWithId()
    {
        User user = new User();
        user.setEmail(TEST_EMAIL_ADDRESS);
        User persistedUser = userRepository.save(user);
        assertThat(persistedUser.getId(), is(not(nullValue())));
    }

If you create a projection, it will work with that too.

public interface UserDisplay
{
    UUID getId();

    String getEmail();

    @Value("#{T(java.time.Period).between(target.dateOfBirth, T(java.time.LocalDate).now()).getYears()}")
    Integer getAge();
}

You can use the projection in combination with a JPQL query,

    @Query("SELECT u.id AS id, " +
           "u.email AS email, " +
           "u.dateOfBirth AS dateOfBirth " +
           "FROM User u WHERE u.id = :userId")
    Optional<UserDisplay> getDisplayById(@Param("userId") UUID userId);

and a native query:

    @Query(value = "SELECT CAST(u.id AS VARCHAR) AS id, " +
            "u.email AS email," +
            "u.date_of_birth AS dateOfBirth " +
            "FROM spring_boot_uuids.users u " +
            "WHERE u.id = :userId", nativeQuery = true)
    Optional<UserDisplay> getNativeDisplayById(@Param("userId") UUID userId);

You’ll notice that the native query requires that the UUID be cast to a VARCHAR datatype. This is because currently Hibernate does not correctly recognise the raw UUID type.