2016-05-31 20 views
1

Ich muss die durchschnittliche Belegung für den ausgewählten Wochentag berechnen (zB alle Freitage - für jede Minute). Ich habe keine JPQL/Querydsl-Lösung für dieses Problem wegen fehlender Date/Time-Funktionen gefunden. Also versuche ich Java Streams zu benutzen. Mein (vereinfacht) Objekt:Wie man Felder aus dem JPA-Repo gruppiert und gemittelt und mit Java-Streams in eine neue Sammlung eingefügt wird

class Occupancy { 
    private LocalDateTime timeStamp; 
    private int occupied; 
} 

meine Repo:

@Query("select o from Occupancy o") 
public Stream<Occupancy> streamAllOccupancies(); 

Beispiel:

try (Stream<Occupancy> stream = repository.streamAllOccupancies()) { 

    Function<Occupancy,LocalTime> OccupancyMinutesGrouping = (Occupancy o) -> { 
     return o.getDateTime().toLocalTime().truncatedTo(ChronoUnit.MINUTES); 
    }; 


    Map<LocalTime,Double> avgMap = stream 
     .filter(o -> o.getDateTime().getDayOfWeek() == DayOfWeek.MONDAY) //example 
     .collect(
      Collectors.groupingBy(
       OccupancyMinutesGrouping, 
       Collectors.averagingInt(Occupancy::getOccupied) 
      ) 
     ); 
} 

Es funktioniert - aber ist es möglich, ändern Sie die Karte in die Liste meiner Belegungsobjekte:

new Occupancy(localTime, averagedOccupancy); 

Ich bin auch Sorgen über die Effizienz der Streams - es muss alle Datensätze aus der Datenbank verarbeiten. Wie funktioniert der Stream mit jpa repo? Erste SQL bekommt alle Datensätze - dann Stream verarbeitet es? Oder werden sie nacheinander auf jedem Datensatz verarbeitet? Vielleicht ist die beste Lösung, eine Native SQL-Abfrage anstelle von Stream zu verwenden? Irgendwelche Ideen werden sehr hilfreich sein ...

Antwort

1

Wie für die Umwandlung in die List<Occupancy>, beachten Sie bitte, dass occupied Feld von int Typ ist, während der Durchschnitt nicht-integral sein könnte. Also gehe ich davon aus, dass die Occupancy Klasse auf diese Weise definiert ist:

class Occupancy { 
    private LocalDateTime timeStamp; 
    private double occupied; 

    public Occupancy(LocalDateTime ts, double occ) { 
     this.timeStamp = ts; 
     this.occupied = occ; 
    } 
} 

Jetzt können Sie nur noch einen Strom erzeugen aus der resultierenden Karte:

List<Occupancy> occupancies = avgMap.entrySet().stream() 
    .map(e -> new Occupancy(e.getKey(), e.getValue())) 
    .collect(Collectors.toList()); 

Es scheint, dass Zwischen Map unvermeidbar ist (zumindest, wenn Dein Stream ist nicht bereits nach LocalTime sortiert).

Wie Speicherverbrauch: es hängt von dem zugrunde liegenden JDBC-Treiber. Der resultierende Stream liest tatsächlich die zugrunde liegende Zeile ResultSet Zeile für Zeile, aber es ist JDBC-spezifisch, wie viele Zeilen gleichzeitig gepuffert werden. Zum Beispiel ist es bekannt, dass MySQL-Treiber standardmäßig komplett ResultSet in den Speicher abruft, so dass Sie kann einige Abfrage-Hinweis, wie diese benötigen:

@QueryHints(value = @QueryHint(name = HINT_FETCH_SIZE, value = "" + Integer.MIN_VALUE)) 

Siehe this blog post für weitere Einzelheiten.

Wenn Ihr JDBC-Treiber die Daten Zeile für Zeile vom Server abruft (ohne Pufferung), könnte dies sogar eine schlechtere Performance haben, da Sie möglicherweise mehr Round-Trips zwischen DBMS und Ihrer Anwendung benötigen besonders wichtig, wenn sich der DBMS-Server auf einem anderen Rechner befindet). Weitere Informationen finden Sie in der JDBC-Treiberdokumentation.

+0

Vielen Dank für Ihre Antwort. Natürlich funktioniert die Lösung mit dem zweiten Stream, allerdings habe ich überlegt, nur einen Stream und group-> average zu verwenden und dann die Ergebnisse in die Liste aufzunehmen. Ich benutze H2 in der Entwicklung - aber in prod. es wird MySQL sein. Danke für den Tipp. – Aragornx