Auto-Generated Id với JPA
Intro
hướng dẫn này nói về cách sử lý auto-generated id
với JPA. có 2 khái niệm cần hiểu trước khi xem ví dụ ở dưới gồm: vòng đời và chiến lượt sinh id (id generation strategy).
Vòng đời và chiến lược sinh id
Mỗi entity có các trạng thái sau trong vòng đời của nó gồm: new
, managed
, detached
và removed
. Chúng ta chỉ quan tâm đến new
và managed
. Trong suốt quá trình tạo đối tượng, thực thể ở trạng thái new
..
Do đó, EntityManager
không biết về đối tượng này. 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
. Phương pháp này yêu cầu một transaction hoạt động.
JPA định nghĩa bốn chiến lược để tạo id. Chúng ta có thể nhóm bốn chiến lược này thành hai loại:
-
Id được phân bổ trước và có sẵn cho EntityManager trước khi xác nhận
-
Id được phân bổ sau khi cam kết giao dịch Để biết thêm chi tiết về từng chiến lược tạo id, hãy tham khảo bài viết When Does JPA Set the Primary Key.
Vấn đề
Trả về id của một đối tượng có thể trở thành một nhiệm vụ nặng nề. Chúng ta cần hiểu các nguyên tắc được đề cập ở phần trước để tránh các vấn đề. Tùy thuộc vào cấu hình JPA, các dịch vụ có thể trả về các đối tượng có id bằng 0 (hoặc null). Trọng tâm sẽ là việc triển khai lớp Service và cách các sửa đổi khác nhau có thể cung cấp cho chúng ta giải pháp.
Tạo một mô-đun Maven với đặc tả JPA và Hibernate. Để đơn giản, tôi sẽ sử dụng H2 in-memory database.
Hãy bắt đầu bằng cách tạo một domain entity và ánh xạ nó vào bảng cơ sở dữ liệu. Trong ví dụ này, tôi sẽ tạo một thực thể User với một số thuộc tính cơ bản:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String username;
private String password;
//...
}
Sau domain class, chúng ta sẽ tạo lớp UserService. Lớp này sẽ có một tham chiếu đến EntityManager và một phương thức để lưu các đối tượng User vào cơ sở dữ liệu:
public class UserService {
EntityManager entityManager;
public UserService(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Transactional
public long saveUser(User user){
entityManager.persist(user);
return user.getId();
}
}
Thiết lập này là một lỗi phổ biến đã đề cập trước đây. Chúng ta có thể chứng minh rằng giá trị trả về của phương thức saveUser bằng 0 bằng một bài test.
@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);
}
Tiếp theo sẽ tiều hiểu tại sao nó lại xảy ra và cách xử lý nó.
Điều khiển Transaction thủ công
Sau khi tạo đối tượng, thực thể User của chúng ta ở trạng thái mới. Trạng thái thực thể thay đổi thành được quản lý sau lệnh gọi phương thức persist (là phương thức lưu entity vào) trong phương thức saveUser. Chúng tôi nhớ từ phần tóm tắt rằng đối tượng được quản lý sẽ nhận được id sau khi thực hiện transaction. Vì phương thức saveUser vẫn đang chạy nên giao dịch được tạo bởi chú thích @Transactional vẫn chưa được commit. Thực thể được quản lý của chúng tôi nhận được id khi saveUser kết thúc quá trình thực thi.
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. Mặt khác, chúng tôi có thể kiểm soát các giao dịch theo cách thủ công và đảm bảo rằng phương thức của chúng tôi trả về id chính xác. Chúng ta có thể làm điều này với EntityManager:
@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
Cho đến thời điểm hiện tại, tôi đã sử dụng loại thứ hai, trong đó việc phân bổ id xảy ra sau khi commit transaction. Các chiến lược phân bổ trước có thể cung cấp cho chúng ta id trước khi thực hiện giao dịch vì chúng giữ một số id trong bộ nhớ. Tùy chọn này không phải lúc nào cũng có thể triển khai được vì không phải tất cả các công cụ cơ sở dữ liệu đều hỗ trợ tất cả các chiến lược tạo. Việc thay đổi chiến lược thành GenerationType.SEQUENCE
có thể giải quyết vấn đề của chúng tôi. Chiến lược này sử dụng chuỗi cơ sở dữ liệu thay vì cột tăng tự động như trong GenerationType.IDENTITY.
Để thay đổi chiến lược, chúng tôi chỉnh sửa lớp domain entity của mình:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
//...
}
Conclusion
Trong bài viết này đã đề cập đến các kỹ thuật tạo id trong JPA. source ở Github
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 na Returning an Auto-Generated Id with JPA
All rights reserved