2012-09-24 10 views
6

Veo este patrón mucho.consultas paginadas/receta del iterador

en el servidor:

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

en el cliente:

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

mayoría de los lugares rodar sus propias versiones de estos dos métodos, y las implementaciones son sorprendentemente difícil hacerlo bien. Hay muchos errores de bordes.

¿Existe una receta o biblioteca canónica para estas consultas?

(puede hacer algunas suposiciones simplificadoras para el almacenamiento del lado del servidor, por ejemplo, T tiene un orden natural).

Respuesta

1

Aquí es uno usando AbstractIterator de la biblioteca de Google-guayaba y la primavera-JDBC para consultar la base de datos realidad:

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 esconde la mayor parte de las complicaciones que afectan a escribir su propia implementación de Iterator. Solo necesita implementar el método computeNext que devuelve el siguiente valor en el iterador o llama al endOfData para indicar que no hay más valores en el iterador.

+0

no creo que esto si el dtrt la tabla se modifica simultáneamente, porque todos los desplazamientos posteriores serán inconsistentes. – ashm

+0

En ese caso, debe pasar un parámetro adicional para el ID de dónde reanudar en lugar de usar el contador de página y el desplazamiento. La estructura del código no cambia. –

1

Aquí hay algo que funciona para mí. También usa AbstractIterator de la biblioteca google-guava, pero aprovecha Java8 Stream para simplificar la implementación. Devuelve un iterador de elementos de tipo 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(); 

También es posible devolver un Iterable utilizando el método de referencia de la siguiente manera:

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