JPA háo hức tìm nạp không tham gia


112

Chính xác thì chiến lược tìm nạp của JPA kiểm soát điều gì? Tôi không thể phát hiện ra sự khác biệt nào giữa háo hức và lười biếng. Trong cả hai trường hợp, JPA / Hibernate không tự động tham gia các mối quan hệ nhiều-một.

Ví dụ: Người có một địa chỉ duy nhất. Một địa chỉ có thể thuộc về nhiều người. Các lớp thực thể được chú thích JPA trông giống như:

@Entity
public class Person {
    @Id
    public Integer id;

    public String name;

    @ManyToOne(fetch=FetchType.LAZY or EAGER)
    public Address address;
}

@Entity
public class Address {
    @Id
    public Integer id;

    public String name;
}

Nếu tôi sử dụng truy vấn JPA:

select p from Person p where ...

JPA / Hibernate tạo một truy vấn SQL để chọn từ bảng Person, sau đó truy vấn địa chỉ riêng biệt cho từng người:

select ... from Person where ...
select ... from Address where id=1
select ... from Address where id=2
select ... from Address where id=3

Điều này rất tệ đối với các tập kết quả lớn. Nếu có 1000 người, nó sẽ tạo ra 1001 truy vấn (1 từ Người và 1000 khác với Địa chỉ). Tôi biết điều này vì tôi đang xem nhật ký truy vấn của MySQL. Tôi hiểu rằng việc đặt kiểu tìm nạp của địa chỉ thành háo hức sẽ khiến JPA / Hibernate tự động truy vấn với một phép nối. Tuy nhiên, bất kể kiểu tìm nạp là gì, nó vẫn tạo ra các truy vấn riêng biệt cho các mối quan hệ.

Chỉ khi tôi bảo nó tham gia một cách rõ ràng thì nó mới thực sự tham gia:

select p, a from Person p left join p.address a where ...

Am i thiếu cái gì ở đây? Bây giờ tôi phải viết tay mọi truy vấn để nó kết hợp với các mối quan hệ nhiều-một. Tôi đang sử dụng triển khai JPA của Hibernate với MySQL.

Chỉnh sửa: Nó xuất hiện (xem Câu hỏi thường gặp về Hibernate ở đâyở đây ) FetchTypekhông ảnh hưởng đến các truy vấn JPA. Vì vậy, trong trường hợp của tôi, tôi đã yêu cầu nó tham gia một cách rõ ràng.


5
Liên kết đến các mục FAQ bị phá vỡ, đây đang làm việc một
n0weak

Câu trả lời:


99

JPA không cung cấp bất kỳ thông số kỹ thuật nào về ánh xạ chú thích để chọn chiến lược tìm nạp. Nói chung, các thực thể có liên quan có thể được tìm nạp theo bất kỳ cách nào dưới đây

  • SELECT => một truy vấn cho các thực thể gốc + một truy vấn cho tập hợp / thực thể được ánh xạ có liên quan của mỗi thực thể gốc = (n + 1) truy vấn
  • SUBSELECT => một truy vấn cho các thực thể gốc + truy vấn thứ hai cho thực thể được ánh xạ có liên quan / tập hợp tất cả các thực thể gốc được truy xuất trong truy vấn đầu tiên = 2 truy vấn
  • JOIN => một truy vấn để tìm nạp cả thực thể gốc và tất cả thực thể được ánh xạ của chúng / truy vấn collection = 1

Vì vậy, SELECTJOINlà hai thái cực và SUBSELECTnằm ở giữa. Người ta có thể chọn chiến lược phù hợp dựa trên mô hình miền của cô ấy / anh ấy.

Theo mặc định SELECTđược sử dụng bởi cả JPA / EclipseLink và Hibernate. Điều này có thể được ghi đè bằng cách sử dụng:

@Fetch(FetchMode.JOIN) 
@Fetch(FetchMode.SUBSELECT)

ở chế độ Hibernate. Nó cũng cho phép thiết lập SELECTchế độ một cách rõ ràng bằng cách sử dụng @Fetch(FetchMode.SELECT)có thể được điều chỉnh bằng cách sử dụng kích thước lô, ví dụ @BatchSize(size=10).

Các chú thích tương ứng trong EclipseLink là:

