어떤 값의 평균이 필요해졌다. 식은 다음과 같았다.
avg = total_value(생산량 합) / working_day(생산한 일수)
DTO는 id
, total_value
, max_date
, min_date
로 4개의 값을 가지고 있었다.
평균을 구하기 위해 controller
에서 for-each문을 돌려 List<DTO>
의 각 값에 접근하여 avg
를 구해 List<float>
에 넣어주었다.
이렇게 하면 끝일 줄 알았다. view
에서는 th:each
써주면 끝이니깐..
그런데 이렇게 view
단에 보내면 table 형태인 view
에서는 두개의 리스트를 돌리지 못했다.
ㅇ ㅏ
<tr th:each="milk : ${milkList}" th:each="milkAvg : ${milkAvgList}">
<td th:text="${milk.cow_id}"></td>
<td th:text="${milkAvg}"></td>
<td th:text="${milk.total_value}"></td>
</tr>
당연히 오류난다.
<tr th:each="milk : ${milkList}">
<td th:text="${milk.cow_id}"></td>
<td th:text="${milk.total_value}"></td>
<tr th:each="milkAvg: ${milkAvgList}">
<td th:text="${milkAvg}"></td>
</tr>
</tr>
view 상에서 안보임
<tr th:each="milk : ${milkList}">
<td th:text="${milk.cow_id}"></td>
<td th:text="${milkAvgList>"></td>
<td th:text="${milk.total_value}"></td>
</tr>
milkAvgList
의 모든 데이터가 한 행에 들어가면서 로딩이 길어진다
<tr th:each="milk : ${milkList}">
<td th:text="${milk.cow_id}"></td>
<td th:text="${milk.total_value}"></td>
</tr>
<tr th:each="milkAvg: ${milkAvgList}">
<td th:text="${milk.avg_value}"></td>
</tr>
1열에 id
, avg_value
값이 들어가게 되면서 3개의 열이 있어야하는데 2개의 열만 존재하게 됨
결국 리스트 하나에 view에 필요한 데이터를 모두 넣어야겠다는 생각이 들었고, DTO를 바꿔야 겠다는 생각까지 도달했다.
쓰려고 보니 문득, 그냥 계속 진행했어햐 했는데
query문에서 DTO에 맞춰 PROJECTION 하고 있었기 때문에 avg 값을 DTO에 추가해주면 query문에서 저 값에 어떤 값을 넣어줘야할지 의문이 들었다.
query 일부분 👇
.select(new QMilkListDTO(qcowActivity.id, qActivity.value.sum(), qActivity.activityDate.min(), qActivity.activityDate.max()))
다시 말하면, controller
에서 계산한 avg
를 넣어주기 위해 DTO 속성을 추가해주게 되면 생성자에 avg
를 위한 파라미터가 추가되기 때문에 위 코드에서처럼 qActivity.(어떤 값)
을 넣어줘야하는데 떠오르는 것이 전혀 없었다.
@QueryProjection
public MilkListDTO(String cow_id, float total_value, LocalDateTime min_activity_date, LocalDateTime max_activity_date) {
this.cow_id = cow_id;
this.total_value = total_value;
this.min_activity_date = min_activity_date;
this.max_activity_date = max_activity_date;
}
DTO의 생성자 부분에는 파라미터를 받아 바인딩 시켜주는 것만 작성해야 한다고 생각했기 때문에 저런 생각이 들었던 것이다. (위 코드처럼)
그래서 DTO를 건드리지 않고, HashMap<Integer, MilkListDTO>
를 사용하여 avg
값을 Key 값으로 하고 Value 값에 DTO를 넣어 avg
과 DTO 모두 view
단에 넘길 수 있게 했다.
평균이 키 값이 되는게 맘에 들진 않았지만 일단 해봤다.
<tr th:each="milk : ${milkList}">
<td th:text="${milk.value.id}"></td>
<td th:text="${milk.key}"></td>
<td th:text="${milk.value.total_value"></td>
</tr>
코드가 맘에 들지 않았지만 출력은 제대로 될 거라고 생각했다.
음? 985개의 항목이 보여야하는데 40개의 항목만 보였다. 생각대로 될리가 없지
디버깅해보니 DTO 타입의 리스트는 985개를 가져오는데 HashMap에 넣기만 하면 40개가 되버리는 것을 볼 수 있었다.
오류 메세지도 없었기에 한참을 원인을 생각했다.
뒤늦게 생각났다. Key 값은 중복될 수 없다. 그런데 avg
는 중복될 수 있는 값이다.
이걸 놓쳤다. 이미 같은 값의 Key 값이 들어가 있으니 put이 안될수 밖에.
Key-Value
형태 이다안그래도 Key값이
avg
값인 것이 맘에 안들어서 다른 방식을 찾아보려 했는데 그냥 안되는 방법이었다. 미련남지 않게 해줘서 고맙다..
avg
속성을 DTO에 추가해주고,controller
에서 계산하던 avg
를 DTO의 생성자에서 계산하도록 해줬다. avg
속성을 추가하더라도 @QueryProjection
부분의 생성자에는 파라미터를 추가할 필요가 없다는 것을 알게 되었다. 그냥 시도해볼걸..
private String id;
private float total_value;
private float avg_value;
@QueryProjection
public CowMilkListDTO(String id, float total_value, LocalDateTime min_activity_date, LocalDateTime max_activity_date) {
this.id = id;
this.total_value = total_value;
Duration duration = Duration.between(min_activity_date, max_activity_date);
avg_value = total_value / duration.toDays();
}
DTO에 선언한 변수와 @QueryProjection의 생성자의 파라미터가 같을 필요가 없었다.
avg
를 DTO에서 계산하면서 controller
에서 view
로 리스트 하나만 넘겨줄 수 있게 되었다.
@GetMapping("/id")
public String individualProductivity(Model model) {
List<MilkListDTO> milkList = ActivityService.findTotalMilkList();
model.addAttribute("milkList", milkList);
return "productivity/id";
}
저거 하나로 코드가 이렇게 간단해지다니
DTO, @QueryProjection
에 대해 좀 더 공부가 필요한 것 같다. (Java 자료구조 클래스도)