我有一个主题:
@Entity
public class Topic {
@Id
private int id;
private LocalDate date;
private String name;
private int points;
@JoinColumn(name = "user_id", nullable = false)
private User user;
}
我正在通过spring data jpa方法获取给定日期的主题列表:
列出主题= topicRepository.findByDateBetween(开始,结束); 输出的内容例如:
Topic(id=1, date="2018-01-01", name="Java examples", User(...), 12)
Topic(id=2, date="2018-02-02", name="Java examples", User(...), 34)
Topic(id=3, date="2018-02-02", name="Java examples", User(...), 56)
Topic(id=4, date="2018-03-03", name="Java examples", User(...), 78)
Topic(id=5, date="2018-03-03", name="Java examples", User(...), 90)
我要达到的目的是将结果输出过滤为(如果日期&&用户是相同的添加点)
Topic(id=1, date="2018-01-01", name="Java examples", User(...), 12)
Topic(id=2, date="2018-02-02", name="Java examples", User(...), 90)
Topic(id=4, date="2018-03-03", name="Java examples", User(...), 168)
我的实际解决方案返回以日期为键,将总和点作为值的地图,但是我需要在输出中提供更多数据,例如用户或名称。
return topics.stream()
.collect(Collectors.groupingBy(Topic::getDate,
Collectors.summingInt(Topic::getPoints)));
在这种情况下,也许还有另一种方法代替创建dto的地图返回?例如
@Data
public class ResultDto {
private LocalDate date;
private String name;
private int points;
private User user;
}
答案 0 :(得分:1)
假设User
一致地实现hashCode
和equals
,则可以分组为日期/用户复合键和ResultDto
值的映射。为此,您需要执行两项操作:一项操作映射to
,另一项操作汇总每个组的点。 (您可以将这些操作放在ResultDto
或实用程序类中,在这里,我假设第一个操作在Mapper
实用程序类中,第二个在ResultDto
中) :
public final class Mapper {
private Mapper() { }
public static ResultDto fromTopic(Topic topic) {
ResultDto result = new ResultDto();
result.setDate(topic.getDate());
result.setName(topic.getName());
result.setPoints(topic.getPoints());
result.setUser(topic.getUser());
return result;
}
}
在ResultDto
中:
public ResultDto merge(ResultDto another) {
this.points += another.points;
return this;
}
请注意,我要分配在Topic.name
映射操作中找到的第一个Mapper.fromTopic
。我以为这是示例中的不一致之处,如果您在实际情况下使用此方法,请考虑到这一点。
现在,我们可以串流主题并按日期/用户分组:
Map<List<Object>, ResultDto> groups = topics.stream()
.collect(Collectors.toMap(
topic -> Arrays.asList(topic.getDate, topic.getUser()), // Java 9: List.of
Mapper::fromTopic,
ResultDto::merge));
这会收集到一个键为List
的地图,其第一个元素为日期,第二个元素为用户。这些2元素列表对于以后的使用不是很有用,这里我们仅使用它们来创建复合键并通过该键对主题进行分组。映射的值是一个ResultDto
实例,该实例最初是从主题创建的,然后与属于同一组(相同日期和用户)的其他主题合并。在此ResultDto.merge
操作中,points
被求和。
您需要的结果是地图的值:
Collection<ResultDto> results = groups.values(); // or new ArrayList<>(groups.values())
编辑::这是一个没有流的更简洁的变体:
Map<List<Object>, ResultDto> groups = new HashMap<>();
topics.forEach(topic -> groups.merge(
List.of(topic.getDate(), topic.getUser()), // Java 8: Arrays.asList
Mapper.fromTopic(topic),
ResultDto::merge));
Collection<ResultDto> results = groups.values();
答案 1 :(得分:1)
按字段子集分组的一种简单方法是将TreeMap
与自定义Comparator
一起使用。假设您要定义
Comparator<Topic> byDateAndUser = Comparator.comparing(Topic::getDate)
.thenComparing(t -> t.getUser().getUserId());
Map<Topic,...> map = new TreeMap<>(byDateAndUser);
结果图将使用提供的比较器而不是equals
来确定相等性,因此会将具有相同日期和用户的所有主题视为相同。
TreeMap
的此功能允许您计算到总点的主题图,并且对于日期/用户的每种组合,将只包含一个条目:
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.summingInt;
Comparator<Topic> byDateAndUser = Comparator.comparing(Topic::getDate)
.thenComparing(t -> t.getUser().getUserId());
Map<Topic, Integer> pointTotals = topics.stream()
.collect(groupingBy(
topic -> topic,
() -> new TreeMap<>(byDateAndUser),
summingInt(Topic::getPoints)
));