2011-02-14 45 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