@JoinFetch
@BatchFetch

5
Tại sao những cài đặt đó tồn tại? Tôi nghĩ rằng JOIN phải được sử dụng hầu như luôn luôn. Bây giờ tôi phải đánh dấu tất cả các ánh xạ bằng các chú thích dành riêng cho chế độ ngủ đông.
vbezhenar

4
Thật thú vị nhưng đáng buồn là @Fetch (FetchMode.JOIN) hoàn toàn không hoạt động đối với tôi (Hibernate 4.2.15), trong JPQL cũng như trong Criteria.
Aphax,

3
Các Hibernate chú thích không dường như làm việc ở tất cả đối với tôi, hoặc, sử dụng Spring JPA
TheBakker

2
@Aphax Điều này có thể là do Hibernate sử dụng các chiến lược mặc định khác nhau cho JPAQL / Criteria so với em.find (). Xem vladmihalcea.com/2013/10/17/… và tài liệu tham khảo.
Joshua Davis

1
@vbezhenar (và othes đọc bình luận của anh ấy một thời gian sau): Truy vấn JOIN tạo sản phẩm cartesian trong cơ sở dữ liệu, vì vậy bạn nên chắc chắn rằng bạn muốn tính toán sản phẩm cartesian đó. Lưu ý rằng nếu bạn sử dụng tham gia tìm nạp, ngay cả khi bạn đặt LAZY, nó sẽ được tải một cách háo hức.
Walfrat

45

"mxc" là đúng. fetchTypechỉ định khi nào mối quan hệ sẽ được giải quyết.

Để tối ưu hóa việc tải mong muốn bằng cách sử dụng kết nối bên ngoài, bạn phải thêm

@Fetch(FetchMode.JOIN)

đến lĩnh vực của bạn. Đây là một chú thích cụ thể ở chế độ ngủ đông.


6
Điều này không hiệu quả với tôi với Hibernate 4.2.15, trong JPQL hoặc Criteria.
Aphax,

6
@Aphax Tôi nghĩ đó là bởi vì JPAQL và Tiêu chí không tuân theo đặc tả Tìm nạp. Chú thích Tìm nạp chỉ hoạt động với em.find (), AFAIK. Xem vladmihalcea.com/2013/10/17/… Ngoài ra, hãy xem docos ngủ đông. Tôi khá chắc rằng điều này được che đậy ở đâu đó.
Joshua Davis

@JoshuaDavis Ý tôi là chú thích \ @Fetch không áp dụng bất kỳ loại tối ưu hóa THAM GIA nào trong các truy vấn, khi JPQL hoặc em.find (), tôi vừa thử một lần nữa trên Hibernate 5.2. + Và nó vẫn như cũ
Aphax

38

Thuộc tính fetchType kiểm soát liệu trường chú thích có được tìm nạp ngay lập tức khi đối tượng chính được tìm nạp hay không. Nó không nhất thiết chỉ định cách câu lệnh tìm nạp được xây dựng, việc triển khai sql thực tế phụ thuộc vào nhà cung cấp bạn đang sử dụng toplink / hibernate, v.v.

Nếu bạn đặt fetchType=EAGERĐiều này có nghĩa là trường chú thích được điền các giá trị của nó cùng lúc với các trường khác trong thực thể. Vì vậy, nếu bạn mở một trình quản lý thực thể, truy xuất các đối tượng người của bạn và sau đó đóng trình quản lý thực thể, sau đó thực hiện một người quản lý thực thể sẽ không dẫn đến một ngoại lệ tải lười biếng được ném ra.

Nếu bạn đặt fetchType=LAZYtrường chỉ được điền khi nó được truy cập. Nếu bạn đã đóng trình quản lý thực thể trước đó thì một ngoại lệ tải lười biếng sẽ được ném ra nếu bạn thực hiện một person.address. Để tải trường, bạn cần đặt thực thể trở lại ngữ cảnh entitymangers với em.merge (), sau đó thực hiện truy cập trường và sau đó đóng entitymanager.

Bạn có thể muốn tải chậm khi xây dựng một lớp khách hàng với một tập hợp các đơn đặt hàng của khách hàng. Nếu bạn truy xuất mọi đơn đặt hàng của một khách hàng khi bạn muốn có được danh sách khách hàng, đây có thể là một thao tác cơ sở dữ liệu tốn kém khi bạn chỉ tìm kiếm tên khách hàng và chi tiết liên hệ. Tốt nhất để lại quyền truy cập db cho đến sau.

