본문 바로가기

Back-End/SpringBoot

Servlet Request Param String to ZoneDateTime

스프링 백엔드 도메인에서 날짜 관련 필드를 ZoneDateTime을 사용하고 있다.

 

처음 API 설계시 front에서 날짜관련 필드 API요청을  yyyymmdd 포맷으로 전송하기 때문에

requestDto, responseDto에 있는 날짜 관련 필드를 String으로 정의했다.

 

이게 별로 맘에 안들어서 String을 ZoneDateTime으로 변경하기로 했다.

 

HTTP 바디 적용

 

serialize

responseDto에 필드위에 @JsonFormat 애노테이션으로 쉽게 변경가능하다

 

deseirialize

requestDto는 날짜가 바디에 있는 경우 HTTP 메시지 converter가 변환해주고

requestParam의 경우  Argument Resolver가 변환해 주는데

 

custom deserializer 추가하면

argumentResolver에서 결국 추가한 objectMapper deserializer를 참고하여 처리할 것이라고 생각한다.

 

argumentResolver에서는 resolveArgument 함수가 있고 파라미터로 WebDataBindFactory를 넘기는데,

Data를 바인딩하면서 @JsonDeserialize 어노테이션을 확인하고 stdDeserializer를 상속받은 클래스를 적용할 것 같다.

 

기존에 잘못 가정했던 부분을 취소선으로 그었다.

Deserializer는 HTTP 컨버터에서만 작동하고 쿼리 파라미터에서는 변환이 되지 않는다.

HTTP 컨버터에서 objectMapper로 매핑하는 과정중에만 동작하는 것 같다.

Get의 query 파라미터 위해서는 Formatter를 추가해야 한다.

 

String to LocalDateTime의 경우는 @DateTimeFormat로 변환 가능하다.

 

 

구현 후

RequestDto

  public class RequestDto {
    @JsonDeserialize(using = ZonedDateTimeDeserializerStart.class)
    private ZonedDateTime startDate;
 	@JsonDeserialize(using = ZonedDateTimeDeserializerEnd.class)
    private ZonedDateTime endDate;
  }

 

ZoneDateTimeDeserializerStart

import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

import java.io.IOException;
import java.time.ZonedDateTime;

public class ZonedDateTimeDeserializerStart extends StdDeserializer<ZonedDateTime> {

    public ZonedDateTimeDeserializerStart(){
        super(ZonedDateTime.class);
    }

    @Override
    public ZonedDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
        String value = p.readValueAs(String.class);
        return TimeUtils.yyyymmddToZdt(value);
    }
}

 

ZoneDateTimeDeserializerEnd

import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

import java.io.IOException;
import java.time.ZonedDateTime;

public class ZonedDateTimeDeserializerEnd extends StdDeserializer<ZonedDateTime> {

    public ZonedDateTimeDeserializerEnd(){
        super(ZonedDateTime.class);
    }

    @Override
    public ZonedDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
        String value = p.readValueAs(String.class);
        return TimeUtils.yyyymmddToZdtEnd(value);
    }
}

 

장점

- dto안의 날짜필드를 ZoneDateTime으로 통일성 있게 가져갈 수 있다.

- Repository에 있던 TimeUtils를 Deserializer로 빼서 코드가 깔끔해졌다.

//querydsl
//변경 전
task.createDate.between(TimeUtils.yyyymmddToZdt(searchCondition.getStartDate())
	, TimeUtils.yyyymmddToZdtEnd(searchCondition.getEndDate()))
    
//변경 후
task.createDate.between(searchCondition.getStartDate(), searchCondition.getEndDate())

 

단점

- 각 Test 코드에 TimeUtils를 적용해야 해서 조금 지저분해짐 

class BusinessTest{
...
//변경 전
	SearchCondition searchCondition = SearchCondition.builder()
            .startDate("20211207")
            .endDate("20220307")

//변경 후
		SearchCondition searchCondition = SearchCondition.builder()
        .startDate(TimeUtils.yyyymmddToZdt("20211207"))
        .endDate(TimeUtils.yyyymmddToZdt("20220307"))

}

 

쿼리 파라미터 적용

- 스프링에서 제공하는 @DateTimeFormat은 String을 LocalDateTime으로 변환은 해주지만 ZoneDateTime으로는 변환해 주지 못한다.

- 그렇기 때문에 Custom Formatter를 추가해주어야 한다.

 

먼저 Fomatter를 추가해보자.

public class StringToZdtFormatter implements Formatter<ZonedDateTime> {
    @Override
    public ZonedDateTime parse(String text, Locale locale) throws ParseException {
        return TimeUtils.yyyymmddToZdtEnd(text);
    }

    @Override
    public String print(ZonedDateTime object, Locale locale) {
        return TimeUtils.zdtToyyyymmdd(object);
    }
}

WebMvcConfigurer에 Custom Formatter를 추가

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addFormatter(new StringToZdtFormatter());
    }
}

 

이제 내가 원했던 대로 모든 계층에서 날짜를 ZoneDateTime으로 통일할 수 있게 되었다.

 

 

참고

https://stackoverflow.com/questions/46017003/spring-boot-parse-form-data-to-zoneddatetime-dd-mm-yyyy-hhmm 

 

Spring Boot parse form data to ZonedDateTime (dd/MM/yyyy HH:mm)

I have an entity which has java.time.ZonedDateTime property. I want to parse a string submitted from form in dd/MM/yyyy HH:mm format to java.time.ZonedDateTime. I tried @DateTimeFormat(pattern = ...

stackoverflow.com

https://d2.naver.com/helloworld/0473330