Vấn đề N+1 câu truy vấn trong Hibernate

Tram Ho

Hibernate là một khung công tác (framework) ORM(Object Relational Mapping) phổ biến trong hệ sinh thái ngôn ngữ lập trình Java. Hiện đang là khung công tác phổ biến không có nghĩa là không cónhững yếu tố khó khăn khi sử dụng.
Các khung ORMs thân thiệt với các lập trình viên và rất nhiều lập trình viên đã sử dụng chúng để nhanh chóng khởi dựng được ứng dụng và thao tác với cơ sở dữ liệu mà không cần phải viết các câu lệnh truy vấn cơ sở dữ liệu.Nhưng nó có thể dẫn đến nhiều vấn đề nếu các lập trình viên không quen với các chức năng cơ bản của khung. Và vấn đề n+1 câu truy vấn là một vấn đề như vậy.
Đây cũng là một câu hỏi mình từng được hỏi trong một cuộc phỏng vấn. Có một bài viết nhất hay về cách giải quyết vấn đề này và mình xin được chia sẻ lại với các bạn.

1. Vấn đề n+1 câu truy vấn

Nếu bạn đã sử dụng Hibernate( hay bất kì khung ORM nào khác ), rất có thể bạn đã gặp phải vấn đề n+1 khét tiếng hết lần này đến lần khác.
Vấn đề n+1 câu truy vấn xảy ra khi trong khi bạn load dữ liệu ở chế độ FetchType.LAZY trong One-to-Many ở mối quan hệ cha con:
1 câu truy vấn để lấy ra n đối tượng cha
Mất thêm n câu truy vấn(mỗi câu cho một đối tượng tra) để lấy ra các đối tượng con

2. Ví dụ minh họa

Môi trường là : Spring Boot, JPA(Hibernate), H2 database.
Chúng ta tạo ra một cơ sở dữ liệu với 5 tác giả(author) và 25 quyển sách(book). Mỗi tác giả gán với 5 quyển sách

Đó là một phần của code để in ra tên của tác giả và số lượng sách liên quan
Ở đây ta sẽ có logs:

Như bạn có thể thấy có 1 một lệnh truy vấn được thực thi để lấy ra danh dách tác giả và sau đó mỗi truy vấn riêng được thực hiện lấy sách cho mỗi tác giả, kêt quả là có thêm 6 câu truy vấn được thực hiên. Đây là hành vi mặc định trong Hibernate , theo mình nó vẫn ổn miễn là bạn lấy cho sách cho một tác giả.
Nhưng bây giờ hay xem xet ảnh hưởng của vấn đề này trong một ứng dựng thực tế, khi mà dữ liệu không nằm cùng trên một máy chủ duy nhất và có hàng nghìn bản ghi. Nó sẽ tốt rất nhiều hiệu suất của bạn khi thực hiên của bạn đấy.

3. Sử dụng Fetch Mode

Trong Hibernate cung cấp chú thích(anotation) Fetch(…) có thể được sử dụng để xác định cách tìm nạp tập hợp các đối tượng liên quan(như nạp đối tượng sách cho đối tượng tác giả ở ví dụ trên)

FetchMode.SUBSELECT

  • “sử dụng một câu truy vấn phụ để tải các bộ sưu tập bổ sung”* — SUBSELECT

    Việc sử dụng Fetch(FetchMode.SUBSELECT) sẽ chỉ đưa ra một truy vấn bổ sung để lấy các quyển sách.
    Sửa lại lớp Author.java như sau:

Và giờ chúng ta sẽ có log:

Chúng ta có thể thấy chỉ có hai câu truy vấn được thực hiện , một cho việc lấy ra danh sách tác giả, một cho việc lấy ra các quyển sách liên quan tới những tác giả này.
JOIN FETCH
Điều gì sẽ xảy ra nếu chúng ta muốn tìm nạp tất cả các tác giả và sách của họ trong một câu truy vấn duy nhất ?
Nếu muốn viết một câu truy vấn cho mục đích này, tôi sẽ thực hiện join các bảng lại với nhau.
Hãy thực hiện điều đó bằng một cách của ORM thân thuộc với các lập trình viên bằng việc sử dụng chú thích Query.
Sử lại lớp AuthorDataService.java như sau:

Giờ ta được log như sau:

Vấn đề đã được giải quyết! Hâu như việc sử dụng ** JOIN FETCH** giải quyết được vấn đề nhiều câu truy vấn bằng cách sử dụng join nhưng nó lại trả về các bản ghi trùng nhau cho mỗi Author. Nhưng đó là những gì là join được cho là phải làm

Thực hiện truy vấn trên cho kết quả như sau, trong đó mỗi tác giả được sao chép với dữ liệu của họ

Giải quyết cho vấn đề lặp lại bản ghi là thêm vào DISTINCT vào trong câu truy vấn:

Logs:

4. Kết luận

Như vậy qua bài viết này mình đã trình bày cho các bạn vấn n+1 câu truy vấn cũng như cách giải quyết trong Hibernate. Cảm hơn các bạn đã dành thời gian đọc và khi vọng nó giúp ích được cho các bạn!
tài liệu tham khảo: https://medium.com/@mansoor_ali/hibernate-n-1-queries-problem-8a926b69f618

Chia sẻ bài viết ngay

Nguồn bài viết : Viblo