Hibernate CollectionOfElements EAGER fetch duplicates elements
Asked Answered
P

6

46

I have a class called SynonymMapping which has a collection of values mapped as a 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;
    }
}

I have a test where I store two SynonymMapping objects to the database and then ask the database to return all saved SynonymMapping objects, expecting to receive the two objects I stored.

When I change the mapping of values to be eager (as shown in in the code by the commented out line) and run the test again, I receive four matches.

I have cleared out the database between runs and I can duplicate this problem swapping between eager and lazy.

I think it has to do with the joins that hibernate creates underneath but I can't find a definite answer online.

Can anyone tell me why an eager fetch is duplicating the objects?

Thanks.

Pyrenees answered 7/7, 2009 at 15:49 Comment(1)
Every one with the Exception "More than one row with the given identifier was found" should know about this. It really spares alot of hours not knowing what the hell is going wrong. See @user176668 answer!!Sovereign
U
34

It's generally not a good idea to enforce eager fetching in the mapping - it's better to specify eager joins in appropriate queries (unless you're 100% sure that under any and all circumstances your object won't make sense / be valid without that collection being populated).

The reason you're getting duplicates is because Hibernate internally joins your root and collection tables. Note that they really are duplicates, e.g. for 2 SynonymMappings with 3 collection elements each you would get 6 results (2x3), 3 copies of each SynonymMapping entity. So the easiest workaround is to wrap results in a Set thereby ensuring they're unique.

Unlicensed answered 7/7, 2009 at 17:34 Comment(8)
But why can Hibernate not filter these out, I cannot see why you would ever want it this this like this.Mirabelle
I can confirm it works. I was using a Collection<T> just in general, and that was a mistake.Riverhead
Note: wrapping in a Set will render incorrect any pagination done on the resultsSorcerer
Is it in general good habit to use Set<>? It solves this "duplicate" problem so I'd use it for all my collections. Is there any downside of it?Pantywaist
@Pantywaist Well, in my case, switching from a List to a Set made it worse: I went from “all elements in three copies” to nine copies.Colorless
@SandySimonton, your comment is irrelevant, because initial issue was about "subset" of objects, and if you are talking about pagination, most probably you need to work with "subobjects" as a "root".Phrase
@Alice-m, it possible only in case when you did not redefine equals and hashcode. Otherwise check what kind of collection is used for your set.Phrase
@dpelisek, yes, sure, objects order is lost.Phrase
H
80

I stepped into the same problem - when you set the FetchType.EAGER for a @CollectionOfElements, the Hibernate tries to get everything in one shot, i.e. using one single query for each entry of element linked to a "master" object. This problem can be successfully solved at a cost of N+1 query, if you add the @Fetch (FetchMode.SELECT) annotation to your collection. In my case I wanted to have a MediaObject entity with a collection of its metadata items (video codec, audio codec, sizes, etc.). The mapping for a metadataItems collection looks as follows:


@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>();
Hesperidin answered 21/9, 2009 at 16:46 Comment(7)
I liked the solution, because we dont need to wrap the result with a Set to get uniqueness.Supernatural
can it also be caused by caching such as EHcache?Huntington
This solution works great, however, it's implementation-dependent.Sociality
Thank you. I have done a lot search about it. Every body said to use Set.Osswald
It works for me. Too bad @Fetch is not a JPA annotation, but who cares. I'm wondering what you are supposed to do when using pure JPA though. It's a bit frustrating.Scotsman
Took ages to debug this, and even longer to find an answer that wasn't a tutorial on implementing collections. Thanks for sharing.Pentameter
@Hesperidin Why you don't use SUBSELECT instead of SELECT?Cholecyst
U
34

It's generally not a good idea to enforce eager fetching in the mapping - it's better to specify eager joins in appropriate queries (unless you're 100% sure that under any and all circumstances your object won't make sense / be valid without that collection being populated).

The reason you're getting duplicates is because Hibernate internally joins your root and collection tables. Note that they really are duplicates, e.g. for 2 SynonymMappings with 3 collection elements each you would get 6 results (2x3), 3 copies of each SynonymMapping entity. So the easiest workaround is to wrap results in a Set thereby ensuring they're unique.

Unlicensed answered 7/7, 2009 at 17:34 Comment(8)
But why can Hibernate not filter these out, I cannot see why you would ever want it this this like this.Mirabelle
I can confirm it works. I was using a Collection<T> just in general, and that was a mistake.Riverhead
Note: wrapping in a Set will render incorrect any pagination done on the resultsSorcerer
Is it in general good habit to use Set<>? It solves this "duplicate" problem so I'd use it for all my collections. Is there any downside of it?Pantywaist
@Pantywaist Well, in my case, switching from a List to a Set made it worse: I went from “all elements in three copies” to nine copies.Colorless
@SandySimonton, your comment is irrelevant, because initial issue was about "subset" of objects, and if you are talking about pagination, most probably you need to work with "subobjects" as a "root".Phrase
@Alice-m, it possible only in case when you did not redefine equals and hashcode. Otherwise check what kind of collection is used for your set.Phrase
@dpelisek, yes, sure, objects order is lost.Phrase
L
8

I have faced this problem and I solved it using

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

This clears out the duplicates which are caused by the join made to the child tables.

Lectern answered 26/5, 2016 at 8:12 Comment(0)
R
2

You could use a SELECT DISTINCT (Hibernate Query Language) clause as follows

SELECT DISTINCT synonym FROM SynonymMapping synonym LEFT JOIN FETCH synonym.values

DISTINCT clause removes duplicate references in Hibernate.

Although both component and value-type collection has its lifecycle bound to the owning entity class, you should declare them in select clause in order to retrieve them. (LEFT JOIN FETCH synonym.values)

ChssPly76's answer is another approach, but does not forget override equals and hashcode method according to Set semantic

regards,

Rinee answered 17/7, 2009 at 13:50 Comment(1)
I was wondering more about why this happens and why the design decision was taken for hibernate to respond like this more than the different ways to get around it.Pyrenees
T
0

Instead of FetchMode.SELECT with N+1 queries it is better using BatchSize e.q. @BatchSize(size = 200).

DISTINCT and Criteria.DISTINCT_ROOT_ENTITY doesn't help, if you have to fetch more than 1 association. For this case see other solutions: https://mcmap.net/q/179996/-hibernate-criteria-returns-children-multiple-times-with-fetchtype-eager

Tymes answered 2/9, 2017 at 12:34 Comment(0)
L
0

I have achieved it via simply add

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

This help to remove duplicate.

Lues answered 29/8, 2018 at 13:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.