2012-09-24 5 views
6

Ich sehe dieses Muster sehr.paginierte Abfragen/Iteratorrezept

Auf Server:

// Get a bounded number of results, along with a resume token to use 
// for the next call. Successive calls yield a "weakly consistent" view of 
// the underlying set that may or may not reflect concurrent updates. 
public<T> String getObjects(
     int maxObjects, String resumeToken, List<T> objectsToReturn); 

Auf Client:

// An iterator wrapping repeated calls to getObjects(bufferSize, ...) 
public<T> Iterator<T> getIterator(int bufferSize); 

Die meisten Orte rollen ihre eigenen Versionen dieser beiden Methoden, und die Implementierungen sind überraschend schwer richtig zu machen. Es gibt viele Edge-Case-Bugs.

Gibt es ein kanonisches Rezept oder eine Bibliothek für diese Abfragen?

(Sie können einige vereinfachende Annahmen für den serverseitigen Speicher machen, z. B. T hat eine natürliche Reihenfolge).

Antwort

1

ist hier ein AbstractIterator von Google-Guave-Bibliothek und Feder-jdbc eigentlich mit der Datenbank abzufragen:

public Iterable<T> queryInBatches(
     final String query, 
     final Map<String, Integer> paramMap, 
     final int pageSize, final Class<T> elementType) { 
    return new Iterable<T>() { 
     @Override 
     public Iterator<T> iterator() { 
      final Iterator<List<T>> resultIter = 
        queryResultIterator(query, paramMap, pageSize, elementType); 

      return new AbstractIterator<T>() { 
       private Iterator<T> rowSet; 

       @Override 
       protected T computeNext() { 
        if (rowSet == null) { 
         if (resultIter.hasNext()) { 
          rowSet = resultIter.next().iterator(); 
         } else { 
          return endOfData(); 
         } 
        } 

        if (rowSet.hasNext()) { 
         return rowSet.next(); 
        } else { 
         rowSet = null; 
         return computeNext(); 
        } 
       }}; 
     }}; 
} 


private AbstractIterator<List<T>> queryResultIterator(
     final String query, final Map<String, Integer> paramMap, 
     final int pageSize, final Class<T> elementType) { 
    return new AbstractIterator<List<T>>() { 
     private int page = 0; 

     @Override 
     protected List<T> computeNext() { 
      String sql = String.format(
        "%s limit %s offset %s", query, pageSize, page++ * pageSize); 
      List<T> results = jdbc().queryForList(sql, paramMap, elementType); 
      if (!results.isEmpty()) { 
       return results; 
      } else { 
       return endOfData(); 
      } 
     }}; 
} 

AbstractIterator verbirgt die meisten der Komplikationen eine eigene Implementierung von Iterator beteiligt zu schreiben. Sie müssen nur die Methode computeNext implementieren, die entweder den nächsten Wert im Iterator zurückgibt oder aufruft, um anzuzeigen, dass im Iterator keine Werte mehr vorhanden sind.

+0

Ich glaube nicht, das wird richtig verhalten sollte, wenn die Tabelle wird gleichzeitig geändert, da alle nachfolgenden Offsets inkonsistent sein werden. – ashm

+0

In diesem Fall geben Sie einen zusätzlichen Parameter für die ID ein, für die die Fortsetzung von statt des Seitenzählers und des Offsets verwendet werden soll. Die Codestruktur ändert sich nicht. –

1

Hier ist etwas, das für mich funktioniert. Es verwendet auch den AbstractIterator der Google-Guava-Bibliothek, nutzt aber Java8 Stream, um die Implementierung zu vereinfachen. Es gibt einen Iterator von Elementen des Typs T.

Iterator<List<T>> pagingIterator = new AbstractIterator<List<T>>() { 
    private String resumeToken; 
    private boolean endOfData; 

    @Override 
    protected List<T> computeNext() { 
     if (endOfData) { 
      return endOfData(); 
     } 

     List<T> rows = executeQuery(resumeToken, PAGE_SIZE); 

     if (rows.isEmpty()) { 
      return endOfData(); 
     } else if (rows.size() < PAGE_SIZE) { 
      endOfData = true; 
     } else { 
      resumeToken = getResumeToken(rows.get(PAGE_SIZE - 1)); 
     } 

     return rows; 
    } 
}; 

// flatten Iterator of lists to a stream of single elements 
Stream<T> stream = StreamSupport.stream(Spliterators.spliteratorUnknownSize(pagingIterator, 0), false) 
    .flatMap(List::stream); 

// convert stream to Iterator<T> 
return stream.iterator(); 

Es ist auch möglich, eine Iterable unter Verwendung von Verfahren Bezug auf folgende Weise zurück:

// convert stream to Iterable<T> 
return stream::iterator; 
Verwandte Themen