Es gibt zwei Varianten, Page-weisen Zugriff oder Zugriff über Cursor. In diesem Post möchte ich den Page-weisen Zugriff beschreiben.
Die API-Funktionen werden im FAQ erklärt, siehe
I want to retrieve query results, one page at a time. Die zugehörigen Befehle sind Criteria.setMaxResults() und Criteria.setFirstResult() und führen zu SELECT's von
Code:
SELECT X
FROM Y
LIMIT ?
wenn nur MaxResult gesetzt ist oder FirstResult 0 ist bzw.
Code:
SELECT X
FROM Y
LIMIT ? OFFSET ?
wenn beide Werte gesetzt sind.
Mein GenericDaoHibernateImpl habe ich wie folgt angepasst:
Code:
/**
* Erzeugt ein Criteria und initialisiert es mit den übergebenen Criterion's
* Dieses kann dann zur direkten Suche benutzt werden:<br>
* <code>createCriteria().list();
* </code><br>
* oder mit zusätzlichen Aufrufen weiter konfiguriert werden.
* @param criterion Liste der Criterion's
* @return ein initialisiertes Criteria-Objekt
*/
protected Criteria createCriteria(Criterion... criterion)
{
Criteria crit = sessionFactory.getCurrentSession().createCriteria(type);
for (Criterion c : criterion)
{
crit.add(c);
}
return crit;
}
/**
* Ermittelt alle Objekte.
* @return Liste der gefundenen Objekte.
*/
@SuppressWarnings("unchecked")
public List<T> findAll()
{
return createCriteria().list();
}
/**
* Adapter, um ein Criteria in ein PageIterable einzupacken. Damit kann mit foreach über das
* Resultset iteriert werden.
* Use this inside subclasses as a convenience method.
* @param pageSize Anzahl der maximal zurückgegebenen Sätze pro Aufruf.
* @param pageNumber 0-basierte Nummer der Page.
*/
protected Iterable<T> findByCriteriaWithPaging(int pageSize, Criterion... criterion)
{
// Criteria erzeugen
Criteria crit = createCriteria(criterion);
// und in ein PageIterable einpacken
return new PageIterable(crit, pageSize);
}
/**
* Adapter, um Java5 for-each nutzen zu können.
* @author clipp
*/
private class PageIterable implements Iterable<T>
{
/**
* Criteria, mit dem gesucht wird.
*/
private Criteria crit;
/**
* Grösse einer Seite, die gefetcht wird.
*/
private int pageSize;
/**
* ctor.
* @param crit
* @param pageSize
*/
PageIterable(Criteria crit, int pageSize)
{
// Übergabe speichern
this.crit = crit;
this.pageSize = pageSize;
}
public Iterator<T> iterator()
{
return new PageIterator(crit, pageSize);
}
};
/**
* Iteriert Page-weise über die Abfrage, die durch ein Criteria definiert ist.
* @todo: Folgende Fälle sind zu testen:
* - leere Tabelle
* - genau teilbar
* - mit Rest teilbar, mehr als eine Page
* - mit Rest teilbar, eine Page
* @author clipp
*/
private class PageIterator implements Iterator<T>
{
/**
* Criteria, mit dem gesucht wird.
*/
private Criteria crit;
/**
* Grösse einer Seite, die gefetcht wird.
*/
private int pageSize;
/**
* Gibt an, welche Page geladen werden soll.
*/
private int pageNumber = 0;
/**
* Gibt an, ob eine weitere Page vorhanden ist.
*/
private boolean hasNextPage = true;
/**
* Iterator für eine einzelne Page.
*/
private Iterator<T> iteratorForSinglePage;
/**
* ctor.
* @param crit Criteria, mit dem gesucht wird
* @param pageSize Grösse der Page
*/
public PageIterator(Criteria crit, int pageSize)
{
// Übergabe speichern
this.crit = crit;
this.pageSize = pageSize;
// Grösse der Page setzen
crit.setMaxResults(pageSize);
}
/**
* Gibt an, ob eine weitere Iteration möglich ist.
* @return true wenn eine weitere Iteration möglich ist.
*/
@Override
public boolean hasNext()
{
// wurde der Iterator noch nicht erzeugt, ist die erste Page zu laden
if (iteratorForSinglePage == null)
{
nextPage();
// Iterator der Liste übernimmt restliche Behandlung,
// inkl. der NoSuchElementException
return iteratorForSinglePage.hasNext();
}
// ansonsten prüfen, ob der Iterator hasNext liefern kann
if (iteratorForSinglePage.hasNext())
{
return true;
}
// wenn nicht, ist die nächste Page zu laden und die Lieferung wieder zu delegieren
if (hasNextPage)
{
nextPage();
return iteratorForSinglePage.hasNext();
}
else
{
return false;
}
}
/**
* Setzt den internen Iterator auf die Datensätze der nächsten Page.
*/
@SuppressWarnings("unchecked")
private void nextPage()
{
// Beginn des ersten Results setzen
crit.setFirstResult(pageNumber * pageSize);
// Page weiterschalten
++pageNumber;
// Berechnen, ob noch weitere Pages vorhanden sind
List<T> result = crit.list();
hasNextPage = (result.size() == pageSize);
// Iterator setzen
iteratorForSinglePage = result.iterator();
}
/**
* Returns the next element in the iteration.
* @exception NoSuchElementException iteration has no more elements.
*/
@Override
public T next()
{
// wurde der Iterator noch nicht erzeugt, ist die erste Page zu laden
if (iteratorForSinglePage == null)
{
nextPage();
// Iterator der Liste übernimmt restliche Behandlung,
// inkl. der NoSuchElementException
return iteratorForSinglePage.next();
}
// ansonsten prüfen, ob der Iterator next liefern kann
if (iteratorForSinglePage.hasNext())
{
return iteratorForSinglePage.next();
}
// wenn nicht, ist die nächste Page zu laden und die Lieferung wieder zu delegieren
// inkl. der NoSuchElementException
nextPage();
return iteratorForSinglePage.next();
}
@Override
public void remove()
{
throw new UnsupportedOperationException();
}
}
}
Die Funktion findByCriteria wurde gelöscht. Als Beispiel nun eine spezifische Funktion in einem DAO:
Code:
public Iterable<Vorgang> findAllWithPaging(int pageSize)
{
return findByCriteriaWithPaging(pageSize);
}
Nun kann ich folgenden Code schreiben:
Code:
// Iteriere über alle Vorgänge
for (Vorgang vorgang : vorgangDao.findAllWithPaging(100))
{
...
}
und alle 100 Einträge wird ein SELECT gegen die Datenbank geschickt. Nicht schlecht, oder?