2011-02-14 52 views
2

我很難實現包含基元映射的實體的映射(Map < String,String>)。結果SQL並非我所期望的(左外連接),結果是,當檢索所述實體的列表時,應該是唯一的實體在結果中被複制。Hibernate/JPA Map of Primitives導致奇怪的左外連接

這裏是爲實體(簡稱):

@Entity(name = "ZPrincipal") 
@Table(name = "users") 
public class ZPrincipal implements Principal, Serializable { 
    @Id 
    @Column(name = "username") 
    private String username; 

    @ElementCollection(fetch = FetchType.EAGER) 
    @CollectionTable(name = "user_metadata", joinColumns = { @JoinColumn(name = "username") }) 
    @MapKeyColumn(name = "meta_key") 
    @Column(name = "meta_value", nullable = false) 
    private Map<String, String> metadata; 
} 

正如你可以看到,有一個用戶類(ZPrincipal),其中包含一個地圖。我的表是這樣的(再次,從縮寫某些領域 '用戶',如電子郵件,密碼等):

CREATE TABLE users (
    username VARCHAR(60) NOT NULL, 
    PRIMARY KEY (username) 
); 

CREATE TABLE user_metadata (
    username VARCHAR(60) NOT NULL, 
    meta_key VARCHAR(255) NOT NULL, 
    meta_value VARCHAR(1024) NOT NULL, 
    CONSTRAINT user_meta_fk FOREIGN KEY (username) REFERENCES users (username), 
    PRIMARY KEY (username, meta_key) 
); 

一些示例內容:

用戶表:

|username | 
+---------+ 
|admin | 
|brett | 
+---------+ 

元表:

|username |meta_key |meta_value | 
+---------+----------+------------+ 
|brett |key1  |value1  | 
|brett |key2  |value2  | 
+---------+----------+------------+ 

通過上述映射,當我使用Hibernate檢索ZPrincipals列表,像這樣:

Criteria criteria = session.createCriteria(ZPrincipal.class); 
List<ZPrincipal> list = criteria.list(); 

休眠運行以下查詢:

select this_.username as username9_1_, 
     metadata2_.username as username9_3_, 
     metadata2_.meta_value as meta2_3_, 
     metadata2_.meta_key as meta3_3_ 
from users this_ 
left outer join user_metadata metadata2_ on this_.username=metadata2_.username 

在返回的行得到的:

|username9_1_ |username9_3_ |meta2_3_ |meta3_3_ | 
+--------------+--------------+----------+----------+ 
|admin   |null   |null  |null  | 
|brett   |brett   |key1  |value1 | 
|brett   |brett   |key2  |value2 | 
+--------------+--------------+----------+----------+ 

這導致含有3實體的用戶的列表(即「問題」),「admin」用戶對象和兩個「brett」用戶對象(相同)。這兩個「brett」實例確實包含一個正確填充的Map。對於那些想知道的,ZPrincipal類確實覆蓋equals(),它提供了一個基於用戶名(與主鍵相同)的比較,並且 覆蓋hashCode()也對用戶名進行散列。

我們的商店從「架構第一」設計開始工作,架構在數據庫意義上看起來「正確」。可能這個問題可以通過插入映射表和生成的id在user_metadata表中解決,但是通過閱讀可用的JPA和Hibernate文檔(​​),似乎應該可能僅使用兩個表來映射一組基本元素。

映射中缺少什麼?或者它是Hibernate中的錯誤?如果是這樣,任何人都可以想到映射解決方法?我對這些註釋進行了很少的無聊。

+0

看似解決了。我會在這裏爲所有遇到此問題的人發佈答案。當我將基於標準的查詢替換爲: Query query = session.createQuery(「FROM ZPrincipal」);` SQL查詢本身是相同的,但結果是一個正確唯一的ZPrincipals列表。恕我直言,這是一個Hibernate漏洞,因爲「FROM ZPrincipal」在語義上應該等價於: `Criteria criteria = session.createCriteria(ZPrincipal.class); 列表 list = criteria.list();` – brettw 2011-02-14 02:53:14

回答

2

這是預期和記錄的行爲。這些實例是完全相同的對象(因爲hibernate保證具有相同主鍵的相同實體在JVM中只有一個實例)。你可以做一個客戶端清除它們。您可以閱讀Hibernate常見問題,以瞭解他們爲什麼決定不自動執行此操作。

你使用「FROM ZPrincipal」嘗試的方法是告訴Hibernate不要對該集合進行一次急切的獲取,因此它將不得不爲每個實體加載地圖(這會導致N + 1選擇問題,如果你打算讀取每個獲取的ZPrincipals的地圖條目(或者或Hibernate發佈後續查詢來填充地圖)。

最好的方法是自己做去重複數據刪除並保存一些查詢。

參考: http://community.jboss.org/wiki/HibernateFAQ-AdvancedProblems#Hibernate_does_not_return_distinct_results_for_a_query_with_outer_join_fetching_enabled_for_a_collection_even_if_I_use_the_distinct_keyword

+0

優秀的答案,正是我所期待的。 – brettw 2011-02-14 08:11:50