본문 바로가기
공부/프로그래밍

[spring] LocalDateTime 주고받기(Response, Request)

by demonic_ 2019. 12. 15.
반응형

 

데이터베이스에서 날짜데이터를 로드할 때 날짜형식의 String 형태으로 구성하여 전달하는 경우가 있다.

(2019-12-14 07:00:00 과 같은 방식)

 

그중 마이바티스를 사용하는 경우 query를 작성할때 날짜를 format변경하여 String으로 조회하게 하여 리턴하는 경우 문제가 발생했는데, 개발자마다 누군가는 포멧팅을 하고 누군가는 포멧팅을 쓰지 않았다. 그러다보니 클라이언트가 날짜를 처리할때 통일되게 처리하지 못해 문제가 발생했었다.

 

그래서 이번에 LocalDateTime 형식으로 변경하면서 날짜포멧을 변경하는 작업을 정리하고자 한다.

 

그전에)

기존에 운영되고 있는 시스템은 spring 5.x 버전을 사용하고 있지만, 여기서는 예제 편의상 springboot 로 진행했다.

Springboot 2.x 이하 버전이나 boot를 사용하지 않는다면 다음 패키지를 추가한다.(버전은 변경될 수 있다)

<dependency>
	<groupId>com.fasterxml.jackson.datatype</groupId>
	<artifactId>jackson-datatype-jsr310</artifactId>
	<version>2.10.1</version>
</dependency>

https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jsr310

 

Maven Repository: com.fasterxml.jackson.datatype » jackson-datatype-jsr310

Add-on module to support JSR-310 (Java 8 Date & Time API) data types. VersionRepositoryUsagesDate2.10.x2.10.1Central430Nov, 20192.10.0Central84Sep, 20192.10.0.pr3Central11Sep, 20192.10.0.pr2Central357Aug, 20192.10.0.pr1Central18Jul, 20192.9.x2.9.10Central7

mvnrepository.com

 

결론만 이야기하자면

Responst (서버 -> 클라이언트) 로 전달할 때에는 @JsonFormat 을 사용,

Request(클라이언트 -> 서버)로 전달할 때는 @DateTimeFormat 을 사용한다.

 

 

 

# (서버->클라이언트 전달) LocalDateTime 에 format 지정하여 전달하기

 

LocalDateTime 형식으로 클라이언트에 전달하려면 @JsonFormat 을 이용하면 된다.

 

샘플 DTO

@JsonFormat 을 사용하지 않은경우

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;

import java.time.LocalDate;
import java.time.LocalDateTime;

@Getter @Setter
public class HelloDTO2 {

    private Long customerId;

    private String customerName;

    private String cellPhone;

    private LocalDate birthDay;

    private LocalDateTime registDate;

    public HelloDTO2(Long customerId, String customerName, String cellPhone, LocalDate birthDay, LocalDateTime registDate) {
        this.customerId = customerId;
        this.customerName = customerName;
        this.cellPhone = cellPhone;
        this.birthDay = birthDay;
        this.registDate = registDate;
    }
}

@JsonFormat 을 사용하는 경우

@Getter @Setter
public class HelloDTO3 {

    private Long customerId;

    private String customerName;

    private String cellPhone;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "Asia/Seoul")
    private LocalDate birthDay;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss", timezone = "Asia/Seoul")
    private LocalDateTime registDate;

    public HelloDTO3(Long customerId, String customerName, String cellPhone, LocalDate birthDay, LocalDateTime registDate) {
        this.customerId = customerId;
        this.customerName = customerName;
        this.cellPhone = cellPhone;
        this.birthDay = birthDay;
        this.registDate = registDate;
    }
}

컨트롤러

@RestController
public class HelloController {
    @GetMapping("/hello2")
    public HelloDTO2 hello2() {
        HelloDTO2 hello2 = new HelloDTO2(2L
                , "백종원"
                , "01012345678"
                , LocalDate.now()
                , LocalDateTime.now());

        return hello2;
    }

    @GetMapping("/hello3")
    public HelloDTO3 hello3() {
        HelloDTO3 hello3 = new HelloDTO3(3L
                , "정혜영"
                , "01012345678"
                , LocalDate.now()
                , LocalDateTime.now());

        return hello3;
    }
}

테스트

@WebMvcTest
public class HelloControllerTest {

    @Autowired
    WebApplicationContext context;

    private MockMvc mockMvc;

    @BeforeEach
    void init() {
        mockMvc = MockMvcBuilders.webAppContextSetup(context)
                .addFilter(new CharacterEncodingFilter("UTF-8", true))
                .build();
    }

    @Test
    public void hello2() throws Exception {
        //given
        String url = "/hello2";

        //when
        ResultActions resultActions = this.mockMvc.perform(get(url));

        //then
        MvcResult mvcResult = resultActions
                .andExpect(status().isOk())
                .andReturn();


        System.out.println(mvcResult.getResponse().getContentAsString());
    }

    @Test
    public void hello3() throws Exception {
        //given
        String url = "/hello3";

        //when
        ResultActions resultActions = this.mockMvc.perform(get(url));

        //then
        MvcResult mvcResult = resultActions
                .andExpect(status().isOk())
                .andReturn();


        System.out.println(mvcResult.getResponse().getContentAsString());
    }
}

@JsonFormat 사용하지 않은 결과

