0
0
Lập trình
Admin Team
Admin Teamtechmely

Hướng Dẫn Chi Tiết Về Cách Sử Dụng Auto-Generated ID Với JPA

Đăng vào 1 tháng trước

• 4 phút đọc

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, detachedremoved. Tuy nhiên, chúng ta sẽ tập trung vào trạng thái newmanaged. 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 Copy
@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 Copy
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 Copy
@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 Copy
@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 Copy
@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

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào