2010-01-03 24 views
94

Tôi có một lớp Order mà có một danh sách các OrderTransactions và tôi ánh xạ nó với một ánh xạ Hibernate one-to-many như vậy:Tiêu chuẩn Hibernate trả về trẻ em nhiều lần với FetchType.EAGER

@OneToMany(targetEntity = OrderTransaction.class, cascade = CascadeType.ALL) 
public List<OrderTransaction> getOrderTransactions() { 
    return orderTransactions; 
} 

Những Order s cũng có một trường orderStatus, được sử dụng để lọc theo các Tiêu chí sau:

Công trình này và kết quả như mong đợi.

Bây giờ đây là câu hỏi của tôi: Tại sao, khi tôi đặt Truy xuất gõ một cách rõ ràng để EAGER, làm Order s xuất hiện nhiều lần trong danh sách kết quả?

@OneToMany(targetEntity = OrderTransaction.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL) 
public List<OrderTransaction> getOrderTransactions() { 
    return orderTransactions; 
} 

Làm cách nào để thay đổi mã Tiêu chí để đạt được kết quả tương tự với cài đặt mới?

+1

Bạn đã thử bật show_sql để xem điều gì đang xảy ra bên dưới? –

+0

Vui lòng thêm mã lớp OrderTransaction và Order. \ –

Trả lời

95

Đây thực sự là hành vi mong đợi nếu tôi hiểu chính xác cấu hình của bạn.

Bạn nhận được cùng một Order dụ trong bất kỳ kết quả, nhưng vì bây giờ bạn đang làm một tham gia với OrderTransaction, nó phải trả lại cùng một số lượng kết quả một sql thường xuyên tham gia sẽ trở lại

Vì vậy, trên thực tế nó nên apear nhiều lần. này được giải thích rất tốt bởi tác giả (Gavin King) mình here: Nó vừa giải thích lý do tại sao, và làm thế nào để vẫn có được kết quả rõ rệt


Cũng đề cập trong Hibernate FAQ:

Hibernate không không trả về kết quả riêng biệt cho truy vấn có tìm nạp tham gia bên ngoài được bật cho bộ sưu tập (ngay cả khi tôi sử dụng từ khóa riêng biệt)? Đầu tiên, bạn cần hiểu SQL và cách OUTER JOINs làm việc trong SQL. Nếu bạn không hoàn toàn hiểu và hiểu được các kết nối bên ngoài trong SQL, không tiếp tục đọc mục Câu hỏi thường gặp này nhưng tham khảo hướng dẫn sử dụng SQL hoặc hướng dẫn . Nếu không, bạn sẽ không hiểu giải thích sau đây và bạn sẽ phàn nàn về hành vi này trên diễn đàn Hibernate.

ví dụ điển hình có thể quay trở lại tài liệu tham khảo bản sao của cùng một đối tượng thứ tự:

List result = session.createCriteria(Order.class) 
        .setFetchMode("lineItems", FetchMode.JOIN) 
        .list(); 

<class name="Order"> 
    ... 
    <set name="lineItems" fetch="join"> 

List result = session.createCriteria(Order.class) 
         .list(); 
List result = session.createQuery("select o from Order o left join fetch o.lineItems").list(); 

Tất cả những ví dụ tạo ra câu lệnh SQL giống nhau:

SELECT o.*, l.* from ORDER o LEFT OUTER JOIN LINE_ITEMS l ON o.ID = l.ORDER_ID 

Bạn muốn biết lý do tại sao các bản sao có ở đó?Hãy xem kết quả SQL, Hibernate không ẩn các phần tử trùng lặp này ở bên trái của kết quả được kết hợp bên ngoài nhưng trả về tất cả các bản sao của bảng điều khiển. Nếu bạn có 5 đơn đặt hàng trong cơ sở dữ liệu và mỗi đơn đặt hàng có 3 chi tiết đơn hàng, kết quả sẽ là 15 hàng. Danh sách kết quả Java của các truy vấn này sẽ có 15 phần tử, tất cả các loại Thứ tự. Chỉ có 5 trường hợp đặt hàng sẽ được tạo bởi Hibernate, nhưng các bản sao của kết quả SQL là được giữ nguyên dưới dạng tham chiếu trùng lặp với 5 trường hợp này. Nếu bạn không hiểu câu cuối cùng này, bạn cần phải đọc trên Java và sự khác biệt giữa một thể hiện trên vùng heap Java và tham chiếu đến ví dụ như vậy.

(Tại sao phải tham gia bên ngoài bên trái? Nếu bạn có một đơn hàng bổ sung không có dòng mục, tập hợp kết quả sẽ là 16 hàng với NULL điền bên phải bên, nơi dữ liệu chi tiết đơn hàng Bạn muốn đặt hàng ngay cả khi họ không có chi tiết đơn hàng, phải không? Nếu không, hãy sử dụng kết nối bên trong tìm nạp trong HQL của bạn).

Hibernate không lọc ra các tham chiếu trùng lặp này theo mặc định. Một số người (không phải bạn) thực sự muốn điều này. Làm thế nào bạn có thể lọc chúng ra?

Như thế này:

Collection result = new LinkedHashSet(session.create*(...).list()); 
+95

Thậm chí nếu bạn hiểu được lời giải thích sau đây, bạn cũng có thể khiếu nại về hành vi này trên diễn đàn Hibernate, vì nó đang đảo lộn hành vi ngu ngốc! –

+12

