코딩관계론

[실무 역량 과제] 신규 유형 (BE) 본문

개발/Java

[실무 역량 과제] 신규 유형 (BE)

개발자_티모 2024. 5. 30. 20:54
반응형

문제 이해하기

Get 요청으로 다음과 같은 URI가 있고, 쿼리 파람을 통해서 예약 상태를 조회하고자 한다. 이 때 주어지는 조건이 여러가지 있는데 모두를 만족시켜야 한다.

@GetMapping("/api/reservation/search")

 

문제 해결 방법

쿼리파람을 DTO 객체에 리플렉션하기

쿼리파람을 DTO 객체에 리플렉션을 하려면 객체의 기본생성자가 존제해야 한다. 그 이유는 Spring이 기본 생성자를 사용하는 이유는 리플렉션을 통한 객체 생성의 필요성, 객체 상태 초기화의 유연성, 데이터 바인딩의 일관성을 유지하기 위해서입니다. 기본 생성자가 없으면 이러한 과정을 수행할 수 없기 때문에, DTO 클래스에는 반드시 기본 생성자가 필요합니다.

 

1. 객체 생성의 일관성

기본 생성자를 통해 객체를 생성하면, Spring은 모든 파라미터를 가진 생성자와 독립적으로 객체를 생성할 수 있습니다. 이를 통해 데이터 바인딩 과정에서 다양한 요청 파라미터를 일관되게 처리할 수 있습니다.

2. 리플렉션(Reflection)을 통한 객체 생성

Spring의 데이터 바인딩 메커니즘은 리플렉션을 사용하여 객체를 생성하고 필드를 설정합니다. 리플렉션을 통해 객체를 생성하려면 기본 생성자가 필요합니다. 기본 생성자가 없으면 리플렉션을 사용하여 객체를 생성할 수 없습니다.

3. 객체 상태 초기화

기본 생성자는 객체를 초기화하는 데 사용됩니다. Spring은 기본 생성자를 호출하여 객체를 생성한 후, setter 메서드를 통해 각 필드를 초기화합니다. 이렇게 하면 객체의 초기 상태를 설정하고, 이후 요청 파라미터에 따라 필드 값을 설정할 수 있습니다.

4. 유연한 데이터 바인딩

기본 생성자를 사용하면 다양한 필드 조합을 유연하게 처리할 수 있습니다. 모든 필드를 초기화하는 생성자가 있는 경우, 특정 필드가 없는 요청을 처리하기 어렵습니다. 기본 생성자를 사용하면 모든 필드를 초기화한 후, 필요한 필드만 설정할 수 있습니다.

 

문자열 비교

자바에서는 동등성 및 동일성의 개념이 있다.

 

간단하게 말하면 동일성은 "=="비교를 통해 주소 값을 비교하는 것이고, 동일성은 equals함수를 통해서 값으로 판별한다.(오버라이딩을 수행했다고 가정)

 

우리는 all 일때 와 name이 들어올때를 구분해야 하는데 동등성 비교를 위해서 equals로 비교를 진행해야 한다. 파이썬만을 사용하다보니 "=="을 무의식적으로 사용하면 왜 안되는지 디버깅하는데 시간을 잡아 먹었다.

 

그 후 all일때는 정렬을 통해서 값들을 오름차순으로 반환해야 한다. SORT 함수는 다음과 같이 작성할 수 있다.

List<ReservationDTO> reservationsDTO = reqDTO.stream().sorted((r1, r2) -> 
r1.getCheckIn().compareTo(r2.getCheckIn())).collect(Collectors.toList());

 

sort함수를 확인해보면 *Compator 함수형 인터페이스를 구현하도록 해놨다.

public abstract java.util.stream.Stream<T> sorted(java.util.Comparator<? super T> arg0);
public abstract interface Comparator<T> {
  public abstract int compare(T arg0, T arg1);
}

 

우리는 이 compare함수를 람다로 구현했다. 왜냐면 함수형 인터페이스이기 때문이다.

 

코드

package project;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@SpringBootApplication
public class App {

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }

    @RestController
    public static class ApiController {
        private final String DATA_DIR = "../data/input";
        private final ObjectMapper objectMapper = new ObjectMapper();

        @GetMapping("/api/reservation/search")
        public ResponseEntity<?> login(RequestDTO dto) {

            try {
                List<ReservationDTO> reqDTO = objectMapper.readValue(
                    Files.readAllBytes(Paths.get(DATA_DIR, "reservation.json")),
                    new TypeReference<List<ReservationDTO>>() {}
                );

                // 정렬 
                List<ReservationDTO> reservationsDTO = reqDTO.stream().sorted((r1, r2) -> r1.getCheckIn().compareTo(r2.getCheckIn())).collect(Collectors.toList());

                if (dto.getCustomerName().isEmpty()){
                    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ResponseErrorDTO("customerName is required"));
                }

                if (dto.getCustomerName().equals("all")){
                    return ResponseEntity.status(HttpStatus.OK).body(reservationsDTO);
                }

                List<ReservationDTO> result = reservationsDTO.stream().filter(e -> e.getCustomerName().contains(dto.getCustomerName())).collect(Collectors.toList());
                return ResponseEntity.status(HttpStatus.OK).body(result);


            } catch (Exception e) {
                e.printStackTrace();
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
            }

        }
    }

    public static class RequestDTO {
        public String customerName;

        public RequestDTO(){
            customerName="";
        }

        public RequestDTO(String customerName) {
            this.customerName = customerName;
        }

        public String getCustomerName() {
            return customerName;
        }

        public void setCustomerName(String customerName) {
            this.customerName = customerName;
        }
    }

    public static class ResponseErrorDTO {
        public String error;

        public ResponseErrorDTO(String error) {
            this.error = error;
        }

        public String getError() {
            return error;
        }

        public void setError(String error) {
            this.error = error;
        }


    }

    public static class ReservationDTO {
        public Integer id;

        @JsonProperty("customer_name")
        public String customerName;
    
        @JsonProperty("check_in")
        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
        public Date checkIn;
    
        @JsonProperty("check_out")
        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
        public Date checkOut;
    
        public String status;

        // 기본 생성자 추가
        public ReservationDTO() {
        }

        // Getters and Setters
        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public String getCustomerName() {
            return customerName;
        }

        public void setCustomerName(String customerName) {
            this.customerName = customerName;
        }

        public Date getCheckIn() {
            return checkIn;
        }

        public void setCheckIn(Date checkIn) {
            this.checkIn = checkIn;
        }

        public Date getCheckOut() {
            return checkOut;
        }

        public void setCheckOut(Date checkOut) {
            this.checkOut = checkOut;
        }

        public String getStatus() {
            return status;
        }

        public void setStatus(String status) {
            this.status = status;
        }
    }
}

코드 리뷰

DTO 매핑을 위해선 반듯이 기본생성자가 필수다.

 

배운점 정리하기

왜 필수냐 리플렉션을 사용하려면 기본생성자로 객체를 생성해야 하며, 기본 생성자로 생성하면 객체의 다양성을 제공한다.

 

함수형 인터페이스(Functional Interface)를 구현할 수 있습니다. 함수형 인터페이스는 하나의 추상 메서드만을 가지는 인터페이스를 말합니다.

반응형