주요글: 도커 시작하기
반응형

스프링 부트 날짜 타입을 JSON으로 응답할 때 별도 설정을 하지 않으면 부트 버전에 따라 응답 형식이 다르다. 먼저 간다한 테스트를 위해 다음과 같이 세 개의 날짜 형식을 갖는 Now 클래스를 사용하자.


import java.time.LocalDateTime;

import java.time.OffsetDateTime;

import java.util.Date;


public class Now {

    private LocalDateTime localTime;

    private OffsetDateTime offsetTime;

    private Date date;


    public Now() {

        localTime = LocalDateTime.now();

        offsetTime = OffsetDateTime.now();

        date = new Date();

    }


    ...getter 생략

}



Now를 생성해서 JSON으로 응답하는 컨트롤러를 다음과 같이 작성했다.


@RestController

public class SampleController {

    @GetMapping("/now")

    public Now time() {

        return new Now();

    }

}


이 글에서 사용한 코드는 https://github.com/madvirus/boot-jackson 리포지토리에서 참고할 수 있다.


부트 2.0에서 기본 JSON 시간 타입 포맷팅


부트 2.0으로 테스트하면 응답 결과가 다음과 같다. 각 타입을 ISO-8601 형식으로 출력하고 있다.


{

    "localTime": "2018-03-01T17:03:50.445428",

    "offsetTime": "2018-03-01T17:03:50.445428+09:00",

    "date": "2018-03-01T08:03:50.445+0000"

}


시간대 정보가 있는 OffsetDateTime 타입은 "+09:00"이 뒤에 붙어 있다. 반면에 Date 타입은 UTC 기준 시간을 사용했다. 또 다른 차이는 OffsetDateTime과 Date의 시간대 표시가 다르다는 것이다. OffsetDateTime 타입은 "+09:00"와 같이 콜론이 포함되어 있고 Date 타입은 "+0000"과 같이 콜론이 없다. LocalDateTime의 경우 오프셋 정보가 없으므로 시간대 부분이 없다.


부트 1.5에서 기본 JSON 시간 타입 포맷팅


부트 1.5에서 테스하면 응답 결과가 다음과 같다. 아주 난리다!


{

"localTime": {

"month": "MARCH",

"dayOfWeek": "THURSDAY",

"dayOfYear": 67,

"nano": 644321400,

"year": 2018,

"monthValue": 3,

"dayOfMonth": 1,

"hour": 17,

"minute": 28,

"second": 2,

"chronology": {

"id": "ISO",

"calendarType": "iso8601"

}

},

"offsetTime": {

"offset": {

"totalSeconds": 32400,

"id": "+09:00",

"rules": {

"fixedOffset": true,

"transitions": [],

"transitionRules": []

}

},

"month": "MARCH",

"dayOfWeek": "THURSDAY",

"dayOfYear": 67,

"nano": 644321400,

"year": 2018,

"monthValue": 3,

"dayOfMonth": 1,

"hour": 17,

"minute": 28,

"second": 2

},

"date": 1519894518644

}


부트 1.5에서 jackson-datatype-jsr310 모듈 추가


부트 1.5에 아래와 같이 jackson-datatype-jsr310 모듈을 추가해보자. 이 모듈은 LocalDateTime이나 OffsetDateTime과 같이 자바 8의 시간 타입을 지원하는 모듈이다.


<dependency>

    <groupId>com.fasterxml.jackson.datatype</groupId>

    <artifactId>jackson-datatype-jsr310</artifactId>

</dependency>


이 모듈을 추가한 뒤에 JSON 생성 결과를 보면 다음과 같다.


{

"localTime": [

2018,

3,

8,

21,

16,

30,

166225000

],

"offsetTime": 1520511390.166383,

"date": 1520511390166

}



부트 1.5 Date 포맷: WRITE_DATES_AS_TIMESTAMPS 비활성, StdFormat 사용


부트 1.5에서 아래 프로퍼티를 application.properties 파일에 추가하면 Date 타입을 ISO-8601 포맷을 사용해서 변환한다.


spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false


다음 프로퍼티를 설정해도 결과가 같다.


spring.jackson.date-format=com.fasterxml.jackson.databind.util.StdDateFormat


실제 출력 결과는 다음과 같다. UTC 기준으로 출력하고 있다.


"date": "2018-03-08T08:51:53.972+0000"



부트 1.5, 2.0: Jackson2ObjectMapperBuilderCustomizer로 포맷팅 설정


