如何使用 java 流对 JPA 存储库中的字段进行分组和平均并放入新集合中
How to group and average fields from JPA repo and put into new collection using java streams
我需要计算一周中所选日期的平均入住率(例如,所有星期五 - 每分钟)。由于缺少 Date/Time 函数,我没有找到解决此问题的任何 JPQL/Querydsl 方法。所以我正在尝试使用 Java 流。我的(简化)对象:
class Occupancy {
private LocalDateTime timeStamp;
private int occupied;
}
我的回购:
@Query("select o from Occupancy o")
public Stream<Occupancy> streamAllOccupancies();
样本:
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)
)
);
}
有效 - 但是否可以将此地图更改为我的占用对象列表:
new Occupancy( localTime, averagedOccupancy );
我也担心流效率 - 它必须处理数据库中的所有记录。流如何与 jpa repo 一起工作?首先 SQL 获取所有记录 - 然后流处理它?或者它们是在每条记录上按顺序处理的?也许最好的解决方案是使用 Native SQL query insted of Stream?任何想法都会很有帮助...
至于转换为 List<Occupancy>
,请注意 occupied
字段是 int
类型,而平均值可以是非整数。所以我假设 Occupancy
class 是这样定义的:
class Occupancy {
private LocalDateTime timeStamp;
private double occupied;
public Occupancy(LocalDateTime ts, double occ) {
this.timeStamp = ts;
this.occupied = occ;
}
}
现在您可以从生成的地图中再创建一个流:
List<Occupancy> occupancies = avgMap.entrySet().stream()
.map(e -> new Occupancy(e.getKey(), e.getValue()))
.collect(Collectors.toList());
似乎中间 Map
是不可避免的(至少如果您的流尚未按 LocalTime
排序)。
至于内存使用:它取决于底层 JDBC 驱动程序。结果流确实逐行读取底层 ResultSet
,但它是 JDBC 特定的,一次预缓冲了多少行。例如,众所周知 MySQL 驱动程序默认将完整的 ResultSet
检索到内存中,因此您可能需要这样的查询提示:
@QueryHints(value = @QueryHint(name = HINT_FETCH_SIZE, value = "" + Integer.MIN_VALUE))
详情见this blog post。
另请注意,如果您的 JDBC 驱动程序实际上是从服务器逐行获取数据(没有缓冲),这实际上可能会降低性能,因为您可能需要在 DBMS 和您的应用程序(如果 DBMS 服务器位于不同的机器上,这可能尤其重要)。因此,请参阅您的 JDBC 驱动程序文档以获取更多详细信息。
我需要计算一周中所选日期的平均入住率(例如,所有星期五 - 每分钟)。由于缺少 Date/Time 函数,我没有找到解决此问题的任何 JPQL/Querydsl 方法。所以我正在尝试使用 Java 流。我的(简化)对象:
class Occupancy {
private LocalDateTime timeStamp;
private int occupied;
}
我的回购:
@Query("select o from Occupancy o")
public Stream<Occupancy> streamAllOccupancies();
样本:
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)
)
);
}
有效 - 但是否可以将此地图更改为我的占用对象列表:
new Occupancy( localTime, averagedOccupancy );
我也担心流效率 - 它必须处理数据库中的所有记录。流如何与 jpa repo 一起工作?首先 SQL 获取所有记录 - 然后流处理它?或者它们是在每条记录上按顺序处理的?也许最好的解决方案是使用 Native SQL query insted of Stream?任何想法都会很有帮助...
至于转换为 List<Occupancy>
,请注意 occupied
字段是 int
类型,而平均值可以是非整数。所以我假设 Occupancy
class 是这样定义的:
class Occupancy {
private LocalDateTime timeStamp;
private double occupied;
public Occupancy(LocalDateTime ts, double occ) {
this.timeStamp = ts;
this.occupied = occ;
}
}
现在您可以从生成的地图中再创建一个流:
List<Occupancy> occupancies = avgMap.entrySet().stream()
.map(e -> new Occupancy(e.getKey(), e.getValue()))
.collect(Collectors.toList());
似乎中间 Map
是不可避免的(至少如果您的流尚未按 LocalTime
排序)。
至于内存使用:它取决于底层 JDBC 驱动程序。结果流确实逐行读取底层 ResultSet
,但它是 JDBC 特定的,一次预缓冲了多少行。例如,众所周知 MySQL 驱动程序默认将完整的 ResultSet
检索到内存中,因此您可能需要这样的查询提示:
@QueryHints(value = @QueryHint(name = HINT_FETCH_SIZE, value = "" + Integer.MIN_VALUE))
详情见this blog post。
另请注意,如果您的 JDBC 驱动程序实际上是从服务器逐行获取数据(没有缓冲),这实际上可能会降低性能,因为您可能需要在 DBMS 和您的应用程序(如果 DBMS 服务器位于不同的机器上,这可能尤其重要)。因此,请参阅您的 JDBC 驱动程序文档以获取更多详细信息。