Hi Sven,
thx for you tips. They didn't solve my problems but provided some pretty good starting points. I guess for now I've found a pretty smart solution, by proxying query.list().
Cause it might be interesting for someone else, I'll post some code snippets here. The solution is ok for now but has some unsolved problems:
I fear there is no way finding out, if a db driver supports a scrollable resultset
There is no generic way to apply a count on a HQL query without retrieving all objects. Select count(*) approaches require a session and doesn't seem to work with all my test cases.
Quite a lot of performance work could be done, if scrollable resultset are supported by the db driver.
I'm thinking of modifying the mapping documents somehow, so a user can configure, if a query on a class should return a traditional or a LazyList. Don't know if such features would be interesting to anybody, just let me know.
Joerg
Code:
public class LazyInitList implements List {
public static final int ALL = Integer.MAX_VALUE;
private final static Log log = LogFactory.getLog(LazyInitList.class);
private static final int UNKWOWN = -1;
private static final int NO_SUPPORTED = 0;
private static final int SUPPORTED = 1;
private int resultSetScrollable = UNKWOWN;
private int pageSize = 20;
private Query query = null;
private Criteria criteria = null;
private List resultList = null;
private int loadedSize=0;
private int totalSize=-1;
public LazyInitList(final Query query) {
super();
this.query = query;
}
public LazyInitList(final Criteria criteria) {
super();
this.criteria = criteria;
}
public LazyInitList(final Query query, final int defaultFetchCount) {
super();
this.query = query;
this.pageSize = defaultFetchCount;
}
public LazyInitList(final Criteria criteria, final int defaultFetchCount) {
super();
this.criteria = criteria;
this.pageSize = defaultFetchCount;
}
private List initList(final int pos) {
if (resultList==null || (pos>loadedSize)) {
if (query!=null) {
if (loadedSize<ALL) {
loadedSize = loadedSize<pageSize ? pageSize : Math.max(loadedSize*2,pos+pageSize);
}
if (pos==ALL) {
loadedSize = ALL;
}
query.setMaxResults(loadedSize);
resultList = query.list();
}
if (criteria!=null) {
if (loadedSize<ALL) {
loadedSize = loadedSize<pageSize ? pageSize : Math.max(loadedSize*2,pos+pageSize);
}
if (pos==ALL) {
loadedSize = ALL;
}
criteria.setMaxResults(loadedSize);
resultList = criteria.list();
}
}
return resultList;
}
/**
* Return total collection size by using scrollable resultsets
* with fallback to HQL or Criteria query
*/
private int getTotalSize() {
if (totalSize==-1) {
if (loadedSize==ALL) {
// If the list is already loaded don't make a separat query
totalSize= resultList.size();
} else {
if (resultSetScrollable!=NO_SUPPORTED) {
// As far as I know there is no way checking for scrollable resultsset
// so this is an try and error approach chechkign for scrollable resultsets
try {
ScrollableResults scrollable = criteria==null ? query.scroll(ScrollMode.SCROLL_SENSITIVE) : criteria.scroll(ScrollMode.SCROLL_SENSITIVE);
scrollable.afterLast();
scrollable.previous();
totalSize = scrollable.getRowNumber()+1;
resultSetScrollable = SUPPORTED;
} catch (Exception e) {
log.warn("ScrollableResults seems not be supported from the database.");
resultSetScrollable = NO_SUPPORTED;
getTotalSizeByQuery();
}
} else {
// return size with separate query
getTotalSizeByQuery();
}
}
}
return totalSize;
}
/**
* Return total collection size with a separate
* HQL or Crieteria query
*/
private void getTotalSizeByQuery() {
if (criteria!=null) {
criteria.setProjection(Projections.rowCount());
totalSize = ((Integer)(criteria.uniqueResult())).intValue();
criteria.setProjection(null);
criteria.setResultTransformer(Criteria.ROOT_ENTITY);
}
if (query!=null) {
initList(ALL);
totalSize = resultList.size();
}
}
public int getLoadedSize() {
return loadedSize;
}
public void add(int index, Object element) {
initList(index);
resultList.add(index, element);
}
public boolean add(Object o) {
initList(size());
return resultList.add(o);
}
[...}
}