- vừa được xem lúc

Tránh select khi insert entity trong JPA với Proxy entity

0 0 34

Người đăng: Tong Hoang Vu

Theo Viblo Asia

Hello, mình đã comeback đây. Trước giờ mình hay viết bài về Java và Spring Boot, nay có chủ đề này hay quá nên đá qua JPA một tí. Còn series Spring Boot mình sẽ cố ra nhiều bài hơn, cũng update thêm vài thứ nữa.

Ok quay trở lại chủ đề chính của bài viết. Thực sự mình làm Spring Boot hơn năm rồi, dùng JPA cũng nhiều mà mãi gần đây mới biết đến Proxy entity. Theo mình đây là chủ đề khá hay, nhưng chưa thấy web hay tutorial nào nói tới. Tìm trên google với đúng từ khóa thì ra được vài bài trên Baedung, Vladmihalcea (ông này trùm Hibernate và các thứ liên quan đến database trong Java) với Techmaster.

Phần mở đầu vậy thôi, hãy tiếp tục với bài viết về Proxy entity trong JPA nhé.

1. Đặt vấn đề

1.1. Vấn đề select khi insert

Ví dụ bạn có 2 entity Book và Category (ví dụ kinh điển 😄), một Category có nhiều Book, là mối quan hệ 1-N. Câu chuyện ở đây là khi bạn muốn insert một Book mới vào, bạn sẽ làm như thế nào?

Đúng ra mình sẽ liệt kê danh sách các bước ở đây. Cơ mà lười quá, mình để code ở đây các bạn xem luôn cho lẹ.

@Override
@Transactional
public Book createBook(BookDto dto) { var category = categoryRepository.findById(dto.getCategoryId()) .orElseThrow(() -> new ApiException(ErrorCode.CATEGORY_NOT_FOUND)); var book = new Book(); book.setName(dto.getName()); // and others book.setCategory(category); return bookRepository.save(book);
}

Khi mình mới bắt đầu code Spring Boot cho tới mãi vài tháng trước, mình đã code theo cách này. Thực sự cũng không có gì xấu, cũng rõ ràng, tường minh, dễ hiểu. Vấn đề duy nhất ở đây là lệnh đầu tiên lấy ra Category entity, nó không cần thiết và cũng làm ảnh hưởng performance.

Nếu bạn bật show-sql: true lên, bạn sẽ thấy Hibernate log ra 2 câu SQL, một select Category, một insert Book.

image.png

1.2. Cách xử lí tạm thời

Thực ra cách thì có đó, cũng không quá phức tạp. Đó là dùng custom query để insert book bằng câu native query.

public interface BookRepository { @Query(value = """ insert into books(name) values (:name)""", nativeQuery = true) @Modifying @Transactional void insertBook(String name);
}

Kết quả chỉ có một lệnh SQL insert được chạy, khá tốt đó chứ.

image.png

Nếu số lượng thuộc tính nhiều lên, có thể dùng syntax sau để gom các param lại.

https://stackoverflow.com/a/61200701/13779659

Cơ mà mình không prefer cách này lắm, dễ bug nếu không cấu hình đúng. Như mình khi demo project cũng bị gặp bug này. Phải đổi auto increment strategy của Book.id lại thành IDENTITY mới fix được.

https://stackoverflow.com/a/54697387/13779659

image.png

2. Tìm hiểu proxy entity

3.1. Proxy entity là gì?

Proxy entity có cấu trúc tương tự entity bình thường, nhưng chỉ có khóa chính là được khởi tạo (trong ví dụ là field id). Các field khác đều chưa được đặt giá trị (khi cần sẽ fetch thêm). Do đó, khi lấy ra proxy entity không yêu cầu chạy câu SQL nào cả.

Proxy entity sẽ hữu ích trong trường hợp không cần fetch dữ liệu của entity, ví dụ như trường hợp nối bảng ở đầu bài. Với các hành động khác, như update, delete thì proxy entity không khác gì entity bình thường cả (đều sinh ra thêm câu SQL select).

https://www.baeldung.com/jpa-entity-manager-get-reference

3.2. Cách lấy ra proxy entity?

Trong Hibernate, dùng EntityManager.getReference() để lấy ra proxy entity. JPA thì đơn giản hơn, được tích hợp vào method JpaRepository.getById(). Chỉ cần truyền id vào thì sẽ nhận lại một proxy entity tương ứng.

public interface CategoryRepository extends JpaRepository<Category, Long> { // ...
}
// ...
var categoryProxy = categoryRepository.getById(1L); // do id là Long

Cần lưu ý với JPA chỉ lấy được proxy entity bằng method getById(). Còn các cách khác đều không cho ra proxy entity, luôn sinh ra câu SQL.