Khá đúng Tom, Id đã quên mất về thái độ kiêu ngạo của Gavin Kings. Ông cũng nói 'Hibernate không lọc ra các tài liệu tham khảo trùng lặp theo mặc định. Một số người (không phải bạn) thực sự muốn điều này 'Id được quan tâm khi mọi người thực sự kiến ​​này. –

+10

@TomAnderson có chính xác. ** Tại sao mọi người lại cần những bản sao đó? ** Tôi đang yêu cầu sự tò mò tinh khiết, vì tôi không có ý tưởng ... Bạn có thể tự tạo bản sao, như nhiều người trong số họ muốn .. ;-) – Parobay

87

Ngoài những gì được đề cập bởi Eran, một cách khác để có được những hành vi mà bạn muốn, là để thiết lập các biến kết quả:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); 
+8

Thao tác này sẽ hoạt động đối với hầu hết các trường hợp .... ngoại trừ khi bạn cố gắng sử dụng Tiêu chí để Tìm nạp 2 bộ sưu tập/liên kết. – JamesD

37

thử

@Fetch (FetchMode.SELECT) 

ví dụ

@OneToMany(targetEntity = OrderTransaction.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL) 
@Fetch (FetchMode.SELECT) 
public List<OrderTransaction> getOrderTransactions() { 
return orderTransactions; 

}

+9

FetchMode.SELECT tăng số lượng truy vấn SQL được Hibernate kích hoạt nhưng chỉ đảm bảo một bản sao cho mỗi bản ghi thực thể gốc. Hibernate sẽ kích hoạt lựa chọn cho mọi bản ghi con trong trường hợp này. Vì vậy, bạn nên tính đến nó đối với những cân nhắc về hiệu suất. – Bipul

+1

@BipulKumar có, nhưng đây là tùy chọn khi chúng tôi không thể sử dụng tìm nạp lười vì chúng tôi cần duy trì một phiên để tìm nạp lười để truy cập các đối tượng phụ. – mathi

16

Không sử dụng Danh sách và ArrayList nhưng Set và HashSet.

@OneToMany(targetEntity = OrderTransaction.class, cascade = CascadeType.ALL) 
public Set<OrderTransaction> getOrderTransactions() { 
    return orderTransactions; 
} 
+1

Đây có phải là một đề cập ngẫu nhiên về một thực hành tốt nhất Hibernate hoặc có liên quan đến câu hỏi truy xuất nhiều con từ OP? –

+1

Kiểm tra http://java.dzone.com/articles/hibernate-facts-favoring-sets này và https://access.redhat.com/documentation/en-US/JBoss_Enterprise_Application_Platform/4.3/html/Hibernate_Reference_Guide/Persistent_Classes- này Implementing_equals_and_hashCode.html –

+0

OK. Trung học cho câu hỏi của OP. Mặc dù, bài viết dzone có lẽ nên được thực hiện với một hạt muối ... dựa trên nhập học của tác giả trong các bình luận. –

3

Sử dụng Java 8 và Streams tôi thêm vào phương pháp hữu ích của tôi trở lại Statment này:

return results.stream().distinct().collect(Collectors.toList()); 

Streams loại bỏ trùng lặp rất nhanh. Tôi sử dụng chú thích trong lớp Thực thể của tôi như thế này:

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) 
@JoinTable(name = "STUDENT_COURSES") 
private List<Course> courses; 

Tôi nghĩ là sử dụng phiên trong phương pháp mà tôi cần dữ liệu từ cơ sở dữ liệu. Phiên đóng cửa khi tôi thực hiện. Ofcourse đặt lớp Entity của tôi để sử dụng kiểu tìm nạp leasy. Tôi đi đến refactor.

1

Tôi có cùng một sự cố khi tìm nạp 2 bộ sưu tập được liên kết: người dùng có 2 vai trò (Đặt) và 2 bữa ăn (Danh sách) và các bữa ăn được nhân đôi.

@Table(name = "users") 
public class User extends AbstractNamedEntity { 

    @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) 
    @Column(name = "role") 
    @ElementCollection(fetch = FetchType.EAGER) 
    @BatchSize(size = 200) 
    private Set<Role> roles; 

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "user") 
    @OrderBy("dateTime DESC") 
    protected List<Meal> meals; 
    ... 
} 

DISTINCT không giúp (DATA-JPA truy vấn):

@EntityGraph(attributePaths={"meals", "roles"}) 
@QueryHints({@QueryHint(name= org.hibernate.jpa.QueryHints.HINT_PASS_DISTINCT_THROUGH, value = "false")}) // remove unnecessary distinct from select 
@Query("SELECT DISTINCT u FROM User u WHERE u.id=?1") 
User getWithMeals(int id); 

Cuối cùng tôi đã tìm thấy 2 giải pháp:

  1. Change List to LinkedHashSet
  2. Sử dụng EntityGraph với trường chỉ "bữa ăn" và loại LOAD, có vai trò tải khi họ khai báo (EAGER và BatchSize = 200 để ngăn chặn sự cố N + 1):

giải pháp cuối cùng:

@EntityGraph(attributePaths = {"meals"}, type = EntityGraph.EntityGraphType.LOAD) 
@Query("SELECT u FROM User u WHERE u.id=?1") 
User getWithMeals(int id); 

UPDATE: trừ khi javadoc cho org.springframework.data.jpa.repository.EntityGraph.EntityGraphType#FETCH

thuộc tính được xác định bởi các nút thuộc tính của biểu đồ thực được coi là FetchType.EAGER và các thuộc tính không được chỉ định là được coi là FetchType.LAZY

với vai trò loại này cũng được tìm nạp.

Các vấn đề liên quan