티스토리 뷰

웹 애플리케이션 개발을 하다보면 피해갈 수 없는 문제 중 하나가 바로 타임존(Timezone) 이다.

 

데이터베이스를 통해 받아온 날짜형식의 데이터를 원하는 Timezone에 맞춰서 보여줘야 하는데 제대로 동작이 안되는 경우를 한번쯤은 겪어봤을 것이다. 지금부터 필자와 함께 어떻게 설정을 해줘야 하는지 코드를 통해 알아보자.

환경

  • Spring Boot 2.6.8
  • Java 17 (Timezone = Asia/Seoul)
  • MySQL 8.0.27 (Timezone = UTC)
  • MySQL Connector/J 8.0.29

예제코드

현재 Timezone이 UTC인 MySQL 서버를 띄웠고 Java 애플리케이션에서 JDBC API를 통해 Connection을 맺은 후 현재시간을 조회해오는 SQL을 작성 후 결과값을 받아온 코드다. Java 애플리케이션의 Timezone은 Asia/Seoul이다.



위 사진에서는 serverTimezone 속성을 명시하지 않았는데 이번에는 serverTimezone=UTC 속성을 추가해보자.



이번에는 Timezone이 UTC가 아닌 Asia/Seoul로 변환된 걸 확인할 수 있다. 이제 JDBC Connection부터 데이터가 반환되는걸 확인해보자.

 

serverTimezone 설정

com.mysql.cj.protocol.a.NativeProtocol#configureTimeZone에서 다음과 같은 동작이 이뤄진다.

  • JDBC url 파라미터에 serverTimezone의 값을 조회해온다.
  • 값이 null이거나 'LOCAL' 일 경우 현재 어플리케이션의 Timezone을 serverTimezone으로 지정한다.
  • 'SERVER'일 경우 별다른 로직을 처리하지 않는다.
  • null, 'LOCAL', 'SERVER'가 아니면 해당 값으로 Timezone 인스턴스를 생성하여 그 Timezone을 sessionTimezone으로 세팅한다.

 

JDBC url 파라미터에 serverTimezone이 명시되있지 않을 경우

  • 현재 어플리케이션의 Timezone으로 Connection이 맺어진다.

JDBC url 파라미터에 serverTimezone이 명시되있을 경우

  • serverTimezone의 값으로 Timezone 인스턴스를 생성하여 생성된 Timezone으로 Connection이 맺어진다.

데이터 변환

com.mysql.cj.jdbc.result.ResultSetImpl생성자에서 SqlTimestampValueFactory 인스턴스를 생성하는데 생성자 인자에 Timezone 타입의 DefaultTimezoneSessionTimezone 두개가 들어간다.



com.mysql.cj.protocol.ServerSession인터페이스를 확인해보면 DefaultTimezone은 JVM에 설정된 timezone 즉, Java 애플리케이션 서버의 timezone을 가져오고 SessionTimezone은 serverTimezone에 설정된 Timezone을 반환한다.



그리고 com.mysql.cj.result.AbstractDateTimeValueFactory#createFromDatetime메서드가 호출되는데 이때 인자로 전달되는 값은 데이터베이스에서 조회해온 날짜데이터가 전달된다.

 

SELECT NOW()의 반환값은 년월일 시분초 포맷이기 때문에 createFormDateTime 메서드가 호출된다. 해당값을 확인해보면 2022년 06월 05일 12시 46분 59초이다.



마지막으로 com.mysql.cj.result.SqlTimestampValueFactory#localCreateFromTimestamp메서드에서 UTC Timezone의 날짜데이터를 Asia/Seoul로 변경해서 리턴한다.

 

Calender.getInstance() 호출 시 this.pset.getBooleanProperty(PropertyKey.preserveInstants).getValue() 값에따라 sessionTimezone을 사용할껀지 defaultTimezone을 사용할껀지 설정할 수 있는데 기본값은 true이므로 sessionTimezone으로 Calender 인스턴스가 생성된다.

 

그리고 인자로 전달된 InternalTimestamp 타입의 년월일시분초 데이터를 Calender 인스턴스에 세팅해준 뒤 TimeStamp 인스턴스를 생성하여 리턴하는데 이때 리턴되는 데이터는 UTC -> Asia/Seoul로 변환된 데이터이다.



Calender.getIntance() 호출 시 내부적으로 Builder를 통해 생성되는데 Instant 필드에 1970-01-01부터 현재 Java 애플리케이션 시간(Asia/Seoul)사이의 밀리초를 반환한다.



그리고 Calender 인스턴스를 build() 할 때 setTimeInMillis 메서드로 Builder로 생성할 때 넣어준 값이 전달된다.



ResultSetImpl 생성자를 보면 Java의 각 변수타입에 맞는 ValueFactory 클래스들을 제공해주는데 날짜타입에 관련해서는 default라는 접두사가 붙는다. 아마도 날짜 데이터를 조회해올 때 Timezone을 default에 맞게끔 변경해주기 위해 붙혀진 것 같다.

요약

  • serverTimezone을 UTC로 지정할 경우 (DB Timezone = UTC)
    • DB에서 UTC Timezone으로 저장된 날짜데이터를 조회
    • UTC Timezone으로 Calender 인스턴스를 생성
    • Calender 인스턴스의 timeInMills의 값을 Java 애플리케이션의 Timezone에 맞게끔 세팅 후 Timestamp 인스턴스 반환
    • UTC -> Asia/Seoul로 변환
  • serverTimezone을 Asia/Seoul로 지정할 경우 (DB Timezone = UTC)
    • DB에서 Asia/Seoul Timezone으로 저장된 날짜데이터를 조회
    • Asia/Seoul Timezone으로 Calender 인스턴스를 생성
    • Calender 인스턴스의 timeInMills의 값을 Java 애플리케이션의 Timezone에 맞게끔 세팅 후 Timestamp 인스턴스 반환. 이때 현재 Java 애플리케이션의 timezone과 JDBC serverTimezone이 같아서 timeInMills의 값은 동일
    • Asia/Seoul -> Asia/Seoul로 변환 -> 변화 없음...
  • DB Timezone이 UTC인 경우 클라이언트로부터 날짜데이터를 Asia/Seoul Timezone으로 변경해서 응답하는법
    • serverTimezone=UTC 추가
728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함