부트는 Jackson2ObjectMapperBuilderCustomizer 인터페이스를 제공하는데 이 인터페이스를 구현한 클래스를 빈으로 등록하면 변환 포맷을 설정할 수 있다. 다음 코드는 사용 예이다.


import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;

import com.fasterxml.jackson.datatype.jsr310.ser.OffsetDateTimeSerializer;

import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;

import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;


import java.time.LocalDateTime;

import java.time.OffsetDateTime;

import java.time.format.DateTimeFormatter;


@SpringBootApplication

public class Boot15JacksonApplication implements Jackson2ObjectMapperBuilderCustomizer {


    public static void main(String[] args) {

        SpringApplication.run(Boot15JacksonApplication.class, args);

    }


    // Jackson2ObjectMapperBuilderCustomizer 인터페이스 메서드

    @Override

    public void customize(Jackson2ObjectMapperBuilder builder) {

        // LocalDateTime은 오프셋 정보가 없으므로 패턴에 시간대에 해당하는 Z가 없다.

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");

        LocalDateTimeSerializer localSerializer = new LocalDateTimeSerializer(formatter);


        DateTimeFormatter formatter2 = 

                DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");

        CustomOffsetDateTimeSerializer offsetSerializer = 

                new CustomOffsetDateTimeSerializer(formatter2);


        builder

                .simpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")

                .serializerByType(LocalDateTime.class, localSerializer)

                .serializerByType(OffsetDateTime.class, offsetSerializer)

        ;

    }


    public class CustomOffsetDateTimeSerializer extends OffsetDateTimeSerializer {

        public CustomOffsetDateTimeSerializer(DateTimeFormatter formatter) {

            super(OffsetDateTimeSerializer.INSTANCE, false, formatter);

        }

    }


}


Jackson2ObjectMapperBuilder의 다음 메서드를 이용해서 시간 타입 변환 포맷을 설정했다. 
  • simpleDateFormat: Date 타입을 위한 변환 포맷 설정
  • serializerByType: 타입을 위한 Jackson의 Serializer 설정
serializerByType 메서드를 이용해서 LocalDateTime과 OffsetDateTime을 위한 Serializer를 설정했다. LocalDateTime을 위한 Serializer로는 Jackson이 제공하는 LocalDateTimeSerializer 클래스를 사용했다. 

OffsetDateTime은 약간 수고가 더 필요하다. Jackson이 제공하는 OffsetDateTimeSerializer를 상속해서 구현한 Serializer를 사용했다. 

위 설정을 추가한 뒤 결과는 다음과 같다.

{
"localTime": "2018-03-08T21:26:46",
"offsetTime": "2018-03-08T21:26:46+0900",
"date": "2018-03-08T21:26:46+0900"
}




  1. 하짜증 2021.08.19 17:29

    저의 책 속의 선생님...

    이 글이 2018년도 글인데, 현 시점의 스프링부트 버전에서 이것보다 더욱 좋아진 방법이 있을까요?

    • madvirus 2021.09.02 09:45 신고

      요즘 저는 각 필드에 타입 포맷을 직접 정의하고 있어요.

  2. DEPENDENCY GOD 2022.06.25 15:38

    springboot 2+ mybatis 환경에서 쿼리에서 resultType을 Vo로 받고 mapper Interface 실행 방식으로 조회시 시간이 한국시간으로 조회되는데

    추가로 요구사항이 조회 방식을 sqlSessionTemplate를 사용하고 쿼리 resultType도 Map객체로 받아서 Vo를 사용하지 않는 방식으로 조회 중인데
    이 방식으로 조회시에는 오라클 디비의 Date 시간과 다르게 화면에 내려가면 utc 시간으로 받아 지네요. controller 리턴 전까지도 디비의 한국시간인데
    화면에만 가면 utc 시간으로 9시간 느린 시간으로 나옵니다. 이럴 경우 어떤 설정을 살펴야 해결 될까요?


    • madvirus 2022.06.27 21:35 신고

      컨트롤러에서 리턴한 결과가 프론트(화면)에 JSON 형식으로 전달되나요? 아니면 JSP와 같은 뷰 기술을 이용해서 출력하나요?

      그리고 JSON으로 응답을 줄 경우 sqlSessionTemplate으로 읽어온 Map에 담김 시간 데이터는 어떤 타입으로 저장되어 있나요?

+ Recent posts