lambda 에서 Collector를 이용해 GroupBy를 사용할 수 있다
결론만 말하자면 DB에 저장되어 있다면 DB에 있는 함수를 사용하길 권장하지만... 데이터 자체를 가공해야한다면 어쩔 수 없이 써야할 거 같다. 암튼 한번 알아보자.
다음과 같이 테스트 데이터를 준비했다.
데이터를 담을 객체를 먼저 생성한다
@Getter
@ToString
public class Sales {
private String yyyymm;
private String storeId;
private Long sales;
public Sales(String yyyymm, String storeId, Long sales) {
this.yyyymm = yyyymm;
this.storeId = storeId;
this.sales = sales;
}
}
@ExtendWith(SpringExtension.class)
public class TestGroupBy {
private List<Sales> dtos;
@BeforeEach
void init() {
dtos = new ArrayList<>();
Sales dto1 = new Sales("2020-11", "1", 6233800l);
Sales dto2 = new Sales("2020-11", "1", 725700l);
Sales dto3 = new Sales("2020-11", "2", 285300l);
Sales dto4 = new Sales("2020-11", "2", 437800l);
Sales dto5 = new Sales("2020-12", "1", 12991300l);
Sales dto6 = new Sales("2020-12", "1", 2650500l);
Sales dto7 = new Sales("2020-12", "2", 108400l);
Sales dto8 = new Sales("2020-12", "2", 445600l);
Sales dto9 = new Sales("2020-12", "1", 1314100l);
Sales dto10 = new Sales("2021-01", "1", 5660700l);
Sales dto11 = new Sales("2021-01", "3", 904300l);
Sales dto12 = new Sales("2021-01", "1", 8000l);
Sales dto13 = new Sales("2021-01", "1", 221500l);
Sales dto14 = new Sales("2021-01", "1", 391700l);
dtos.add(dto1);
dtos.add(dto2);
dtos.add(dto3);
dtos.add(dto4);
dtos.add(dto5);
dtos.add(dto6);
dtos.add(dto7);
dtos.add(dto8);
dtos.add(dto9);
dtos.add(dto10);
dtos.add(dto11);
dtos.add(dto12);
dtos.add(dto13);
dtos.add(dto14);
}
}
그럼 이제 GroupBy를 이용해 묶어보도록 한다
위의 객체에서 월(yyyymm), 그리고 storeId 별로 묶으려 한다
묶을 필드를 클래스로 만든다.
@Getter
@ToString
public class SalesKey {
private String yyyymm;
private String storeId;
public SalesKey(Sales salesDto) {
this.yyyymm = salesDto.getYyyymm();
this.storeId = salesDto.getStoreId();
}
}
그리고 stream을 이용해 묶기를 시도한다.
...
@Test
@DisplayName("groupBy mulitiple key test")
void groupByMultipleKeyTest() {
Map<SalesKey, List<Sales>> collect = dtos.stream()
.collect(Collectors.groupingBy(SalesKey::new));
// then
System.out.println("=======================================");
for (SalesKey salesKey : collect.keySet()) {
System.out.println("key:value = " + salesKey + ":" + collect.get(salesKey));
}
System.out.println("=======================================");
}
...
리턴된 유형을 보면 Map 형식에서 키=SalesKey와 값=List<Sales> 으로 구성되어 있다. 그룹별로 List객체에 담은 것이다. 그런데 로그를 찍어보니 조금 이상하다.
hashCode: 1008608255
hashCode: 1937693946
hashCode: 468776694
hashCode: 1959758632
hashCode: 939475028
hashCode: 14633842
hashCode: 1053744929
hashCode: 1455177644
hashCode: 8996952
hashCode: 918899286
hashCode: 732189840
hashCode: 2063009760
hashCode: 216746962
hashCode: 1613332278
=======================================
key:value = SalesKey(yyyymm=2020-11, storeId=1):[Sales(yyyymm=2020-11, storeId=1, sales=6233800)]
key:value = SalesKey(yyyymm=2021-01, storeId=3):[Sales(yyyymm=2021-01, storeId=3, sales=904300)]
key:value = SalesKey(yyyymm=2020-11, storeId=2):[Sales(yyyymm=2020-11, storeId=2, sales=437800)]
key:value = SalesKey(yyyymm=2021-01, storeId=1):[Sales(yyyymm=2021-01, storeId=1, sales=221500)]
key:value = SalesKey(yyyymm=2020-12, storeId=2):[Sales(yyyymm=2020-12, storeId=2, sales=445600)]
key:value = SalesKey(yyyymm=2020-12, storeId=1):[Sales(yyyymm=2020-12, storeId=1, sales=2650500)]
key:value = SalesKey(yyyymm=2021-01, storeId=1):[Sales(yyyymm=2021-01, storeId=1, sales=8000)]
key:value = SalesKey(yyyymm=2020-12, storeId=1):[Sales(yyyymm=2020-12, storeId=1, sales=12991300)]
key:value = SalesKey(yyyymm=2020-12, storeId=1):[Sales(yyyymm=2020-12, storeId=1, sales=1314100)]
key:value = SalesKey(yyyymm=2021-01, storeId=1):[Sales(yyyymm=2021-01, storeId=1, sales=391700)]
key:value = SalesKey(yyyymm=2020-12, storeId=2):[Sales(yyyymm=2020-12, storeId=2, sales=108400)]
key:value = SalesKey(yyyymm=2021-01, storeId=1):[Sales(yyyymm=2021-01, storeId=1, sales=5660700)]
key:value = SalesKey(yyyymm=2020-11, storeId=1):[Sales(yyyymm=2020-11, storeId=1, sales=725700)]
key:value = SalesKey(yyyymm=2020-11, storeId=2):[Sales(yyyymm=2020-11, storeId=2, sales=285300)]
=======================================
분명 yyyymm와 storeId 별로 묶여야 할거 같은데, 전체 다 1개씩 나열되어 있다. List안에도 1개씩만 들어있다. Java에서는 필드가 아닌 Hash코드를 통해 그룹을 분리한다. 그래서 해시코드를 찍어보면 다음과 같이 되어있다.
...
for (SalesKey salesKey : collect.keySet()) {
System.out.println("hashCode: " +salesKey.hashCode());
}
...
그럼 이 문제를 어떻게 해결하면 될까?
Lombok을 사용한다면 @EqualsAndHashCode 를 사용하면 된다.
SalesKey 객체에 다음을 추가한다
@Getter
@ToString
@EqualsAndHashCode // 추가
public class SalesKey {
private String yyyymm;
private String storeId;
public SalesKey(Sales salesDto) {
this.yyyymm = salesDto.getYyyymm();
this.storeId = salesDto.getStoreId();
}
}
해시 값을 확인해보면 중복되는건 제거된걸 확인할 수 있다.
hashCode: 490587957
hashCode: 490587958
hashCode: 490588017
hashCode: 492343797
hashCode: 490588016
hashCode: 492343799
=======================================
key:value = SalesKey(yyyymm=2020-11, storeId=1):[Sales(yyyymm=2020-11, storeId=1, sales=6233800), Sales(yyyymm=2020-11, storeId=1, sales=725700)]
key:value = SalesKey(yyyymm=2020-11, storeId=2):[Sales(yyyymm=2020-11, storeId=2, sales=285300), Sales(yyyymm=2020-11, storeId=2, sales=437800)]
key:value = SalesKey(yyyymm=2020-12, storeId=2):[Sales(yyyymm=2020-12, storeId=2, sales=108400), Sales(yyyymm=2020-12, storeId=2, sales=445600)]
key:value = SalesKey(yyyymm=2021-01, storeId=1):[Sales(yyyymm=2021-01, storeId=1, sales=5660700), Sales(yyyymm=2021-01, storeId=1, sales=8000), Sales(yyyymm=2021-01, storeId=1, sales=221500), Sales(yyyymm=2021-01, storeId=1, sales=391700)]
key:value = SalesKey(yyyymm=2020-12, storeId=1):[Sales(yyyymm=2020-12, storeId=1, sales=12991300), Sales(yyyymm=2020-12, storeId=1, sales=2650500), Sales(yyyymm=2020-12, storeId=1, sales=1314100)]
key:value = SalesKey(yyyymm=2021-01, storeId=3):[Sales(yyyymm=2021-01, storeId=3, sales=904300)]
=======================================
이제야 제대로 그룹별로 묶는것이 보인다.
그럼 이제 월,매장별 매출 갯수와 금액을 합쳐보자.
결과가 저장될 객체를 생성한다
@Getter
@ToString
public class SalesGroupBy {
private SalesKey salesKey;
private Long salesCount;
private Long salesSum;
public SalesGroupBy(SalesKey salesKey, Long salesCount, Long salesSum) {
this.salesKey = salesKey;
this.salesCount = salesCount;
this.salesSum = salesSum;
}
}
Collectors.toMap 을 이용해 해당 키에 해당하는 값을 불러와 stream 을 이용해 값을 카운팅하거나 합친 후, 각각의 값을 SalesGroupBy 객체에 주입하면서 생성한다.
...
Map<SalesGroupBy, SalesKey> collect1 = collect
.entrySet().stream()
.collect(Collectors.toMap(o -> {
Long salesCount = o.getValue().stream().count();
Long salesSum = o.getValue().stream().mapToLong(Sales::getSales).sum();
return new SalesGroupBy(o.getKey(), salesCount, salesSum);
}, Map.Entry::getKey));
System.out.println("=======================================");
for (SalesGroupBy salesGroupBy : collect1.keySet()) {
System.out.println("salesGroupBy = " + salesGroupBy);
}
System.out.println("=======================================");
...
결과를 보면 다음과 같이 나온다.
=======================================
salesGroupBy = SalesGroupBy(salesKey=SalesKey(yyyymm=2021-01, storeId=1), salesCount=4, salesSum=6281900)
salesGroupBy = SalesGroupBy(salesKey=SalesKey(yyyymm=2020-12, storeId=1), salesCount=3, salesSum=16955900)
salesGroupBy = SalesGroupBy(salesKey=SalesKey(yyyymm=2021-01, storeId=3), salesCount=1, salesSum=904300)
salesGroupBy = SalesGroupBy(salesKey=SalesKey(yyyymm=2020-11, storeId=2), salesCount=2, salesSum=723100)
salesGroupBy = SalesGroupBy(salesKey=SalesKey(yyyymm=2020-11, storeId=1), salesCount=2, salesSum=6959500)
salesGroupBy = SalesGroupBy(salesKey=SalesKey(yyyymm=2020-12, storeId=2), salesCount=2, salesSum=554000)
=======================================
원하는데로 값이 샘되어 나온것을 확인할 수 있다.
여기서 배운 것.
1) multiple field를 사용할 땐 Key 역할을 할 객체를 새로 만드는게 좋다.
2) Key 객체는 반드시 hashCode 쓰도록 @EqualsAndHashCode 를 사용한다
끝.
'공부 > 프로그래밍' 카테고리의 다른 글
[springboot] swagger 설정 & 사용법 (1) | 2021.02.04 |
---|---|
[spring] 객체 내 객체에 @Valid 점검하기 (0) | 2021.01.25 |
[retrofit, gson] Expected BEGIN_OBJECT but was STRING 에러 해결(String -> LocalDate) (1) | 2021.01.18 |
[jpa] 쿼리 로그 설정 (0) | 2021.01.15 |
[axios, react, redux] 서버통신 시 로딩바 띄우기 (0) | 2021.01.13 |
댓글