Hibernate CollectionOfElements 的 EAGER 拉取会出现重复元素。

46
我有一个名为SynonymMapping的类,其中包含一个作为CollectionOfElements映射的值集合。
@Entity(name = "synonymmapping")
public class SynonymMapping {

    @Id private String keyId;

    //@CollectionOfElements(fetch = FetchType.EAGER)
    @CollectionOfElements
    @JoinTable(name="synonymmappingvalues", joinColumns={@JoinColumn(name="keyId")})
    @Column(name="value", nullable=false)
    @Sort(type=SortType.NATURAL)
    private SortedSet<String> values;

    public SynonymMapping() {
        values = new TreeSet<String>();
    }

    public SynonymMapping(String key, SortedSet<String> values) {
        this();
        this.keyId = key;
        this.values = values;
    }

    public String getKeyId() {
        return keyId;
    }

    public Set<String> getValues() {
        return values;
    }
}

我有一个测试,在其中将两个SynonymMapping对象存储到数据库中,然后请求数据库返回所有保存的SynonymMapping对象,期望接收到我存储的两个对象。

当我将值的映射更改为eager(如代码中注释掉的那行所示)并再次运行测试时,我会收到四个匹配项。

我已经在运行之间清空了数据库,并且我可以在eager和lazy之间交替复制此问题。

我认为这与hibernate创建的连接有关,但我在网上找不到确切的答案。

有人可以告诉我为什么eager获取会导致对象重复吗?

谢谢。


每个遇到“找到给定标识符的多行”异常的人都应该知道这个。它真的可以节省很多时间,不用再猜测出了什么问题。请查看@user176668的答案!! - Gotham Llianen
6个回答

80

我遇到了同样的问题 - 当你为@CollectionOfElements设置FetchType.EAGER时,Hibernate会尝试一次性获取所有元素,即针对与“主”对象关联的每个条目使用单个查询。如果您将@Fetch(FetchMode.SELECT)注释添加到集合中,则可以成功解决此问题,但需要付出N+1查询的代价。

@CollectionOfElements(targetElement = String.class,fetch = FetchType.EAGER)
@JoinTable(name =“ mo_metadata_item”,joinColumns = @JoinColumn(name =“ media_object_id”))
@MapKey(columns = @Column(name =“ name”))
@Column(name =“ value”)
@Fetch(FetchMode.SELECT)
private Map<String,String> metadataItems = new HashMap<String,String>();

1
我喜欢这个解决方案,因为我们不需要用 Set 包装结果来获得唯一性。 - sanbhat
这个解决方案非常好,但它依赖于具体实现。 - rion18
1
谢谢。我已经做了很多关于它的搜索。每个人都说要使用Set。 - mirzaei
它对我来说很有效。可惜@Fetch不是JPA注释,但谁在乎呢。我想知道在使用纯JPA时应该做什么。这有点令人沮丧。 - xtian
2
调试这个问题花了很长时间,甚至更长的时间去找到一个不是关于实现集合的教程的答案。感谢分享。 - AndyT
显示剩余2条评论

34

在映射中强制使用急加载通常不是一个好主意——最好在适当的查询中指定急加载连接(除非您 100% 确定,在任何情况下,如果不填充该集合,则您的对象毫无意义/无效)。

产生重复结果的原因是因为Hibernate在内部连接您的根表和集合表。请注意,它们实际上是重复的,例如,对于具有每个3个集合元素的2个SynonymMappings,您将获得6个结果(2x3),即每个SynonymMapping实体的3个副本。因此,最简单的解决方法是将结果封装在Set中,从而确保它们是唯一的。


43
为什么Hibernate不能过滤掉这些,我不明白为什么你会希望它这样。 - Paul Taylor
我可以确认它是有效的。我通常使用了一个Collection<T>,但那是个错误。 - Aron Lorincz
注意:将结果包装在Set中会使对结果进行的任何分页操作无效。 - Sandy Simonton
使用Set<>通常是一个好习惯吗?它解决了“重复”的问题,所以我会在所有集合中使用它。它有什么缺点吗? - dpelisek
@dpelisek 好吧,在我的情况下,从列表切换到集合使情况变得更糟:我从“所有元素有三个副本”变成了九个副本。 - Alice M.
显示剩余3条评论

8

我曾遇到过这个问题,并使用下面的代码进行了解决:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

这将清除由于与子表进行的连接而引起的重复项。


2
您可以使用以下方式使用SELECT DISTINCT(Hibernate查询语言)子句:
SELECT DISTINCT synonym FROM SynonymMapping synonym LEFT JOIN FETCH synonym.values

DISTINCT子句可以在Hibernate中删除重复引用。

虽然组件和值类型集合的生命周期都绑定到拥有它们的实体类,但你应该在select子句中声明它们以便检索它们。(LEFT JOIN FETCH synonym.values)

ChssPly76的答案是另一种方法,但不要忘记根据Set语义覆盖equals和hashcode方法。

祝好!


3
我想知道为什么会发生这种情况以及为什么选择了这种方式来响应,而不是针对此进行不同的解决方法。请帮我解释一下。 - Rachel

0

我通过简单地添加已经实现了它

session.createCriteria(ModelClass.class).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

这有助于去除重复项。


0

使用BatchSize(例如:@BatchSize(size = 200))比使用N+1查询的FetchMode.SELECT更好。

如果您需要获取多个关联对象,则DISTINCTCriteria.DISTINCT_ROOT_ENTITY无法解决问题。对于这种情况,请参考其他解决方案:https://dev59.com/2HI-5IYBdhLWcg3wJE0K#46013654


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接