[JPA (Hibernate)] ZonedDateTime 사용시 UTC 값으로 저장되는 이슈
문제
spring boot 3.2.1 버전을 사용하고 있으며, 이에 따라 org.hibernate.orm:hibernate-core:6.4.1.FINAL 버전을 사용하고 있었다. JVM 및 MySQL TimeZone 설정이 Asia/Seoul 값으로 되어 있음에도 UTC 값으로 저장되는 이슈였다.
Hibernate 로그 살펴보기
application.yml 파일에 아래 옵션들을 추가하고, hibernate sql 로그를 살펴보기로 했다.
logging:
level:
org:
springframework:
orm.jpa: debug
hibernate:
SQL: debug
orm.jdbc.bind: trace # Spring Boot 3.x 이상 부터는 'orm.jdbc.bind' 를 사용해야 바인딩 된 쿼리 파라미터를 볼 수 있다.
TIMESTAMP_UTC?
로그를 살펴보던 도중 아래 이미지처럼 (7:TIMESTAMP_UTC) 로그를 확인했다.
Hibernate time_zone을 변경했으나, 실패
hibernate.jdbc.time_zone 설정을 Asia/Seoul 값으로 지정했으나, 역시 실패했다.
spring:
jpa:
properties:
hibernate:
jdbc.time_zone: Asia/Seoul
우연히 찾게 된 hibernate.timezone.default_storage 옵션
Spring Boot 3.1 버전부터는 Hibernate 6.2 버전을 사용하는데, 가장 큰 변경점이 Hibernate에서 시간대(time zone) 정보를 다루는 방식이 달라졌다고 한다. Hibernate 6.2 버전부터 시간대 관련 정보는 hibernate.timezone.default_storage 옵션으로 다룬다는 점을 확인했다. 이 옵션은 내부적으로 TimeZoneStorageType 값과 매핑되고, 이를 통해 Hibernate가 시간 관련 정보를 실제 DB 테이블에 저장하고 데이터를 읽을 때, 시간 데이터를 어떤 식으로 보간하는지 조정할 수 있다고 했다.
만일 별다른 옵션을 주지 않았다면 hibernate.timezone.default_storage=DEFAULT 값으로 설정되고 다음과 같이 작동한다.
- DB 레벨에서 시간대 정보를 관리할 수 있도록 지원하는 경우, 절대 시간과 시간대 정보가 DB 테이블에 함께 저장된다.
- TimeZoneStorageType.NATIVE
- DB 레벨에서 시간대 정보를 관리하지 않는 경우, UTC 시간대로 보간해서 시간 정보가 저장된다.
- TimeZoneStorageType.NORMALIZE_UTC
더 자세한 정보는 https://techblog.lycorp.co.jp/ko/how-to-migrate-to-spring-boot-3 링크에서 확인하면 된다.
hibernate.timezone.default_storage=NORMALIZE 적용
NORMALIZE 옵션을 적용하기로 했는데, 이유는 다음과 같다.
- 기존에 저장된 데이터와의 정합성을 보장하고, 애플리케이션 로직의 하위 호환성을 고려해서 NORMALIZE 적용
- NORMALIZE 옵션은 시간대 정보를 6.2 이전 버전과 동일한 방식으로 설정하는 옵션으로, 데이터소스 연결 정보에 설정한 시간대 또는 JVM 기본 시간대 정보를 사용하기 때문에 이전과 동일한 방식으로 DB 테이블에 데이터를 저장할 수 있다.
@TimeZoneStorage & @TimeZoneColumn Annotations 사용해서 컬럼에 각각 매핑할 수도 있다.
결과
아래와 같은 결과를 얻을 수 있었고, DB 테이블에도 정상적인 ZoneDateTime 값이 저장되었다.
참고 문서
https://techblog.lycorp.co.jp/ko/how-to-migrate-to-spring-boot-3
https://www.woolha.com/tutorials/hibernate-using-timezonestorage-timezonecolumn-annotations