데이터베이스에서 날짜데이터를 로드할 때 날짜형식의 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
결론만 이야기하자면
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=장만월®istDateTime=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
https://log-laboratory.tistory.com/16
'공부 > 프로그래밍' 카테고리의 다른 글
[docker] 로그 logrotate 로 관리하기(주기적으로 삭제하기) (0) | 2019.12.18 |
---|---|
[spring] jpa와 mybatis 동시 사용시 transactinoManager (multi) 설정하기(xml) 및 내부 살펴보기 (2) | 2019.12.17 |
[spring] 데이터 변수명 변경하여 전달하기 (@JsonProperty 사용) (0) | 2019.12.14 |
[spring, jpa] Could not build ClassFile 에러 (0) | 2019.11.27 |
[docker] 공유된 volume 에 접근이 안될 때 (0) | 2019.11.02 |
댓글