Hi guys! I have found an elegant solution to this problem. With code below it is possible to truly paginate the result of a hibernate query, yet avoid duplicate parent problem. But - only when using criteria api.
The idea is the following:
- First a projection is created which returns only distinct id's of records we wish to retrieve, with applied pagination.
- Second, based on retrieved id's, whole records are retrieved, with no worries that they are duplicated, because pagination was accomplished at previous step.
- Last step - the resulting records are passed through a LinkedHashSet to eliminate duplicates yet preserve sort order.
Here is my spring-managed dao implementation. I did a lot of research to accomplish this solution, an I would be pleased if someone else will find it useful:
Code:
public class CriteriaDao extends HibernateDaoSupport {
public int getCount(DetachedCriteria criteria) {
return (Integer) getHibernateTemplate().findByCriteria(createCountProjection(criteria)).get(0);
}
/**
* This implementation do the real pagination of entities, and is also aware of duplicate parent entities problem.
* It heavy relies on criteria projection api to avoid the duplicate problem, yet accomplish the true pagination.
*/
public <T> List<T> getAll(DetachedCriteria criteria, SortAndPagingParams params) {
DetachedCriteria idProjection = createIdProjectionPreservingSort(criteria, params);
List<Long> idList = getHibernateTemplate().findByCriteria(idProjection, params.getFirst(), params.getCount());
DetachedCriteria idListCriteria = createGetByIdListCriteria(criteria, params, idList);
List<T> rezultWithDuplicates = getHibernateTemplate().findByCriteria(idListCriteria);
List<T> rezult = distinctList(rezultWithDuplicates);
return rezult;
}
protected static DetachedCriteria createSortedCriteria(DetachedCriteria criteria, SortAndPagingParams params) {
DetachedCriteria rez = copy(criteria);
if (params.getSortProperty() != null) {
rez.addOrder(params.isSortAsc() ? Order.asc(params.getSortProperty()) :
Order.desc(params.getSortProperty()));
}
return rez;
}
protected static DetachedCriteria createCountProjection(DetachedCriteria criteria) {
DetachedCriteria rez = copy(criteria);
rez.setProjection(Projections.countDistinct("id"));
return rez;
}
/**
* List the given criteria and return only distinct values.
*
* @see <a href="https://forum.hibernate.org/viewtopic.php?t=955186">Duplicate Parent record when joined with
* one-to-many</a>
* <p/>
* @see <a href="https://www.hibernate.org/117.241.html">fetch produces duplicate entries</a>
*/
@SuppressWarnings({"unchecked"})
protected static <T> List<T> distinctList(List<T> initialList) {
return new ArrayList<T>(new LinkedHashSet<T>(initialList));
}
/**
* Copy (deep clone) the provided criteria using serialization.
*
* @see <a href="https://forum.hibernate.org/viewtopic.php?t=939781">Cloning criteria using serialization</a>
*/
protected static DetachedCriteria copy(DetachedCriteria criteria) {
try {
ByteArrayOutputStream baostream = new ByteArrayOutputStream();
ObjectOutputStream oostream = new ObjectOutputStream(baostream);
oostream.writeObject(criteria);
oostream.flush();
oostream.close();
ByteArrayInputStream baistream = new ByteArrayInputStream(baostream.toByteArray());
ObjectInputStream oistream = new ObjectInputStream(baistream);
DetachedCriteria copy = (DetachedCriteria) oistream.readObject();
oistream.close();
return copy;
} catch (Throwable t) {
throw new HibernateException(t);
}
}
protected static DetachedCriteria createIdProjectionPreservingSort(DetachedCriteria criteria, SortAndPagingParams params) {
DetachedCriteria rez = createSortedCriteria(criteria, params);
rez.setProjection(Projections.distinct(Projections.id()));
return rez;
}
protected static DetachedCriteria createGetByIdListCriteria(DetachedCriteria criteria, SortAndPagingParams params,
List<Long> idList) {
DetachedCriteria rez = createSortedCriteria(criteria, params);
rez.add(Restrictions.in("id", idList));
return rez;
}
}
Code:
/**
* Data passed from data provider to dao layer, containing information about sort and paging.
*
* @author Denis Bogdanas
*/
public class SortAndPagingParams {
private String sortProperty;
private boolean sortAsc;
private int first;
private int count;
public SortAndPagingParams(String sortProperty, boolean sortAsc, int first, int count) {
this.sortProperty = sortProperty;
this.sortAsc = sortAsc;
this.first = first;
this.count = count;
}
public String getSortProperty() {
return sortProperty;
}
public boolean isSortAsc() {
return sortAsc;
}
public int getFirst() {
return first;
}
public int getCount() {
return count;
}
}
This dao is able to:
- Use as input any criteria containing restrictions and subcriteria's
- Retrieve record count
- Retrieve records sorted by arbitrary parent property
- Retrieve records paginated
- Avoid duplicate parent problem in one-to-many, many-to-many associations
- Leave input criteria unchanged, thus reusable
- Ideally suited to be used in a wicket DataProvider
Enjoy!