ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Jpa java8 date (LocalDateTime) 와 Jackson
    카테고리 없음 2023. 4. 21. 15:26
    제목이 거창하지만 별거 없다. Spring data jpa와 java8에 추가된 LocalDateTime 설정과 그에 맞게 json으로 보낼 Jackson 설정을 알아볼 예정이다. 현재 JPA2.1은 java8의 date(LocalDateTime) 를 지원하지 않는다. 아마도 java8이 릴리즈 되기 전에 JPA2.1이 먼저 나와서 일것이다. java8 이전의 Date API는 엉망진창이다.(물론 내가 잘만드는건 아니지만..) thread-safe 하지도 않고 API가 직관적이지도 않다. 그래서 우리는 Third-party 라이브러리인 Joda-time을 많이 쓰곤했다. 하지만 java8에 추가된 date는 Joda-time의 저자와 오라클 주도하에 JSR310 표준에 맞춰 개발 하였다. 아무튼 이걸 말하고자 하는건 아닌데.. 그럼 한번 살펴보자. 일단 우리는 java8의 추가된 LocalDateTime과 DB와의 연결고리를 만들어야 한다. 직접적으로 컨버터를 만들어도 되지만 Spring에서 만든 컨버터가 있다. 필자는 거의 최신이라 버전이 낮으면 없을 수도 있으니 여기를 참고해서 만들자. 아마도 직접적으로 만들면 딱히 설정할 필요가 없는데 만약 Spring에서 만든 컨버터를 사용하려면 약간의 설정이 필요하다.
    @SpringBootApplication
    @EntityScan(basePackageClasses = {Application.class, Jsr310JpaConverters.class} )
    public class Application {
    
      public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
      }
    }
    
    EntityScan에 위와 같이 넣어주면 끝난다. 그럼 일단 DB와는 컨버터가 되었다. 한번 데이터베이스에 잘 들어가나 확인해보자. 아마도 문제 없이 잘 들어갈 것이다. 왜냐면 필자가 잘 되었기 때문이다. ㅎㅎ 컨버터는 되었으니 View에 뿌려줄 json 데이터가 문제다. 처음엔 그냥 있는 그대로 출력해보자. 출력한 결과는 다음과 같다.
    {  
      "id":1,
      "name":"tt",
      "password":"111",
      "email":"123",
      "localDateTime":{  
        "hour":22,
        "minute":47,
        "second":34,
        "nano":158000000,
        "dayOfYear":194,
        "dayOfWeek":"TUESDAY",
        "month":"JULY",
        "dayOfMonth":12,
        "year":2016,
        "monthValue":7,
        "chronology":{  
          "id":"ISO",
          "calendarType":"iso8601"
        }
      }
    }
    
    조금 그렇다. 물론 이렇게 써도 되겠지만(?) 필자는 컨버터가 하고 싶었다. 컨버터 방법역시 간단하다. 일단 메이븐에 아래와 같이 추가하자.
    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jsr310</artifactId>
    </dependency>
    
    필자는 Spring Boot를 쓰는데 저것 역시 Version을 Boot에서 관리해준다. 그래서 명시 하지 않았다. Boot를 쓰지 않는다면 버전을 명시해야 한다. 버전은 인터넷에서.. 현재 필자의 버전은 2.8.0이다. Spring boot의 경우에는 일단 메이븐에만 추가해도 내용이 바뀌어 출력된다. Boot가 아닌경우에는..잘... 따로 설정을 해야 하나?.. 메이븐을 추가하고 다시 테스트를 해보면 아래와 같이 출력된다.
    {
      "id": 1,
      "name": "tt",
      "password": "111",
      "email": "123",
      "localDateTime": [
        2016,
        7,
        12,
        22,
        47,
        34,
        158000000
      ]
    }
    
    아까보다는 나아졌다고 할 수 있지만 그래도 영 시원찮다. 좀 더 깔끔한 방법으로 출력하고 싶다. 그럼 아래와 같이 조금 설정을 해줘야한다.
    @Bean
    public ObjectMapper objectMapper() {
      return Jackson2ObjectMapperBuilder
        .json()
        .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
        .modules(new JavaTimeModule())
        .build();
    }
    
    Spring boot를 쓰면 위와 같이 하지 않고 properties나 yaml 파일에 아래와 같이 넣자.
    spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
    
    그럼 아래와 같이 ISO 8601 포멧으로 바뀐다. 요즘 Rest API에 많이 쓴다고 한다.
    {
      "id": 1,
      "name": "tt",
      "password": "111",
      "email": "123",
      "localDateTime": "2016-07-12T22:47:34.158"
    }
    
    이제 깔끔하게 되었다. LocalDateTime도 알아봤으니 나머지 Date(LocalDate, LocalTime) 등들도 어떻게 출력 되는지 한번 살펴 보자.

    private ZonedDateTime zonedDateTime; private LocalDate localDate; private LocalTime localTime; private OffsetDateTime offsetDateTime;
    이 외에도 좀 더 존재하는거 같지만 위에 4개만 더 테스트 해보자.

    메이븐에 jackson에 추가 히지 않았을 경우 (아무 설정 하지 않았을경우)

    {
      zonedDateTime: {
        offset: {
          totalSeconds: 32400,
          id: "+09:00",
          rules: {
            fixedOffset: true,
            transitions: [
    
            ],
            transitionRules: [
    
            ]
          }
        },
        zone: {
          id: "Asia/Seoul",
          rules: {
            fixedOffset: false
          }
         //.. zonedDateTime long
         //..
         //..
        }
      },
      localDate: {
        year: 2016,
        month: "JULY",
        dayOfYear: 194,
        dayOfWeek: "TUESDAY",
        leapYear: true,
        dayOfMonth: 12,
        monthValue: 7,
        chronology: {
          id: "ISO",
          calendarType: "iso8601"
        },
        era: "CE"
      },
      localTime: {
        hour: 23,
        minute: 35,
        second: 30,
        nano: 847000000
      },
      offsetDateTime: {
        offset: {
          totalSeconds: 32400,
          id: "+09:00",
          rules: {
            fixedOffset: true,
            transitions: [
    
            ],
            transitionRules: [
    
            ]
          }
        },
        dayOfYear: 194,
        dayOfWeek: "TUESDAY",
        month: "JULY",
        dayOfMonth: 12,
        year: 2016,
        monthValue: 7,
        hour: 23,
        minute: 35,
        second: 30,
        nano: 847000000
      }
    }
    
    zonedDateTime 은 너무 길어서 일부만 가져와서 보여줬다.
    
    

    spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS 설정을 제거 했을 경우

    {
      id: 1,
      name: "tt",
      password: "111",
      email: "123",
      localDateTime: [
        2016,
        7,
        12,
        23,
        35,
        30,
        847000000
      ],
      zonedDateTime: 1468334130.847,
      localDate: [
        2016,
        7,
        12
      ],
      localTime: [
        23,
        35,
        30,
        847000000
      ],
      offsetDateTime: 1468334130.847
    }
    

    spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS 설정을 사용 했을 경우

    {
      id: 1,
      name: "tt",
      password: "111",
      email: "123",
      localDateTime: "2016-07-12T23:35:30.847",
      zonedDateTime: "2016-07-12T23:35:30.847+09:00",
      localDate: "2016-07-12",
      localTime: "23:35:30.847",
      offsetDateTime: "2016-07-12T23:35:30.847+09:00"
    }
    
    참고로 아무 설정 하지 않았을 경우 zonedDateTime, offsetDateTime 은 DB에 BLOB로 매핑된다. 원래는 금방 포스팅했는데 컴터가 맛탱이가서 좀 오래 걸렸다. 컴터를 바꿀때가 된듯하다...

    댓글

Designed by Tistory.