Primitives的Hibernate / JPA Map导致奇怪的左外连接

时间:2011-02-14 01:03:27

标签: hibernate jpa

我很难实现包含基元Map的实体的适当映射(Map< String,String>)。生成的SQL不是我所期望的(左外连接),结果是当检索所述实体的列表时,应该是唯一的实体在结果中重复。

这是实体(缩写):

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

如您所见,有一个包含Map的用户类(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();

Hibernate运行以下查询:

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()也在用户名上进行散列。

我们的商店从“架构优先”设计开始工作,并且架构在数据库意义上似乎是“正确的”。可能这个问题可以通过插入映射表并在user_metadata表中生成id来解决,但是通过阅读可用的JPA和Hibernate文档(http://en.wikibooks.org/wiki/Java_Persistence/ElementCollection),似乎只使用两个表来映射一组原始元素应该是可能的。

映射中缺少什么?或者它是Hibernate中的一个错误?如果是这样,任何人都可以想到一个映射解决方案吗?我已经用注释捣乱了很多没有快乐。

1 个答案:

答案 0 :(得分: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