[SpringBoot] UTC 시간 처리, 코드가 아니라 설정으로 정리하기 (Hibernate 6 & Jackson)

2026. 1. 21. 22:25·Backend/Trouble Shooting

1. 설정은 맞았지만, 최선은 아니었다

지난 포스팅(https://wlals916.tistory.com/6)에서는
DB와 서버의 타임존을 UTC로 통일하는 과정을 정리했다.
당시에는 “이 정도면 충분히 잘 맞췄다”고 생각했다.

하지만 코드 리뷰를 받으면서 생각이 바뀌었다.
분명 동작은 하고 있었지만,

  • 같은 설정이 여러 곳에서 반복되고 있었고
  • Hibernate 6에서 이미 제공하는 더 현대적인 해결책을 활용하지 못하고 있었다.

이번 글에서는
타임존을 안전하게 다루기 위해 내가 놓치고 있던 지점들과
이를 정리하면서 얻은 설계적 통찰을 공유한다.


2. 문제 제기: “왜 이 코드는 반복되는가?”

다음은 실제 DTO에 존재하던 코드 일부다.

@Schema(description = "일기 작성 날짜 및 시간 (UTC 기준, ISO-8601 형식). 프론트엔드에서는 사용자의 로컬 타임존으로 변환하여 표시해야 합니다.", example = "2024-05-20T10:00:00.000Z")
private final OffsetDateTime dateTime;

@Schema(description = "일기 생성 날짜 및 시간 (UTC 기준, ISO-8601 형식). 프론트엔드에서는 사용자의 로컬 타임존으로 변환하여 표시해야 합니다.", example = "2024-05-20T10:00:00.000Z")
private final OffsetDateTime createdAt;

여기에 더해, 실제 코드에서는 모든 시간 필드마다
@JsonFormat까지 함께 붙어 있었다.

통찰

  • 모든 시간 필드에 동일한 설명과 포맷을 반복해서 적고 있다.
  • 만약 이런 필드가 100개라면, 이 반복은 과연 합리적인가?

핵심 질문

  • DB 저장 시 타임존을 더 안전하게 강제할 방법은 없을까?
  • AttributeConverter가 정말 최선의 선택일까?

이 질문에서부터 개선이 시작됐다.


3. 본론: 비판적 검토와 해결책

① Hibernate 6의 현대적인 처리: NORMALIZE_UTC

리뷰에서 가장 먼저 지적받은 부분은
OffsetDateTime과 MySQL 타입 간의 미묘한 위험성이었다.

MySQL의 DATETIME, TIMESTAMP 타입은
offset 정보를 저장하지 않는다.
이로 인해 다음과 같은 문제가 발생할 수 있다.

  • offset 정보 손실
  • DB ↔ 애플리케이션 간 자동 변환 오류
  • Hibernate 버전에 따른 런타임 매핑 에러

이에 대한 한 가지 해결책으로, 다음과 같은
AttributeConverter를 사용하는 방법이 제안됐다.

@Converter(autoApply = true)
public class OffsetDateTimeAttributeConverter
        implements AttributeConverter<OffsetDateTime, Timestamp> {

    @Override
    public Timestamp convertToDatabaseColumn(OffsetDateTime odt) {
        return odt == null ? null : Timestamp.from(odt.toInstant());
    }

    @Override
    public OffsetDateTime convertToEntityAttribute(Timestamp ts) {
        return ts == null ? null
            : OffsetDateTime.ofInstant(ts.toInstant(), ZoneOffset.UTC);
    }
}

이 방식은 DB 스키마 변경 없이 문제를 해결할 수 있어 안정적이다.
다만, 여기서 한 번 더 생각해볼 필요가 있었다.

정말 이 코드가 필요할까?

Hibernate 6.2 이상에서는
다음과 같은 설정 하나로 같은 효과를 낼 수 있다.

spring:
  jpa:
    properties:
      hibernate:
        type:
          default_storage: NORMALIZE_UTC

이 옵션을 사용하면,

  • 모든 OffsetDateTime이
  • DB 저장 전에 자동으로 UTC 기준으로 정규화된다.

즉,

  • 별도의 Converter 클래스를 만들 필요가 없고
  • 도메인 코드가 프레임워크 설정으로부터 깔끔하게 분리된다.

같은 문제를 “코드”가 아니라 “설정”으로 해결할 수 있다면,
그쪽이 더 유지보수에 유리하다
고 판단했다.


② Jackson 전역 설정을 통한 DTO 다이어트

다음 문제는 JSON 직렬화였다.
기존에는 DTO마다 @JsonFormat이 붙어 있었다.

이는 곧,

  • 포맷 변경 시 모든 DTO를 수정해야 하고
  • 표현 책임이 DTO에 과도하게 몰린다는 의미였다.

그래서 Jackson 설정을 전역으로 옮겼다.

builder
    .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
    .simpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");

여기서 중요한 점은
단순히 false 설정만 한 것이 아니라는 점이다.

  • 프론트엔드와 이미 약속된 포맷이 있었고
  • 밀리초 + offset(SSSXXX) 형식을 유지해야 했다.

따라서 전역 설정이더라도 명시적인 패턴 선언이 필요했다.
이 판단이야말로, 단순 설정이 아닌 설계의 영역이라고 생각한다.


4. 지식 정리: Hibernate와 Jackson의 역할 분담

이번 리팩토링을 통해 두 라이브러리의 역할이 명확해졌다.

  • Hibernate
    • 애플리케이션 ↔ DB 사이
    • “시간을 어떻게 저장할 것인가”를 책임진다.
  • Jackson
    • 애플리케이션 ↔ 클라이언트(JSON) 사이
    • “시간을 어떻게 보여줄 것인가”를 책임진다.

이 둘을 분리해서 사고하지 않으면,
설정은 점점 중복되고 코드에는 책임이 뒤섞이게 된다.

 

'Backend > Trouble Shooting' 카테고리의 다른 글

JPA 자동 스키마 관리에서 Flyway 기반 DB 마이그레이션  (0) 2026.02.03
[Spring Boot] Swagger(Springdoc)에서 제네릭 공통 응답 스키마가 꼬이는 문제 해결기  (0) 2026.01.22
[SpringBoot] 검증 로직의 위치(@Valid vs Service) : 테스트 코드로 드러난 설계 문제  (0) 2026.01.14
[SpringBoot] 날짜와 시간 처리를 UTC로 표준화하기  (0) 2026.01.10
[SpringBoot] 프로젝트에 SonarCloud 연동하기: Gradle + GitHub Actions  (0) 2026.01.05
'Backend/Trouble Shooting' 카테고리의 다른 글
  • JPA 자동 스키마 관리에서 Flyway 기반 DB 마이그레이션
  • [Spring Boot] Swagger(Springdoc)에서 제네릭 공통 응답 스키마가 꼬이는 문제 해결기
  • [SpringBoot] 검증 로직의 위치(@Valid vs Service) : 테스트 코드로 드러난 설계 문제
  • [SpringBoot] 날짜와 시간 처리를 UTC로 표준화하기
wlals916
wlals916
  • wlals916
    JM.devlog
    wlals916
    GitHub
  • 전체
    오늘
    어제
    • 분류 전체보기 (28)
      • Algorithms (20)
        • Problem Solving (14)
        • Language & Concept (6)
      • Backend (7)
        • Study Notes (1)
        • Trouble Shooting (6)
        • Development (0)
      • AI (0)
      • Review (1)
      • Dev Log (0)
  • 관리자

    • 글쓰기
    • 관리 페이지
    • 티스토리 홈
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    코테
    C++
    DevOps
    코딩테스트
    백준
    백엔드
    김영한
    프로그래머스
    SpringBoot
    회고
  • 최근 댓글

  • hELLO· Designed By정상우.v4.10.6
wlals916
[SpringBoot] UTC 시간 처리, 코드가 아니라 설정으로 정리하기 (Hibernate 6 & Jackson)
상단으로

티스토리툴바