[트러블슈팅 - hibernate] 나노초가 있으면 왜 에러가 나죠

Hocaron·2024년 7월 22일
1

트러블슈팅

목록 보기
11/12

spring boot 3 으로 올린 프로젝트에서 아래와 같은 에러가 나기 시작했다.

org.opentest4j.AssertionFailedError: Unexpected exception thrown: java.time.DateTimeException: Invalid value for NanoOfSecond (valid values 0 - 999999999): -971000000

밀리초 단위의 시간에서만 발생한다.

	@Override
	public <X> LocalTime wrap(X value, WrapperOptions options) {
		if ( value == null ) {
			return null;
		}

		if (value instanceof LocalTime) {
			return (LocalTime) value;
		}

		if (value instanceof Time) {
			final Time time = (Time) value;
			final LocalTime localTime = time.toLocalTime();
			final long millis = time.getTime() % 1000;
			if ( millis == 0 ) {
				return localTime;
			}
			return localTime.with( ChronoField.NANO_OF_SECOND, millis * 1_000_000L );
		}

LocalTimeJavaType 은 Hibernate에서 LocalTime 타입을 처리하는 클래스이다.

time.getTime()은 에포크 시간(1970년 1월 1일 00:00:00 UTC) 이후의 밀리초를 반환한다. 이 값을 1000으로 나눈 나머지를 계산하여 밀리초 값을 얻는데, time.getTime()이 음수일 때, 모듈러 연산 결과가 음수가 될 수 있다🫠

ValueRange 에서 해당 값이 유효한지 체크하게 되는데, 음수면 AssertionFailedError 에러가 발생한다.

왜 에포크 시간이 음수가 되는걸까?

KST (UTC+9)              UTC                         에포크 시간
────────────────────────────────────────────────────────────────
1970-01-01 01:20:23     1969-12-31 16:20:23        -23177

DB 서버 타임존은 UTC(+0) 이고, 서버 타임존은 KST(+9) 이다. 에포크 시간은 1970년 1월 1일 00:00:00 UTC를 기준으로 하는 시간 표현 방식인데, DB row 값인 01:20:23 는 연도가 없어 1970-01-01 01:20:23 으로 세팅되고, KST -> UTC 로 변환되면서 음수가 되어버린 것이다.

그럼 어떻게 해야하지

해당 이슈는 하이버네이트 6 이슈로, 6.6 에서 패치될 예정이라고 한다.

fyi; 관련 PR

자바에서 시간을 다루는 객체에 대해 알아보자

  1. LocalDate
  • 설명: 날짜를 나타내며, 시간 정보는 포함하지 않는다. 예를 들어, 2023-08-07.
  • 용도: 시간대나 오프셋을 고려하지 않고 날짜만 필요한 경우 사용된다.
LocalDate date = LocalDate.now();
LocalDate specificDate = LocalDate.of(2023, Month.AUGUST, 7);
  1. LocalTime
  • 설명: 시간을 나타내며, 날짜 정보는 포함하지 않는다. 예를 들어, 13:45:30.
  • 용도: 특정 날짜와 관련 없이 시간을 표현할 때 사용된다.
LocalTime time = LocalTime.now();
LocalTime specificTime = LocalTime.of(13, 45, 30);
  1. LocalDateTime
  • 설명: 날짜와 시간을 모두 나타낸다. 시간대나 오프셋 정보는 포함하지 않는다. 예를 들어, 2023-08-07T13:45:30.
  • 용도: 시간대에 구애받지 않고 특정 날짜와 시간을 함께 표현할 때 사용된다.
LocalDateTime dateTime = LocalDateTime.now();
LocalDateTime specificDateTime = LocalDateTime.of(2023, Month.AUGUST, 7, 13, 45, 30);
  1. OffsetTime
  • 설명: 시간과 시간 오프셋을 나타낸다. 예를 들어, 13:45:30+02:00.
  • 용도: 시간대가 아닌 특정 오프셋을 고려한 시간을 표현할 때 사용된다.
OffsetTime offsetTime = OffsetTime.now();
OffsetTime specificOffsetTime = OffsetTime.of(13, 45, 30, 0, ZoneOffset.ofHours(2));
  1. OffsetDateTime
  • 설명: 날짜와 시간, 시간 오프셋을 모두 나타낸다. 예를 들어, 2023-08-07T13:45:30+02:00.
  • 용도: 시간대가 아닌 특정 오프셋을 고려한 날짜와 시간을 표현할 때 사용된다.
OffsetDateTime offsetDateTime = OffsetDateTime.now();
OffsetDateTime specificOffsetDateTime = OffsetDateTime.of(2023, Month.AUGUST, 7, 13, 45, 30, 0, ZoneOffset.ofHours(2));
  1. ZonedDateTime
  • 설명: 날짜와 시간, 시간대 정보를 모두 나타낸다. 예를 들어, 2023-08-07T13:45:30+02:00[Europe/Paris].
  • 용도: 특정 시간대를 고려한 날짜와 시간을 표현할 때 사용된다.
ZonedDateTime zonedDateTime = ZonedDateTime.now();
ZonedDateTime specificZonedDateTime = ZonedDateTime.of(2023, Month.AUGUST, 7, 13, 45, 30, 0, ZoneId.of("Europe/Paris"));
  1. Instant
  • 설명: 타임라인의 특정 시점을 나타낸다. 주로 에포크 시간(1970년 1월 1일 00:00:00 UTC) 이후의 시간을 나노초 단위로 나타낸다.
  • 용도: 특정 시점을 표현할 때 사용된다. 주로 타임스탬프를 다룰 때 사용된다.
Instant now = Instant.now();
Instant specificInstant = Instant.ofEpochSecond(1627849265); // Epoch time
  1. year, YearMonth, MonthDay
  • Year: 연도를 나타낸다.

    Year year = Year.of(2023);
  • YearMonth: 연도와 월을 나타낸다.

    YearMonth yearMonth = YearMonth.of(2023, Month.AUGUST);
  • MonthDay: 월과 일을 나타낸다.

    MonthDay monthDay = MonthDay.of(Month.AUGUST, 7);
  1. `oneId와 ZoneOffset
  • ZoneId: 시간대를 나타낸다.

    ZoneId zoneId = ZoneId.of("Europe/Paris");
  • ZoneOffset: UTC 기준으로 오프셋을 나타낸다.

    ZoneOffset offset = ZoneOffset.ofHours(9);

이 객체들은 자바의 새로운 날짜 및 시간 API인 java.time 패키지에 포함되어 있으며, JSR 310을 기반으로 설계되어있다.

profile
기록을 통한 성장을

0개의 댓글