[프로젝트] Timestamp와 JPA Entity

공부하는 감자·2024년 1월 23일
1

F-Lab 프로젝트

목록 보기
5/11

들어가는 말

도메인 모델과 Entity를 만들다가 Birthday와 같은 날짜를 String이 아닌 날짜 형식으로 맞춰주는 게 좋다는 피드백을 들었다.

String으로 하면 데이터의 타입이나 형식이 잘못 들어왔을 경우를 막기 어렵다. 물론, 검증 로직을 추가하면 되지만 그럴 바에는 처음부터 타입을 명시해두는 편이 실수를 줄일 수 있다.

따라서, java의 날짜 타입을 찾아보다가 글을 정리하게 되었다.

Java 날짜 API

내가 알고 있고 배웠던 Java 날짜 타입은 DateCalendar 와 같은 타입이었다. 그런데 찾아보니, 이 두 타입은 문제가 있어 사용하지 않는 것이 좋다고 한다.

따라서 기존의 날짜 API와 대체하여 나온 새로운 날짜 API를 정리해봤다.

기존 Java의 날짜 API

Java 8 이전에 사용하던 Date 관련 클래스로는, 대표적으로 다음 세 가지가 있다.

  • Date
  • Calendar
  • SimpleDateFormat

Date

  • java.util 패키지에 있는 객체로, Java 8부터 deprecated 되었다.
  • 원하는 날짜 및 시간을 가져오고, 시간을 밀리초로 변환하는 등의 기능을 사용할 수 있다.
import java.util.Date;

public class DateExample {
    public static void main(String[] args) {

        // 현재 날짜와 시간을 가져오기
        Date currentDate = new Date();
        System.out.println("현재 날짜와 시간: " + currentDate);

        // 특정 날짜와 시간을 생성하기 (2022년 1월 24일)
		// 연도는 1900년이 기준이며, 상대적인 값으로 넣어야 한다.
        Date specificDate = new Date(122, 0, 24);
        System.out.println("특정 날짜와 시간: " + specificDate);

        // 시간을 밀리초로 가져오기
        long currentTimeInMillis = currentDate.getTime();
        System.out.println("현재 시간(밀리초): " + currentTimeInMillis);

        // 특정 날짜와 시간으로 설정하기 (현재 시간 + 1일)
        Date newDate = new Date();
        newDate.setTime(currentTimeInMillis + 86400000);
        System.out.println("하루 뒤의 날짜와 시간: " + newDate);
    }
}

Calendar

  • java.util 패키지에 있는 객체로, Java 8부터 deprecated 되었다.
  • 날짜 및 시간 정보를 다루는 데 사용된다.
import java.util.Calendar;

public class CalendarExample {
    public static void main(String[] args) {
        // 현재 날짜와 시간 정보 가져오기
        Calendar calendar = Calendar.getInstance();
        System.out.println("현재 날짜와 시간: " + calendar.getTime());

        // 특정 날짜와 시간 설정
        calendar.set(2022, Calendar.JANUARY, 24, 14, 30, 0);
        System.out.println("지정된 날짜와 시간: " + calendar.getTime());

        // 특정 필드 값 변경
        calendar.set(Calendar.YEAR, 2023);
        System.out.println("연도 변경 후: " + calendar.getTime());

        // 특정 필드 값 추가
        calendar.add(Calendar.MONTH, 3);
        System.out.println("3개월 후: " + calendar.getTime());

        // 특정 필드 값 롤백
        calendar.roll(Calendar.DAY_OF_MONTH, -5);
        System.out.println("5일 전으로 롤백 후: " + calendar.getTime());
    }
}

