DTO / Map) 이렇게 쓸 수 있는 거였어...? / 공부 다시 해야겠다

AeZan·2021년 10월 1일
2

어떤 값의 평균이 필요해졌다. 식은 다음과 같았다.

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에서는 두개의 리스트를 돌리지 못했다.
ㅇ ㅏ

📎 두 개의 리스트 값을 table에 가져오기 위한 노력

<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을 써볼까..?

그래서 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개가 되버리는 것을 볼 수 있었다.

오류 메세지도 없었기에 한참을 원인을 생각했다.

📎 Map 공부 다시하자😓

뒤늦게 생각났다. Key 값은 중복될 수 없다. 그런데 avg는 중복될 수 있는 값이다.
이걸 놓쳤다. 이미 같은 값의 Key 값이 들어가 있으니 put이 안될수 밖에.

  • HashMap은 Map 인터페이스의 구현 클래스이다.
  • Key-Value 형태 이다
  • Key 값은 중복된 값을 허용하지 않는다.
  • 순서가 없다.

안그래도 Key값이 avg값인 것이 맘에 안들어서 다른 방식을 찾아보려 했는데 그냥 안되는 방법이었다. 미련남지 않게 해줘서 고맙다..


📎 돌고돌아 처음 생각했던 대로 DTO 수정으로 돌아가자

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 자료구조 클래스도)

0개의 댓글