// Cách nào cũng không được
public interface CategoryRepository extends JpaRepository<Category, Long> { Optional<Category> getById(Long id); // Override trả về Optional<T> Category getCategoryById(Long id); // Sửa đổi method name Category getByName(String name); // Get thông qua field khác // ...
}

3.1. Áp dụng giải quyết vấn đề

Đọc đến đây các bạn đã hiểu được phần nào về proxy entity rồi. Và cũng không khó để mường tượng ra được cách áp dụng proxy entity để giải quyết vấn đề ở đầu bài.

Thay vì lấy ra cả một entity Category, thì chỉ cần lấy ra proxy entity là được.

Proxy entity sẽ được dùng làm khóa ngoại category_id khi insert Book.

var categoryProxy = categoryRepository.getById(dto.getCategoryId());
// ...
book.setCategory(categoryProxy);
// ...

Và lấy ra proxy entity sẽ không sinh ra câu SQL, nên không còn lệnh select dư thừa nữa (hiệu suất cũng được cải thiện).

3. Hạn chế của proxy entity

Hạn chế lớn nhất của proxy entity là bạn sẽ không biết được entity lấy ra có tồn tại hay không. Mọi field của proxy entity lấy ra được đều null, cả khi entity tồn tại hay không tồn tại trong database.

Theo trên Javadoc thì method này có thể ném EntityNotFoundException, cơ mà hành vi này phụ thuộc vào provider cụ thể. Cá nhân mình dùng method này mà chả thấy nó ném exception gì cả, nên cũng khó xác định được có tồn tại record hay không.

image.png

Do đó, phải dùng thêm method kiểm tra id tồn tại trước, rồi mới lấy ra proxy entity để dùng. Cơ mà tính ra thì vẫn tối ưu hơn cách làm cũ, một câu select count chắc chắn nhanh hơn select cả một entity phải không.

@Override
@Transactional
public Book createBook(BookDto dto) { if (!categoryRepository.existsById(dto.getCategoryId())) { throw new ApiException(ErrorCode.CATEGORY_NOT_FOUND); } var categoryProxy = categoryRepository.getById(dto.getCategoryId()); var book = new Book(); book.setName(dto.getName()); // and others book.setCategory(categoryProxy); return bookRepository.save(book);
}

Đoạn code hoàn chỉnh mình để ở đây nhé.


Bài viết hôm nay tới đây thôi. Hi vọng các bạn đã hiểu được thêm về proxy entity và tránh được sai lầm nêu ra trong bài (nếu có bị rồi thì cũng giúp bạn biết cách khắc phục). Nếu thấy bài hay, hữu ích đừng ngại vote và share nhiều vào nhé nhé. Thanks 😍

Bình luận

Bài viết tương tự

- vừa được xem lúc

Tổng hợp các bài hướng dẫn về Design Pattern - 23 mẫu cơ bản của GoF

Link bài viết gốc: https://gpcoder.com/4164-gioi-thieu-design-patterns/. Design Patterns là gì. Design Patterns không phải là ngôn ngữ cụ thể nào cả.

0 0 302

- vừa được xem lúc

Học Spring Boot bắt đầu từ đâu?

1. Giới thiệu Spring Boot. 1.1.

0 0 278

- vừa được xem lúc

Cần chuẩn bị gì để bắt đầu học Java

Cần chuẩn bị những gì để bắt đầu lập trình Java. 1.1. Cài JDK hay JRE.

0 0 51

- vừa được xem lúc

Sử dụng ModelMapper trong Spring Boot

Bài hôm nay sẽ là cách sử dụng thư viện ModelMapper để mapping qua lại giữa các object trong Spring nhé. Trang chủ của ModelMapper đây http://modelmapper.org/, đọc rất dễ hiểu dành cho các bạn muốn tìm hiểu sâu hơn. 1.

0 0 194

- vừa được xem lúc

[Java] 1 vài tip nhỏ khi sử dụng String hoặc Collection part 1

. Hello các bạn, hôm nay mình sẽ chia sẻ về mẹo check String null hay full space một cách tiện lợi. Mình sẽ sử dụng thư viện Lớp StringUtils download file jar để import vào thư viện tại (link).

0 0 71

- vừa được xem lúc

Deep Learning với Java - Tại sao không?

Muốn tìm hiểu về Machine Learning / Deep Learning nhưng với background là Java thì sẽ như thế nào và bắt đầu từ đâu? Để tìm được câu trả lời, hãy đọc bài viết này - có thể kỹ năng Java vốn có sẽ giúp bạn có những chuyến phiêu lưu thú vị. DJL là tên viết tắt của Deep Java Library - một thư viện mã ng

0 0 139