{"customerId":2,"customerName":"백종원","cellPhone":"01012345678","birthDay":"2019-12-14"
,"registDateTime":"2019-12-14T07:51:01.242012"}

@JsonFormat 사용결과

{"customerId":3,"customerName":"정혜영","cellPhone":"01012345678","birthDay":"2019-12-14"
,"registDateTime":"2019-12-14T07:51:01"}

끝에 ms 가 붙은거 외엔 동일하다.

 

 

 

# (클라이언트->서버 전달) 날짜형 String을 LocalDateTime 으로 받기

 

이번에는 GET방식으로 데이터형식을 전달받으면 convert 해보자

 

DTO

@Getter @Setter
@NoArgsConstructor
public class HelloDTO4 {

    private Long customerId;

    private String customerName;

    private LocalDateTime registDateTime;

    public HelloDTO4(Long customerId, String customerName, LocalDateTime registDateTime) {
        this.customerId = customerId;
        this.customerName = customerName;
        this.registDateTime = registDateTime;
    }
}

컨트롤러

@RestController
public class HelloController {

    @GetMapping("/hello4")
    public HelloDTO4 hello4(HelloDTO4 helloDTO4) {
        System.out.println(helloDTO4);
        return helloDTO4;
    }
}

테스트 작성

@WebMvcTest
public class HelloControllerTest {

    @Autowired
    WebApplicationContext context;

    private MockMvc mockMvc;

    @BeforeEach
    void init() {
        mockMvc = MockMvcBuilders.webAppContextSetup(context)
                .addFilter(new CharacterEncodingFilter("UTF-8", true))
                .build();
    }

    @Test
    public void hello4() throws Exception {
        //given
        String url = "/hello4?customerId=10&customerName=장만월&registDateTime=2019-12-14T06:34:20";

        //when
        ResultActions resultActions = this.mockMvc.perform(get(url));

        //then
        MvcResult mvcResult = resultActions
                .andExpect(status().isOk())
                .andReturn();


        System.out.println(mvcResult.getResponse().getContentAsString());
    }
}

실행하면 직렬화에 실패한다

2019-12-14 07:41:03.906  INFO 33989 --- [           main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2019-12-14 07:41:03.906  INFO 33989 --- [           main] o.s.t.web.servlet.TestDispatcherServlet  : Initializing Servlet ''
2019-12-14 07:41:03.908  INFO 33989 --- [           main] o.s.t.web.servlet.TestDispatcherServlet  : Completed initialization in 2 ms
2019-12-14 07:41:03.959  WARN 33989 --- [           main] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'helloDTO4' on field 'registDateTime': rejected value [2019-12-14T06:34:20]; codes [typeMismatch.helloDTO4.registDateTime,typeMismatch.registDateTime,typeMismatch.java.time.LocalDateTime,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [helloDTO4.registDateTime,registDateTime]; arguments []; default message [registDateTime]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.time.LocalDateTime' for property 'registDateTime'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@org.springframework.format.annotation.DateTimeFormat java.time.LocalDateTime] for value '2019-12-14T06:34:20'; nested exception is java.lang.IllegalArgumentException: Unknown pattern letter: T]]


java.lang.AssertionError: Status expected:<200> but was:<400>
Expected :200
Actual   :400
<Click to see difference>


	at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59)
	at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:98)
	at org.springframework.test.web.servlet.result.StatusResultMatchers.lambda$matcher$9(StatusResultMatchers.java:627)
	at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:196)
	at com.example.demo.controller.HelloControllerTest.hello4(HelloControllerTest.java:97)
	...

 

메세지내용을 보면 registDateTime 의 값이 rejected 당했다고 뜬다.

좀더 상세한 내용으로는 convert에 실패한 것이다.

Failed to convert from type [java.lang.String] to type [@org.springframework.format.annotation.DateTimeFormat java.time.LocalDateTime] for value '2019-12-14T06:34:20';

 

 

이 문제를 해결하기 위해서는 @DateTimeFormat 을 사용하면 된다.

 

DTO에서 LocalDateTime을 받는 registDateTime 에 다음의 어노테이션을 붙인다

@Getter @Setter
@NoArgsConstructor
public class HelloDTO4 {

    private Long customerId;

    private String customerName;

    @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
    private LocalDateTime registDateTime;

    public HelloDTO4(Long customerId, String customerName, LocalDateTime registDateTime) {
        this.customerId = customerId;
        this.customerName = customerName;
        this.registDateTime = registDateTime;
    }
}

테스트 재실행

{"customerId":10,"customerName":"장만월","registDateTime":"2019-12-14T06:34:20"}

끝.

 

 

 

참고:

https://jojoldu.tistory.com/361

 

SpringBoot에서 날짜 타입 JSON 변환에 대한 오해 풀기

안녕하세요? 이번 시간엔 Spring과 JSON에 대해 정리해보려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (공부한 내용을 정리하는 Github와 세미나+책 후기를 정리..

jojoldu.tistory.com

https://log-laboratory.tistory.com/16

 

[Spring] @DateTimeFormat - TimeZone

@DateTimeFormat 활용하면, 다양한 형식으로 customizing하여 데이터를 입력 받아올 수 있다. 사용 예시 public class testDate{ @DateTimeFormat(pattern = "yyyy-MM-dd") private DateTime testDate1; @DateTime..

log-laboratory.tistory.com

 

반응형

댓글