Hibernate, collections, and duplicate objects

原文链接:https://doctorjw.wordpress.com/2012/01/11/hibernate-collections-and-duplicate-objects/

 

It’s interesting that Hibernate doesn’t take care of this under the covers, but forces developers to realize they may get duplicate objects under some circumstances.

 

The story: at work yesterday, a buddy and I started working on an odd defect. When our users added a particular type of object (Waste), it would show up in the list of existing objects as 2 Waste entries. Deleting one of them resulted in both going away; a quick check of the db indicated that yes, indeed, when we added an item, just one was created in the db. Clearly, either the view was showing a duplicate, or somewhere in the business layer, the item was being replicated. It didn’t take long to isolate the issue to the retrieval of the Waste items from the database, and to tie the appearance of the issue to a particular change in our Waste class.

@OneToMany(mappedBy = "waste", fetch = FetchType.LAZY, orphanRemoval = true)
@org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.ALL})
private List<WasteDetail> details = new ArrayList<WasteDetail>();

 

was changed to

@OneToMany(mappedBy = "waste", fetch = FetchType.EAGER, orphanRemoval = true)
@org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.ALL})
private List<WasteDetail> details = new ArrayList<WasteDetail>();

 

Changing from FetchType.LAZY to FetchType.EAGER apparently has Hibernate attempting to retrieve all items within the collection with a single fetch, using an outer join. Within our dao, we have

Criteria c = getSession().createCriteria(Waste.class);
c.add(Restrictions.eq("store.idoreId));
c.addOrder(Order.desc("wasteDateList<Waste> retVal = c.list();

 

The retrieval of a list of Waste items, with the collection inside each Waste, results in a cartesian product through the outer join — so a Waste object with 2 WasteDetails will show up twice in the list; 3 WasteDetails will net you 3 Waste objects …

 

A quick bit of Google magic led me to this post, which further pointed to the Hibernate FAQ; of specific use was this entry. Several techniques for eliminating the duplicates are documented; one that we’ve (apparently) used on this project in the past is not, however.

 

What we’ve used before is to add the annotation @Fetch(FetchMode.SUBSELECT) (though FetchMode.SELECT will also work) on the collection in the domain object. The collection with it’s annotations then becomes:

@OneToMany(mappedBy = "waste", fetch = FetchType.EAGER, orphanRemoval = true)
@org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.ALL})
@Fetch(FetchMode.SUBSELECT)
private List<WasteDetail> details = new ArrayList<WasteDetail>();

 

This forces hibernate to not use the Join, and instead sends multiple sql calls to the db to retrieve the data.

 

If you’re not concerned about performance, using FetchMode.SUBSELECT or FetchMode.SELECT on the domain class will work, eliminating the duplicates.

 

Alternatively, if you’re concerned about performance and want the select to the database to happen in a single call via the join, then use one of the techniques in the hibernate FAQ. I would lean towards adding setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) to the Criteria, as it’s pretty explicit (unlike using a LinkedHashSet). For example, this:

Criteria c = getSession().createCriteria(Waste.class);
c.add(Restrictions.eq("store.idoreId));
c.addOrder(Order.desc("wasteDatec.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
List<Waste> retVal = c.list();

 

also works just fine, and preserves the Join nature of the select generated by Hibernate. The issue I have with the techniques in the FAQ are that they are not directly on the domain class, but on the DAO. Not a huge deal, I suppose, as all operations affecting this object should go through the Waste dao … but … what happens if I persist another object that contains a collection of Waste items? Hmmm…..

猜你喜欢

转载自a2429854489.iteye.com/blog/2254210