Giới Thiệu
Trong hướng dẫn này, chúng ta sẽ tìm hiểu về cách xử lý auto-generated id
(id tự động sinh) với JPA (Java Persistence API). Để nắm rõ nội dung, bạn cần hiểu hai khái niệm quan trọng: Vòng đời thực thể và Chiến lược sinh id.
Vòng Đời Thực Thể và Chiến Lược Sinh ID
Mỗi thực thể trong JPA có bốn trạng thái, bao gồm: new
, managed
, detached
và removed
. Tuy nhiên, chúng ta sẽ tập trung vào trạng thái new
và managed
. Trong suốt quá trình khởi tạo đối tượng, thực thể ở trạng thái new
, vì vậy EntityManager
chưa nhận biết được đối tượng này. Sau khi gọi phương thức persist
trên EntityManager
, đối tượng sẽ chuyển từ trạng thái new
sang trạng thái managed
, và phương thức này yêu cầu phải có một giao dịch hoạt động.
JPA định nghĩa bốn chiến lược để sinh id, có thể chia thành hai loại:
- Id được phân bổ trước và có sẵn cho
EntityManager
trước khi giao dịch được xác nhận. - Id được phân bổ sau khi giao dịch được cam kết. Để biết thêm chi tiết, bạn có thể tham khảo bài viết "When Does JPA Set the Primary Key".
Vấn Đề
Việc trả về id của một đối tượng có thể gây ra nhiều khó khăn. Để tránh những vấn đề này, chúng ta cần hiểu rõ các nguyên tắc đã đề cập trước đó. Tùy thuộc vào cấu hình của JPA, các phương thức có thể trả về đối tượng với id là 0 (hoặc null). Vì vậy, chúng ta sẽ tập trung vào việc triển khai lớp Service và cách thức mà các thay đổi khác nhau có thể cung cấp giải pháp.
Chúng ta sẽ tạo một mô-đun Maven với cấu hình JPA và Hibernate, đồng thời sử dụng H2 in-memory database để đơn giản hóa.
Tạo Thực Thể Domain
Đầu tiên, hãy tạo một thực thể User
với một số thuộc tính cơ bản và ánh xạ nó vào bảng cơ sở dữ liệu:
java
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String username;
private String password;
//...
}
Triển Khai Lớp UserService
Tiếp theo, chúng ta sẽ tạo lớp UserService
, bao gồm một tham chiếu đến EntityManager
và một phương thức để lưu đối tượng User
vào cơ sở dữ liệu:
java
public class UserService {
private EntityManager entityManager;
public UserService(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Transactional
public long saveUser(User user) {
entityManager.persist(user);
return user.getId();
}
}
Đây là một lỗi phổ biến mà nhiều người gặp phải. Chúng ta có thể kiểm tra giá trị của phương thức saveUser
bằng một bài kiểm tra:
java
@Test
public void whenNewUserIsPersisted_thenEntityHasNoId() {
User user = new User();
user.setUsername("test");
user.setPassword(UUID.randomUUID().toString());
long index = service.saveUser(user);
Assert.assertEquals(0L, index);
}
Tại Sao Chuyện Này Xảy Ra?
Sở dĩ có hiện tượng này là vì sau khi khởi tạo, thực thể User
của chúng ta đang ở trạng thái new
. Sau khi gọi phương thức persist
, trạng thái của thực thể này chuyển thành managed
. Tuy nhiên, vì phương thức saveUser
chưa kết thúc, giao dịch được tạo bởi chú thích @Transactional
vẫn chưa được xác nhận, dẫn đến việc thực thể managed
chỉ nhận được id sau khi phương thức kết thúc.
Điều Khiển Giao Dịch Thủ Công
Một giải pháp khả thi là gọi phương thức flush
trên EntityManager
theo cách thủ công, hoặc kiểm soát giao dịch bằng thủ công để đảm bảo phương thức trả về id chính xác. Ví dụ, bằng cách sử dụng EntityManager
như sau:
java
@Test
public void whenTransactionIsControlled_thenEntityHasId() {
User user = new User();
user.setUsername("test");
user.setPassword(UUID.randomUUID().toString());
entityManager.getTransaction().begin();
long index = service.saveUser(user);
entityManager.getTransaction().commit();
Assert.assertEquals(2L, index);
}
Sử Dụng Chiến Lược Sinh ID
Trong phần trước, chúng ta đã sử dụng loại thứ hai, trong đó id được phân bổ sau khi giao dịch được xác nhận. Các chiến lược phân bổ trước có thể cung cấp id ngay trước khi thực hiện giao dịch nhờ vào việc lưu trữ một số id trong bộ nhớ. Tùy chọn này không phải lúc nào cũng khả thi, vì không phải tất cả các công cụ cơ sở dữ liệu đều hỗ trợ các chiến lược sinh id này. Để giải quyết vấn đề mà chúng ta gặp phải, ta có thể thay đổi chiến lược thành GenerationType.SEQUENCE
, loại chiến lược này sẽ sử dụng chuỗi cơ sở dữ liệu thay vì cột tự động tăng như GenerationType.IDENTITY
.
Để thay đổi chiến lược, chúng ta cần chỉnh sửa lớp thực thể domain:
java
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
//...
}
Kết Luận
Trong bài viết này, chúng ta đã thảo luận về các kỹ thuật tạo id trong JPA và cách xử lý các vấn đề liên quan đến nó. Bạn có thể tham khảo thêm source code trên GitHub để có thêm thông tin chi tiết.
Nguồn Tham Khảo
Bài viết này được dịch và viết lại dựa trên bài "Returning an Auto-Generated Id with JPA".
source: viblo