Đối với phần thứ hai của câu hỏi - làm thế nào để có được chế độ ngủ đông để tạo SQL được tối ưu hóa?

Hibernate sẽ cho phép bạn cung cấp các gợi ý về cách tạo truy vấn hiệu quả nhất nhưng tôi nghi ngờ có điều gì đó không ổn với cấu trúc bảng của bạn. Mối quan hệ có được thiết lập trong các bảng không? Hibernate có thể đã quyết định rằng một truy vấn đơn giản sẽ nhanh hơn một phép nối, đặc biệt nếu thiếu chỉ mục, v.v.


20

Hãy thử với:

select p from Person p left join FETCH p.address a where...

Nó hoạt động tương tự với tôi với JPA2 / EclipseLink, nhưng có vẻ như tính năng này cũng có trong JPA1 :



2

để tham gia, bạn có thể làm nhiều việc (sử dụng liên kết eclipselink)

  • trong jpql, bạn có thể làm trái tìm nạp tham gia

  • trong truy vấn được đặt tên, bạn có thể chỉ định gợi ý truy vấn

  • trong TypedQuery, bạn có thể nói điều gì đó như

    query.setHint("eclipselink.join-fetch", "e.projects.milestones");

  • cũng có gợi ý tìm nạp hàng loạt

    query.setHint("eclipselink.batch", "e.address");

xem

http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html


1

Tôi đã gặp chính xác vấn đề này với ngoại lệ rằng lớp Người có một lớp khóa được nhúng. Giải pháp của riêng tôi là tham gia cùng họ trong truy vấn VÀ xóa

@Fetch(FetchMode.JOIN)

Lớp id nhúng của tôi:

@Embeddable
public class MessageRecipientId implements Serializable {

    @ManyToOne(targetEntity = Message.class, fetch = FetchType.LAZY)
    @JoinColumn(name="messageId")
    private Message message;
    private String governmentId;

    public MessageRecipientId() {
    }

    public Message getMessage() {
        return message;
    }

    public void setMessage(Message message) {
        this.message = message;
    }

    public String getGovernmentId() {
        return governmentId;
    }

    public void setGovernmentId(String governmentId) {
        this.governmentId = governmentId;
    }

    public MessageRecipientId(Message message, GovernmentId governmentId) {
        this.message = message;
        this.governmentId = governmentId.getValue();
    }

}

-1

Hai điều xảy ra với tôi.

Đầu tiên, bạn có chắc bạn muốn nói đến ManyToOne cho địa chỉ? Điều đó có nghĩa là nhiều người sẽ có cùng một địa chỉ. Nếu nó được chỉnh sửa cho một trong số chúng, nó sẽ được chỉnh sửa cho tất cả chúng. Đó có phải là ý định của bạn? 99% địa chỉ thời gian là "riêng tư" (theo nghĩa là chúng chỉ thuộc về một người).

Thứ hai, bạn có bất kỳ mối quan hệ mong muốn nào khác trên thực thể Person không? Nếu tôi nhớ chính xác, Hibernate chỉ có thể xử lý một mối quan hệ háo hức trên một thực thể nhưng đó có thể là thông tin lỗi thời.

Tôi nói vậy bởi vì sự hiểu biết của bạn về cách thức hoạt động của điều này về cơ bản là chính xác từ nơi tôi đang ngồi.


Đây là một ví dụ được tạo thành sử dụng nhiều-một. Địa chỉ cá nhân có thể không phải là ví dụ tốt nhất. Tôi không thấy bất kỳ loại tìm nạp háo hức nào khác trong mã của mình.
Steve Kuo

Đề xuất của tôi sau đó là giảm điều này thành một ví dụ đơn giản chạy và thực hiện những gì bạn đang thấy và sau đó đăng điều đó. Có thể có các biến chứng khác trong mô hình của bạn đang gây ra hành vi không mong muốn.
cletus

1
Tôi đã chạy mã chính xác như nó xuất hiện ở trên và nó thể hiện hành vi đã nói.
Steve Kuo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.