2010-01-03 73 views
94

我有一個Order類,它具有的OrderTransactions名單,我和一個一對多的Hibernate映射像這樣它映射:休眠條件返回兒童多次與FetchType.EAGER

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

這些Order小號也有一個字段orderStatus,其用於用以下標準篩選:

public List<Order> getOrderForProduct(OrderFilter orderFilter) { 
    Criteria criteria = getHibernateSession() 
      .createCriteria(Order.class) 
      .add(Restrictions.in("orderStatus", orderFilter.getStatusesToShow())); 
    return criteria.list(); 
} 

這工作,其結果是如預期。

現在這裏是我的問題:爲什麼,當我設置抓取明確鍵入EAGER,做Order小號出現在結果列表中多次嗎?

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

我將如何更改標準碼以達到與新設置相同的結果?

+1

您是否嘗試過啓用show_sql來查看下面發生了什麼? – 2010-01-03 14:29:21

+0

請增加OrderTransaction和Order類的代碼。\ – 2010-01-03 15:24:55

回答

95

這實際上是預期的行爲,如果我正確理解您的配置。

你得到任何結果的相同Order實例,但既然你現在正在做的OrderTransaction一個連接,它必須返回結果相同數量的常規的SQL JOIN將返回

因此,實際上它應該猿多次。這是由作者很好的解釋(是Gavin King)本人here: 這既解釋了爲什麼,以及如何仍然得到不同的結果


在Hibernate FAQ還提到:

Hibernate並不對於集合中啓用外連接提取的查詢,不會返回不同的結果(即使我使用不同的 關鍵字)?首先,您需要了解SQL以及SQL中的OUTER JOIN如何工作 。如果您不完全理解並理解SQL中的外部連接,請不要繼續閱讀此FAQ項目,但請參閱SQL手冊或 教程。否則,你不會理解以下解釋 ,你會在Hibernate論壇上抱怨這種行爲。

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(); 

所有這些例子都產生相同的SQL語句:

可能返回相同的 Order對象的重複引用典型實例

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

想知道爲什麼有重複嗎?查看SQL結果集, 休眠不會隱藏外部 加入結果左側的這些副本,但會返回驅動表的所有副本。如果 您在數據庫中有5個訂單,並且每個訂單都有3個訂單項,則 結果集將爲15行。這些查詢的Java結果列表 將具有15個元素,所有類型爲Order。 Hibernate只創建5個訂單實例 ,但SQL結果集的重複值爲 ,這些實例被保存爲對這5個實例的重複引用。如果你不懂 這個最後一句話,你需要閱讀Java和Java堆上的一個實例與參考 這樣一個實例之間的區別。

(爲什麼是左外連接?如果您有附加訂單且沒有行 項目,則結果集爲16行,其中NULL填充右側 一側,其中訂單項數據用於其他訂單。即使沒有訂單項,您也希望訂單 ,對嗎?如果沒有,請在您的HQL中使用內部連接 )。

Hibernate默認不會過濾掉這些重複引用。有些人(不是你)實際上想要這個。你怎麼能過濾掉它們?

像這樣:

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

即使你理解了下面的解釋,你也可以在Hibernate論壇上抱怨這種行爲,因爲它正在翻轉愚蠢的行爲! – 2011-03-13 12:12:50

+12

相當恰當的湯姆,我忘記了加文國王傲慢的態度。他還說'默認情況下,Hibernate不會過濾掉這些重複的引用。有些人(不是你)實際上想要這個'Id有興趣,當人們實際上螞蟻這個。 – 2012-02-28 20:52:01

+10

@TomAnderson是的。 **爲什麼會有人需要這些重複?**我出於純粹的好奇心,因爲我不知道...你可以自己創建重複,就像你想要的許多重複... ;-) – Parobay 2014-01-24 14:16:29

87

除了什麼是伊蘭提到的,另一種方式來得到你想要的行爲,是設定結果變壓器:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); 
+8

這適用於大多數情況....除了當您嘗試使用標準提取2個集合/關聯時。 – JamesD 2012-03-28 21:26:08

37

嘗試

@Fetch (FetchMode.SELECT) 

例如

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

}

+9

FetchMode.SELECT增加了由Hibernate觸發的SQL查詢的數量,但確保每個根實體記錄只有一個實例。在這種情況下,Hibernate將爲每個子記錄激發一個選擇。所以你應該考慮性能方面的考慮。 – Bipul 2014-04-23 13:37:12

+1

@BipulKumar是的,但是當我們不能使用延遲獲取時,這是一個選項,因爲我們需要維護一個會話以便訪問子對象。 – mathi 2014-04-25 05:20:41

16

不要使用List和ArrayList,但設置和HashSet的。

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

這是偶爾提到的Hibernate最佳實踐還是與OP有關的多孩子檢索問題? – 2014-10-09 15:34:34

+1

請查看http://java.dzone.com/articles/hibernate-facts-favoring-sets和https://access.redhat.com/documentation/en-US/JBoss_Enterprise_Application_Platform/4.3/html/Hibernate_Reference_Guide/Persistent_Classes- Implementing_equals_and_hashCode.html – 2014-10-10 10:10:27

+0

明白了。僅次於OP的問題。雖然,dzone文章可能應該採取一粒鹽......基於作者自己的意見在評論。 – 2014-10-10 19:02:28

3

使用Java 8行旅我在實用方法添加此回statment:

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

流刪除重複的速度非常快。我用標註在我的實體類是這樣的:

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

我覺得是bether在我的應用程序使用會話中的方法,我需要從數據庫中的數據。當我完成時,Closse會話。 Ofcourse設置我的實體類使用leasy獲取類型。我去重構。

1

我有同樣的問題來獲取2個關聯的集合:用戶有2個角色(Set)和2餐(List),並且飯菜是重複的。

@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沒有幫助(DATA-JPA查詢):

@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); 

最後我找到了2個解決方案:

  1. Change List to LinkedHashSet
  2. 使用EntityGraph只有場「膳食」和類型加載,它們加載他們聲明的角色(EAGER和BatchSize = 200以防止N + 1問題):

最終溶液:

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

UPDATE:除非Javadoc中由所述實體圖形的屬性節點指定org.springframework.data.jpa.repository.EntityGraph.EntityGraphType#FETCH

屬性被視爲FetchType.EAGER和未指定的屬性是視爲FetchType.LAZY

此類型的角色也被提取。