Điểm khác nhau giữa Lazy và Eager trong Hibernate và JPA

Đối với các ứng dụng sử dụng JPA/Hibernate thì các tác vụ liên quan đến truy vấn lấy dữ liệu từ database xảy ra rất thường xuyên. Đây là một điểm khá quan trọng mà Hibernate phải làm thật tốt để nâng cao hiệu năng cho ứng dụng. Khái niệm về FetchType được Hibernate đưa ra nhầm giải quyết việc lấy dữ liệu từ các bảng có mối quan hệ với nhau.

FetchType gồm 2 loại chính là LazyEager đều có cách sử dụng khác nhau trong các trường hợp cụ thể. Chúng ta cùng tìm hiểu chúng ngay sau đây.

Default FetchType

Khi bạn mới bắt đầu với JPA – Hibernate, thông thường khi ánh xạ entity bạn sẽ không cần quan tâm đến FetchType vì các giá trị mặc định của nó thường phù hợp với các nhu cầu cơ bản. Trong trường hợp bạn không chỉ định FetchType thì giá trị mặc định thường là Lazy cho mối quan hệ many-to-manymany-to-oneEager cho one-to-one.

Mặc dù các giá trị mặc định của FetchType thường phù hợp trong đa số trường hợp nhưng nếu muốn thay đổi chúng ta vẫn có thể thay đổi nếu muốn. Tất nhiên là thay đổi có chủ đích nhé, chứ đừng có làm bừa.

@Entity
@Table(name = "PurchaseOrder")
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @OneToMany(mappedBy = "order", fetch = FetchType.EAGER)
    private Set<Item> items = new HashSet<Item>();
    // getter, setter, constructor 
}

OK, giờ chúng ta sẽ tìm hiểu chi tiết xem mỗi FetchType có ý nghĩa gì trong JPA – Hibernate.

FetchType.Eager

Khi FetchType.Eager được sử dụng, Hibernate sẽ lấy tất cả các entity có quan hệ với root entity (entity được select trong câu truy vấn). Ví dụ mình có mối quan hệ many-to-one giữa OrderItem được ánh xạ như sau.

@Entity
@Table(name = "PurchaseOrder")
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @OneToMany(mappedBy = "order", fetch = FetchType.EAGER)
    private Set<Item> items = new HashSet<Item>();

    public Set<Item> getItems() {
        return items;
    }
}
@Entity
@Table(name = "Item")
public class Item {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @ManyToOne
    private Order order;
}

Khi mình lấy một dòng dữ liệu trong bảng OrderID=1 từ database, Hibernate cũng sẽ lấy cả các Item có quan hệ many-to-one với nó.

Order order = em.find(Order.class, 1L);

Output

Hibernate: 
    select
        order0_.id as id1_1_0_,
        items1_.order_id as order_id2_0_1_,
        items1_.id as id1_0_1_,
        items1_.id as id1_0_2_,
        items1_.order_id as order_id2_0_2_ 
    from
        PurchaseOrder order0_ 
    left outer join
        Item items1_ 
            on order0_.id=items1_.order_id 
    where
        order0_.id=?

Các bạn có thể thấy câu truy vấn được sinh ra bởi Hibernate thực hiện phép JOIN SELECT các Item cùng với Order mà chúng thuộc về. Việc này có thể tốt trong thời gian đầu khi dữ liệu còn ít. Nhưng khi dữ liệu trở nên lớn hơn, việc lấy toàn bộ các Item như vậy có thể ảnh hưởng xấu đến hiệu xuất của chương trình, nhất là khi lấy mà không cần sử dụng đến.

FetchType.Lazy

Đây là FetchType mà Hibernate khuyên nên sử dụng, Hibernate chỉ lấy các entity có quan hệ với root entity khi cần thiết. Ví dụ mình thay đổi ánh xạ của Order thành FetchType.Lazy với Item.

@Entity
@Table(name = "PurchaseOrder")
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
    private Set<Item> items = new HashSet<Item>();

    public Set<Item> getItems() {
        return items;
    }
}

Khi thực hiện lấy một dòng dữ liệu trong bảng Order với ID=1, thì Hibernate chỉ lấy dữ liệu trong bảng Order tương ứng mà không đụng đến các dữ liệu của các bảng có quan hệ.

Order order = em.find(Order.class, 1L);

Output

Hibernate: 
    select
        order0_.id as id1_1_0_ 
    from
        PurchaseOrder order0_ 
    where
        order0_.id=?

FetchType.Lazy sẽ không ảnh hưởng đến logic code của bạn, chúng ta hoàn toàn có thể lấy những Item thuộc về Order thông qua getter() method, lúc này Hibernate truy vấn để lấy dữ liệu từ database thay vì lấy cùng lúc với Order như Eager .

Order order = em.find(Order.class, 1L);
Set<Item> items = order.getItems();
System.out.println(items.size());

Output

Hibernate: 
    select
        order0_.id as id1_1_0_ 
    from
        PurchaseOrder order0_ 
    where
        order0_.id=?
Hibernate: 
    select
        items0_.order_id as order_id2_0_0_,
        items0_.id as id1_0_0_,
        items0_.id as id1_0_1_,
        items0_.order_id as order_id2_0_1_ 
    from
        Item items0_ 
    where
        items0_.order_id=?

Như vậy chúng ta thấy rằng Hibernate đã sinh ra 2 câu truy vấn thay vì 1 như chúng ta sử dụng FetchType.Eager. Việc sử dụng Lazy trường hợp này có thể không có vấn đề gì vì chỉ chúng ta chỉ có một Order cần lấy các Item hoặc thậm chí là một tập các Order có kích thước nhỏ.

Nhưng nếu bạn có có một danh sách số lượng lớn các Order được lấy lên từ database với FetchType.Lazy, mỗi phần tử bạn duyệt qua và lấy danh sách các Item thì Hibernate sẽ thực thi hàng loạt câu lệnh SELECT để lấy chúng, nó còn được gọi là N +1 Problem trong ORM.

Chúng ta có 2 cách để tránh xảy ra N + 1 Problem:

  1. Bạn có thể sử dụng FetchType.Eager trong trường hợp chắc chắn rằng bất cứ khi nào truy vấn Order entity thì bạn cũng cần lấy các Item entity để xử lý.
  2. Nếu có những trường hợp chỉ cần sử dụng Order entity mà không cần Item thì sử dụng FetchType.Lazy và sử dụng một các tùy chọn Hibernate cung cấp (FetchJoin,  Named Entity Graph etc) khi cần thiết các Item của Order cùng lúc để xử lý. 

Tóm lược

Qua bài viết này, chúng ta cần hiểu và sử dụng đúng loại FetchType cho các trường hợp cụ thể để tránh các lỗi về hiệu xuất phổ biến trong Hibernate. Trong đa số trường hợp thì FetchType.Lazy là một lựa chọn tốt, thế nhưng cần chú ý để tránh N + 1 problem. 

Sử dụng FetchType.Eager khi chắc chắn rằng bạn muốn lấy tất cả các entity liên quan bất cứ khi nào lấy root entity. Nhưng trong thực tế thường rất ít các trường hợp như vậy, nếu sử dụng Eager sẽ lãng phí khá nhiều tài nguyên.

Sau cùng, các bạn có thể tham khảo mã nguồn được mình công khai trên gitlab.

Nếu vẫn chưa biết cách cấu hình một project JPA – Hibermate thì bạn có thể tham khảo bài viết sau để thực hành.

Nguồn tham khảo

https://thorben-janssen.com/entity-mappings-introduction-jpa-fetchtypes/

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x