Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@ElementCollection Java Persistence (Hibernate) Causes Loading of Duplicate Instances

When using @ElementCollection, load all is loading multiple instances of an object. More specifically, it is loading one instance for each element in the collectionOfStrings.

For example, a database with a single instance of MyClass with collectionOfStrings.size() == 4, the call to load all MyClass values will return a List of size 4 (all the same object) instead of just 1 object.

Is there a clean and simple way to resolve this or is the behaviour expected?

// Parent class is a @MappedSuperclass which may or may not be relevant to the issue
@Entity
public class MyClass extends ParentClass {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @ElementCollection(fetch=FetchType.EAGER)
    @IndexColumn(name="indexColumn")
    private List<String> collectionOfStrings;

    // other instance variables, constructors, getters, setters, toString, hashcode and equals
}

public class MyClassDAO_Hibernate extends GenericHibernateDAO<MyClass, Long> implements MyClassDAO {

    @Override
    public List<MyClass> loadAll() {
        List<MyClass> entityList = null;
        Session session = getSession();
        Transaction trans = session.beginTransaction();
        entityList = findByCriteria(session);
        trans.commit();
        return entityList;
    }

}

protected List<T> findByCriteria(Session session, Criterion... criterion) {
    Criteria crit = session.createCriteria(getPersistentClass());
    for (Criterion c : criterion) {
        crit.add(c);
    }
    return crit.list();
}

MyClassDAO myClassDAO = new MyClassDAO_Hibernate(); // in reality, implementation type is determined with a Factory
...
List<MyClass> myClassInstances = myClassDAO.loadAll();

Thanks, HeavyE

Edit: added findByCriteria call.

like image 665
HeavyE Avatar asked Jul 19 '11 18:07

HeavyE


3 Answers

I'm not sure whether it is a bug or legitimate behaviour, but it can be fixed by applying DISTINCT_ROOT_ENTITY result transformer:

protected List<T> findByCriteria(Session session, Criterion... criterion) {
    Criteria crit = session.createCriteria(getPersistentClass());
    for (Criterion c : criterion) {
        crit.add(c);
    }
    crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
    return crit.list();
}
like image 167
axtavt Avatar answered Nov 14 '22 15:11

axtavt


This is observed only when the collection is eagerly fetched. Hibernate translates this annotation mapping into an outer join query, which causes a multiple in the root element list for each linked collectionOfString element.

See ticket HHH-6783 in the Hibernate ORM issue tracker for this exact problem. And apparently no solution. :-(

A link to the Hibernate FAQ that goes into issues with outer join is also provided here.

I'm dealing with the exact same issue. Using an @ElementCollection makes sense in my case, but not at the cost of reviewing all my data access layer implementations. What is one to do?

like image 3
dgeske Avatar answered Nov 14 '22 15:11

dgeske


This is the correct behavior of the List. List permits duplication of objects and this is the reason why you need indexed column.

This are the general collection type which can be mapped by Hibernate:

Set is a collection in which no item occurs more than once. This is the most common persistent collection type, in my experience.

Bag is a colleciton in which items may occur more than once: they are very inefficient because hibernate cannot tell if items you put into it are the same as ones already in it (assuming that they are equal), so it has to delete the entire collection and re-save it from memory.

List is an indexed bag. The index lets hibernate know whether or not a particular in-memory object is the same one as an equal on-DB object, so complete delete/re-insert is not needed.

Map is just like a list, except the index doesn't have to be a computable (usually sequential) integer, it can be anything, even another object.

So in your case I recommend you to use Set.

like image 2
danny.lesnik Avatar answered Nov 14 '22 14:11

danny.lesnik