2012-09-24 2 views
6

Je vois beaucoup ce modèle.requêtes paginées/recette d'itérateur

Sur le serveur:

// 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); 

Le client:

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

La plupart des endroits roulent leurs propres versions de ces deux méthodes, et les mises en œuvre sont étonnamment difficiles à obtenir le droit. Il y a beaucoup de bogues de cas limites.

Existe-t-il une recette canonique ou une bibliothèque pour ces requêtes?

(vous pouvez faire quelques suppositions simplificatrices pour le stockage côté serveur, par exemple T a un ordre naturel).

Répondre

1

Voici un à l'aide de la bibliothèque AbstractIterator google-goyave et le printemps-jdbc pour interroger la base de données réellement:

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 cache la plupart des complications impliquant la rédaction de votre propre implémentation de Iterator. Vous devez uniquement implémenter la méthode computeNext qui renvoie la valeur suivante dans l'itérateur ou appelle endOfData pour indiquer qu'il n'y a plus de valeurs dans l'itérateur.

+0

Je ne pense pas que cela dtrt si la La table est modifiée simultanément, car tous les offsets suivants seront incohérents. – ashm

+0

Dans ce cas, vous transmettez un paramètre supplémentaire pour l'ID de l'endroit où vous voulez reprendre au lieu d'utiliser le compteur de pages et le décalage. La structure du code ne change pas. –

1

Voici quelque chose qui fonctionne pour moi. Il utilise également AbstractIterator de la bibliothèque google-guava mais profite de Java8 Stream pour simplifier l'implémentation. Il renvoie un itérateur d'éléments de type 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(); 

Il est également possible de retourner un Iterable en utilisant la méthode de référence de la manière suivante:

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