SimpleDateFormat

  • 날짜 및 시간을 지정된 패턴에 맞게 변환하거나, 특정 패턴의 문자열을 날짜 및 시간으로 파싱하는 데 사용한다.
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class SimpleDateFormatExample {
    public static void main(String[] args) {
        // 현재 날짜와 시간을 얻어옵니다.
        Date currentDate = new Date();
        System.out.println("현재 날짜와 시간: " + currentDate);

        // SimpleDateFormat 객체를 생성하고 원하는 패턴을 지정합니다.
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        // 날짜를 문자열로 형식화합니다.
        String formattedDate = sdf.format(currentDate);
        System.out.println("형식화된 날짜: " + formattedDate);

        // 문자열을 날짜로 파싱합니다.
        String dateString = "2022-01-24 15:30:00";
        try {
            Date parsedDate = sdf.parse(dateString);
            System.out.println("파싱된 날짜: " + parsedDate);
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}

문제점

  • 부적절한 클래스와 메서드 이름을 가지고 있다.
    • Date 클래스의 경우, TimeStampe 방식으로 동작하지만 이름은 Date이다.
  • Thread Saftety 하지 않다.
    • Date 클래스는 mutable하기 때문에, 다른 Thread에서 값을 참조하고 변경할 수 있다.
  • 버그가 발생할 여지가 많다.
    • Calender 클래스는 month가 0부터 시작한다. (0이 1월)

현재 Java 날짜/시간 API

Java SE 8 버전부터 추가되었다.

  • date, time, instant, time-zone을 포함하는 공식 시간 개념 지원
  • immutable 구현
  • 개발자의 사용성에 중점을 둔 JDK에 적절하고 효과적인 API 제공
  • 기존의 JDK API와의 통합
  • 제한된 calendar 시스템 세트를 제공하고 다른 것들로 확장 가능
  • ISO-8601, CLDR 및 BCP47을 포함한 관련 표준 사용
  • UTC에 연결하여 명시적 시간 척도를 기반

Instant

  • 1970년 1월 1일 UTC의 첫 번째 순간 이후의 현재 시간까지의 나노초
  • 일반적으로 순간을 표현할 때 사용한다.
  • Unix Timestamp를 구할 때 사용할 수 있다.
    • Unix Timestamp는 숫자 자료형을 가지고 연산을 하기 때문에 연산 속도가 빠르다.
  • UTC 표준 시간대를 사용한다.
Instant cur = Instant.now();
System.out.println(cur);                    // 2022-10-09T12:45:11.825755Z
System.out.println(cur.getEpochSecond());   // 1665319511
System.out.println(cur.getNano());          // 825755000

LocalDate, LocalTime, LocalDateTime

  • LocalDate: 날짜
  • LocalTime: 시간
  • LocalDateTime: 날짜 + 시간
    • LocalDate와 LocalTime으로 구성되어 있다.
    • LocalDateTime.now()는 default time-zone의 정보를 사용한다.
  • 시간대(Zone Offset/Zone Region)에 대한 정보가 없다.
  • 주로 생일이나 기념일 등에 사용된다.
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;

public class LocalDateExample {
    public static void main(String[] args) {

		// LocalDate
        // 현재 날짜 가져오기
        LocalDate currentDate = LocalDate.now();
        System.out.println("현재 날짜: " + currentDate);

        // 특정 날짜 생성
        LocalDate specificDate = LocalDate.of(2022, 1, 24);
        System.out.println("특정 날짜: " + specificDate);

		// LocalTime
        // 현재 시간 가져오기
        LocalTime currentTime = LocalTime.now();
        System.out.println("현재 시간: " + currentTime);

        // 특정 시간 생성
        LocalTime specificTime = LocalTime.of(14, 30, 0);
        System.out.println("특정 시간: " + specificTime);

		// LocalDateTime
        // 현재 날짜와 시간 가져오기
        LocalDateTime currentDateTime = LocalDateTime.now();
        System.out.println("현재 날짜와 시간: " + currentDateTime);

        // 특정 날짜와 시간 생성
        LocalDateTime specificDateTime = LocalDateTime.of(2022, 1, 24, 14, 30, 0);
        System.out.println("특정 날짜와 시간: " + specificDateTime);
    }
}

Timezone

Timezone이란

시간대(時間帶,time zone)는 영국의 그리니치 천문대(본초 자오선, 경도 0도)를 기준으로 지역에 따른 시간의 차이, 다시 말해 지구의 자전에 따른 지역 사이에 생기는 낮과 밤의 차이를 인위적으로 조정하기 위해 고안된 시간의 구분선을 일컫는다.

출처: 위키백과 - https://ko.wikipedia.org/wiki/시간대

TimeZone은 특정 지역에서 사용되는 표준 시간을 말한다. 지구는 24개의 시간대로 나뉘어져 있으며, 각 시간대는 특정 경도를 기준으로 정해진다.

시간대는 UTC를 기준으로 일정한 시간 차이를 가지며, 이를 통해 전 세계의 시간을 일관성 있게 표시할 수 있다.

  • GMT (그리니치 평균시)
    • 영국 런던의 그리니치 천문대를 기준으로 하는 협정 세계시이다.
    • 지구 자전의 영향을 받기 때문에, 조금씩 늦어지고 있는 자전 주기 문제를 해결하기 위해 새로 협정 세계시(UTC)가 재정되었다.
  • UTC (협정 세계시)
    • 세슘 원자의 진동수를 기반으로 측정해 매우 정확하다.
  • 오프셋
    • UTC와의 차이를 말한다.
    • UTC 기준 (+, -) 시, 분 으로 표현한다.
    • UTC + 9 라면 UTC 보다 9시간이 빠르다는 의미이다.

MySQL의 Timestamp와 JPA 매핑

나는 MySQL을 DB로 고려 중이었고, 생성 일시와 같은 날짜+시간을 Timestamp로 저장하기로 결정했다.

  • Timestamp는 내부적으로 시간을 가져올 때 Timezone을 적용해서 보여준다.

따라서 Timestamp와 LocalDateTime을 매핑하는 방법에 대해 찾아봤는데 JPA 2.2 에서도 지원하고 있었다.

@Column(name = "local_time", columnDefinition = "TIME")
private LocalTime localTime;

@Column(name = "local_date", columnDefinition = "DATE")
private LocalDate localDate;

@Column(name = "local_date_time", columnDefinition = "TIMESTAMP")
private LocalDateTime localDateTime;

코드 출처: https://www.baeldung.com/jpa-java-time

만약, JPA 2.2 버전을 사용한다면 Converter를 사용해서 매핑시켜줘야 한다.

TimeZone을 고려한 Java 타입 선택

대한민국 내에서라면 TimeZone이 없는 객체(Local로 시작하는 객체들)를 선택하면 되지만, 글로벌한 환경을 고려한다면 TimeZone이 있는 객체를 선택하는 것이 좋다.

맺는 말

처음에는 Entity에 Timestamp로 선언을 했다가, 나중에 LocalDate를 적용하면서 이것도 수정해야 하나 고민에 빠졌다.

LocalDateTime을 사용하면 Timezone 정보가 없어서 DB와 시간이 엇나갈 수 있겠다는 생각이 들었기 때문이다.

그래서 TimeZone부터 해서 다시 찾아봤는데, LocalDateTime과 Timestamp를 매핑해주는 기술을 지원하고 있었다.

TimeZone 관련해서는 예전에도 지나가듯 관련 글을 읽어본 적이 있었는데, 일단 이렇게 정리해두고 나중에 개발하다 문제가 생기면 깊게 분석해봐야겠다.

Reference

참고 사이트

Java의 날짜, 시간에 대한 기본적인 정책

자바의 정석 - 날짜와 시간(Calendar와 Date, java.time패키지) | Integerous DevLog

Java Date & Time, 제대로 사용하기

Java 프로젝트에서 Default Time Zone은 어떻게 설정되는가?

Java 타임존, 날짜 그리고 시간객체 뽀개기

타임존

협정세계시(UTC)와 그리니치 평균시(GMT)란? + 서머타임(Summer Time) | bobbohee

MySQL, DATETIME VS TIMESTAMP

JPA 에서 LocalDateTime 사용하기

JPA 2.2 Support for Java 8 Date/Time Types | Baeldung

profile
책을 읽거나 강의를 들으며 공부한 내용을 정리합니다. 가끔 개발하는데 있었던 이슈도 올립니다.

0개의 댓글

관련 채용 정보