25

I'm using a HQL query to obtain certain records. If I use LEFT JOIN FETCH a collection which is in my target entity will contain duplicate records. If I only use left join, it won't. I guess when Hibernate lazily loads the records, it avoids duplicates.

"SELECT z FROM ", TableA.TABLE_NAME, " z ",  //
            "LEFT JOIN FETCH z.attributeX pv ", //
            "LEFT JOIN FETCH pv.attributeY anteil ", //
            "LEFT JOIN FETCH z.attributeB kma ", //
            "LEFT JOIN FETCH kma.attributeC ", //
            "WHERE anteil.attributeD.attributeE.id = :eId ", //
            "AND pv.attributeG.id = :gId ");

My entity TableA has a link to to TablePV (LEFT JOIN FETCH z.attributeX pv)

TablePV has a collection of TableY (LEFT JOIN FETCH pv.attributeY anteil)

Now Hibernate will map everything correctly except for the children of TablePV. It will contain several times the same record. A distinct on TableA doesn't help, since there are no duplicates there. I could manually remove those records which would be quite nonperforming I guess.

Dherik
  • 17,757
  • 11
  • 115
  • 164
Daniel
  • 834
  • 1
  • 9
  • 25
  • 1
    Use a Set as your collection. – JB Nizet Mar 15 '12 at 16:07
  • AFAIK LEFT JOIN FETCH and a restriction on it is mutual exclusive since the restriction might filter out elements of the collection to fetch eager. Maybe you have a different root cause here – Firo Mar 16 '12 at 12:00
  • 1
    Thanks for your answers. A Set will solve the problem but is a work-around and will create other problems (ordering for example). I don't know if it's mutualy exclusive, since the case makes sense. I want the records to be fetched in the same query and not with another select statement. This would happen if I only used LEFT JOIN without FETCH. – Daniel Mar 16 '12 at 15:47
  • How can a Set remove duplicate objects in the collection properties?! – The Light Jul 24 '13 at 12:38
  • Learn what LEFT JOIN returns: INNER JOIN rows plus unmatched left table rows extended by NULLs. Always know what INNER JOIN you want as part of a LEFT JOIN. A WHERE or INNER ON that requires a right table column to be not NULL after a LEFT JOIN ON removes any rows extended by NULLs, ie leaves only INNER JOIN rows, ie "turns LEFT JOIN into INNER JOIN". You have that. – philipxy Dec 20 '18 at 10:39

2 Answers2

14

The only way to really guarantee is using Set or SortedSet on these collections instead of use List. There is no other way officially to avoid this problem using Hibernate:

@OneToMany
private Set<AttributeY> attributeY;

You can read this tip in an old Hibernate documentation:

Queries that make use of eager fetching of collections usually return duplicates of the root objects, but with their collections initialized. You can filter these duplicates through a Set.

Or some kind of reference for the same problem on a newer one:

The only difference is that Set doesn’t allow duplicates, but this constraint is enforced by the Java object contract rather than the database mapping.

Set and order

If you would like to use Set and control the order of the entities, you can use SortedSet and implements Comparable on the child entities:

@OneToMany
@SortNatural
private SortedSet<AttributeY> attributeY = new TreeSet<>();

And:

@Entity
public class AttributeY implements Comparable<AttributeY> {

    @Override
    public int compareTo(AttributeY o) {
        return number.compareTo( o.getNumber() );
    }

}

To a custom sorting logic, you can use @SortComparator.

Precautions

Without more details it's hard to say why this happen in some cases using List and another cases don't. But you can try to implement equals/hashCode methods using the "business key" of the entity:

When using sets, it’s very important to supply proper equals/hashCode implementations for child entities. In the absence of a custom equals/hashCode implementation logic, Hibernate will use the default Java reference-based object equality which might render unexpected results when mixing detached and managed object instances.

Also, you are applying a condition using the FETCH alias pv and anteil. Don't do that. And get rid off the "train wreck" on your JPQL (anteil.attributeD.attributeE.id), because this can make Hibernate create weird SQLs (like doing the same JOIN more than once or invalid SQLs). So, make the JOINs explicit and not using the FETCH alias on WHERE:

LEFT JOIN FETCH z.attributeX
LEFT JOIN FETCH pv.attributeY
LEFT JOIN FETCH z.attributeB kma
LEFT JOIN FETCH kma.attributeC
LEFT JOIN pv.attributeY anteil
LEFT JOIN anteil.attributeD attributeD
LEFT JOIN attributeD.attributeE attributeE
LEFT JOIN z.attributeX pv
LEFT JOIN pv.attributeG attributeG
WHERE attributeE.id = :eId 
AND attributeG.id = :gId

If the duplication was in the root entity TableA, the DISTINCT will help, but it's not your case.

Dherik
  • 17,757
  • 11
  • 115
  • 164
-2

Try using DISTINCT in your query, something like SELECT DISTINCT z FROM Entity z...

biegleux
  • 13,179
  • 11
  • 45
  • 52
  • 3
    As mentioned in my question, this does not do the trick. The problem is, that the collection contains duplicates, not the main entity itself. – Daniel Oct 02 '12 at 12:27