1. Overview
When building robust Java applications with PostgreSQL, handling unique identifiers is a fundamental requirement. Instead of relying on auto-incrementing numeric IDs, UUIDs (Universally Unique Identifiers) offer an excellent alternative, particularly in distributed systems.
Over time, generating UUIDs as primary keys has become quite common in Java applications. In this tutorial, we’ll explore how to persist UUIDs in PostgreSQL using JPA (Java Persistence API), focusing on practical implementation with a relatable example of managing users in an application.
2. Setting up PostgreSQL and JPA
Before diving into configuration details, let’s ensure our development environment is ready. First, we’ll need a PostgreSQL database. Then, we’ll set up our Spring Boot project with the necessary Maven dependencies and PostgreSQL-related properties for our application.
2.1. Maven Dependencies
To start using PostgreSQL, we’ll add the driver dependency to our pom.xml file:
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
This dependency adds the PostgreSQL JDBC driver, enabling our application to connect and communicate with the database. It ensures compatibility between our Java application and PostgreSQL.
Next, we’ll also need to add the the spring-data-jpa dependency:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
This dependency brings in Hibernate, Spring Data JPA, and other utilities that allow us to interact with relational databases using Object-Relational Mapping (ORM). It also simplifies common database operations without requiring custom SQL queries.
2.2. PostgreSQL Configuration
To configure the data source, let’s add the necessary properties to our application.properties file:
spring.datasource.url=jdbc:postgresql://localhost:5432/user_management
spring.datasource.username=username
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
This configuration block helps our Spring Boot application connect to the PostgreSQL database by specifying the database details like URL, name, authentication credentials, and Hibernate settings.
3. Configuring JPA for UUIDs
Now that our development environment is ready, let’s focus on configuring JPA to handle UUIDs effectively. We’ll map a UUID column in PostgreSQL and integrate it with our JPA entity.
3.1. UUID Column in PostgreSQL
In PostgreSQL, the UUID type natively supports 128-bit universally unique identifiers, making it an excellent choice for ensuring unique primary keys, especially in distributed systems.
Considering the example of managing users in an application, let’s create a table named users where we leverage a UUID for the primary key:
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE
);
This structure ensures that each user has a globally unique identifier, eliminating the risk of key collisions across different systems. The gen_random_uuid() function generates a new random UUID for the id column by default whenever a record is inserted, simplifying the task of assigning unique primary keys.
3.2. Entity Class
Let’s now create a JPA entity to map rows from the users table:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(columnDefinition = "uuid", updatable = false, nullable = false)
private UUID id;
@Column(nullable = false)
private String username;
@Column(nullable = false, unique = true)
private String email;
// Getters and Setters
}
Here, the id field is annotated with @Id and @GeneratedValue, indicating that it’s the primary key and its value will be automatically generated as a UUID. We also specifically declare this column as a uuid with the columnDefinition.
The username and email fields are mapped to their respective columns and marked as @Column. The email field is also marked as unique to ensure no duplicate email entries are allowed in the table.
3.3. Repository and Service Classes
Next, let’s implement a repository to interact with the database and a service layer to encapsulate business logic for managing the User entities.
The UserRepository will handle database operations:
public interface UserRepository extends JpaRepository<User, UUID> {
}
The UserRepository extends JpaRepository, providing built-in methods for common database operations such as saving, finding, and deleting entities.
The UserService provides business logic for managing users:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User createUser(String name, String email) {
User user = new User();
user.setName(name);
user.setEmail(email);
return userRepository.save(user);
}
public List<User> getAllUsers() {
return userRepository.findAll();
}
public User getUserById(UUID id) {
return userRepository.findById(id).orElse(null);
}
}
The UserService encapsulates the business logic, interacting with the repository to handle user-related operations like saving new users and retrieving existing ones by their UUID.
4. Testing UUID Persistence
We’ll add and execute tests for the UserRepository to ensure proper functionality when persisting and retrieving users with UUIDs. We’ll also verify that the saved entity’s id is a valid UUID.
First, let’s add a test to save a user through UserRepository:
@DataJpaTest
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
public void givenUserEntity_whenSaved_thenIdIsUUID() {
// Create and save a User entity
User user = new User();
user.setName("Alice");
user.setEmail("[email protected]");
// Save the user to the database
User savedUser = userRepository.save(user);
// Verify the saved entity has a valid UUID
assertThat(savedUser.getId()).isNotNull();
assertThat(savedUser.getId()).isInstanceOf(UUID.class);
}
}
Next, let’s add another test in the same class to retrieve a user by ID:
@Test
public void givenSavedUser_whenFindById_thenUserIsRetrieved() {
// Save a user
User user = new User();
user.setName("Jane Smith");
user.setEmail("[email protected]");
User savedUser = userRepository.save(user);
// Retrieve the user by ID
Optional<User> retrievedUser = userRepository.findById(savedUser.getId());
// Verify the user is retrieved correctly
assertThat(retrievedUser).isPresent();
assertThat(retrievedUser.get().getId()).isEqualTo(savedUser.getId());
assertThat(retrievedUser.get().getName()).isEqualTo("Jane Smith");
assertThat(retrievedUser.get().getEmail()).isEqualTo("[email protected]");
// Verify the Id is UUID
assertThat(retrievedUser.get().getId()).isNotNull();
assertThat(retrievedUser.get().getId()).isInstanceOf(UUID.class);
}
With these tests in place, we’ve ensured that our application can reliably store and retrieve users with UUID-based primary keys, confirming the integration of JPA with PostgreSQL’s UUID functionality.
5. Conclusion
In this article, we covered the essential steps to persist UUIDs in PostgreSQL using JPA.
We began by setting up PostgreSQL and integrating JPA to establish a connection between the application and the database. Next, we configured JPA for UUID support by defining a UUID column in PostgreSQL and mapping it in the entity class as the primary key.
We then demonstrated how to store and retrieve UUIDs efficiently using JPA repositories. Finally, we validated the implementation by testing UUID persistence. All these steps ensure that the application remains scalable and secure, and that it adheres to modern best practices.
As always, the implementation’s source code is available over on GitHub.