[java] JPA 및 Hibernate를 사용하여 UTC 시간대에 날짜 / 시간 및 타임 스탬프를 저장하는 방법

날짜 / 시간을 UTC (GMT) 시간대로 데이터베이스에 저장하도록 JPA / Hibernate를 어떻게 구성 할 수 있습니까? 다음 주석이 달린 JPA 엔티티를 고려하십시오.

public class Event {
    @Id
    public int id;

    @Temporal(TemporalType.TIMESTAMP)
    public java.util.Date date;
}

날짜가 2008 년 2 월 3 일 오전 9시 30 분 태평양 표준시 (PST)이면 2008 년 2 월 3 일 오후 5시 30 분의 UTC 시간을 데이터베이스에 저장하고 싶습니다. 마찬가지로 데이터베이스에서 날짜를 검색 할 때 UTC로 해석하고 싶습니다. 따라서이 경우 530pm은 530pm UTC입니다. 표시되면 오전 9시 30 분 PST로 형식이 지정됩니다.



답변

Hibernate 5.2에서는 이제 다음 구성 속성을 사용하여 UTC 시간대를 강제 적용 할 수 있습니다.

<property name="hibernate.jdbc.time_zone" value="UTC"/>

자세한 내용은 이 도움말을 확인 하세요 .


답변

내가 아는 한, 전체 자바 앱을 UTC 시간대에 넣어야하며 (Hibernate가 날짜를 UTC로 저장하도록), 물건을 표시 할 때 원하는 시간대로 변환해야합니다 (적어도 우리는 그렇게합니다). 이 방법).

시작시 다음을 수행합니다.

TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC"));

원하는 시간대를 DateFormat으로 설정합니다.

fmt.setTimeZone(TimeZone.getTimeZone("Europe/Budapest"))


답변

Hibernate는 Dates의 시간대 항목을 무시하지만 (아무것도 없기 때문에) 실제로 문제를 일으키는 것은 JDBC 계층입니다. ResultSet.getTimestamp그리고 PreparedStatement.setTimestamp둘 다 문서에서 데이터베이스에서 읽고 쓸 때 기본적으로 현재 JVM 시간대로 날짜를 변환한다고 말합니다.

org.hibernate.type.TimestampType이 JDBC 메소드가 로컬 시간대 대신 UTC를 사용하도록 강제 하는 서브 클래 싱 을 통해 Hibernate 3.5에서 이에 대한 해결책을 찾았습니다 .

public class UtcTimestampType extends TimestampType {

    private static final long serialVersionUID = 8088663383676984635L;

    private static final TimeZone UTC = TimeZone.getTimeZone("UTC");

    @Override
    public Object get(ResultSet rs, String name) throws SQLException {
        return rs.getTimestamp(name, Calendar.getInstance(UTC));
    }

    @Override
    public void set(PreparedStatement st, Object value, int index) throws SQLException {
        Timestamp ts;
        if(value instanceof Timestamp) {
            ts = (Timestamp) value;
        } else {
            ts = new Timestamp(((java.util.Date) value).getTime());
        }
        st.setTimestamp(index, ts, Calendar.getInstance(UTC));
    }
}

이러한 유형을 사용하는 경우 TimeType 및 DateType을 수정하기 위해 동일한 작업을 수행해야합니다. 단점은 누군가가 더 일반적인 재정의 방법을 알지 않는 한 POJO의 모든 날짜 필드에서 기본값 대신 이러한 유형이 사용되도록 수동으로 지정해야한다는 것입니다 (또한 순수한 JPA 호환성을 깨뜨립니다).

업데이트 : Hibernate 3.6은 유형 API를 변경했습니다. 3.6에서는이를 구현하기 위해 UtcTimestampTypeDescriptor 클래스를 작성했습니다.

public class UtcTimestampTypeDescriptor extends TimestampTypeDescriptor {
    public static final UtcTimestampTypeDescriptor INSTANCE = new UtcTimestampTypeDescriptor();

    private static final TimeZone UTC = TimeZone.getTimeZone("UTC");

    public <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) {
        return new BasicBinder<X>( javaTypeDescriptor, this ) {
            @Override
            protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
                st.setTimestamp( index, javaTypeDescriptor.unwrap( value, Timestamp.class, options ), Calendar.getInstance(UTC) );
            }
        };
    }

    public <X> ValueExtractor<X> getExtractor(final JavaTypeDescriptor<X> javaTypeDescriptor) {
        return new BasicExtractor<X>( javaTypeDescriptor, this ) {
            @Override
            protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
                return javaTypeDescriptor.wrap( rs.getTimestamp( name, Calendar.getInstance(UTC) ), options );
            }
        };
    }
}

이제 앱이 시작될 때 TimestampTypeDescriptor.INSTANCE를 UtcTimestampTypeDescriptor의 인스턴스로 설정하면 POJO의 주석을 변경하지 않고도 모든 타임 스탬프가 UTC로 저장되고 처리됩니다. [아직 테스트하지 않았습니다]


답변

Spring Boot JPA를 사용하면 application.properties 파일에서 아래 코드를 사용하고 분명히 원하는대로 시간대를 수정할 수 있습니다.

spring.jpa.properties.hibernate.jdbc.time_zone = UTC

그런 다음 Entity 클래스 파일에서

@Column
private LocalDateTime created;


답변

Shaun Stone의 힌트를 사용하여 완전히 기반하고 매각에 빚진 답변을 추가합니다 . 일반적인 문제이고 해결책이 약간 헷갈 리기 때문에 자세히 설명하고 싶었습니다.

이것은 Hibernate 4.1.4.Final을 사용하고 있지만 3.6 이후의 모든 것이 작동 할 것이라고 생각합니다.

먼저 divestoclimb의 UtcTimestampTypeDescriptor를 만듭니다.

public class UtcTimestampTypeDescriptor extends TimestampTypeDescriptor {
    public static final UtcTimestampTypeDescriptor INSTANCE = new UtcTimestampTypeDescriptor();

    private static final TimeZone UTC = TimeZone.getTimeZone("UTC");

    public <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) {
        return new BasicBinder<X>( javaTypeDescriptor, this ) {
            @Override
            protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
                st.setTimestamp( index, javaTypeDescriptor.unwrap( value, Timestamp.class, options ), Calendar.getInstance(UTC) );
            }
        };
    }

    public <X> ValueExtractor<X> getExtractor(final JavaTypeDescriptor<X> javaTypeDescriptor) {
        return new BasicExtractor<X>( javaTypeDescriptor, this ) {
            @Override
            protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
                return javaTypeDescriptor.wrap( rs.getTimestamp( name, Calendar.getInstance(UTC) ), options );
            }
        };
    }
}

그런 다음 상위 생성자 호출에서 SqlTypeDescriptor로 TimestampTypeDescriptor 대신 UtcTimestampTypeDescriptor를 사용하지만 그렇지 않으면 모든 것을 TimestampType에 위임하는 UtcTimestampType을 만듭니다.

public class UtcTimestampType
        extends AbstractSingleColumnStandardBasicType<Date>
        implements VersionType<Date>, LiteralType<Date> {
    public static final UtcTimestampType INSTANCE = new UtcTimestampType();

    public UtcTimestampType() {
        super( UtcTimestampTypeDescriptor.INSTANCE, JdbcTimestampTypeDescriptor.INSTANCE );
    }

    public String getName() {
        return TimestampType.INSTANCE.getName();
    }

    @Override
    public String[] getRegistrationKeys() {
        return TimestampType.INSTANCE.getRegistrationKeys();
    }

    public Date next(Date current, SessionImplementor session) {
        return TimestampType.INSTANCE.next(current, session);
    }

    public Date seed(SessionImplementor session) {
        return TimestampType.INSTANCE.seed(session);
    }

    public Comparator<Date> getComparator() {
        return TimestampType.INSTANCE.getComparator();
    }

    public String objectToSQLString(Date value, Dialect dialect) throws Exception {
        return TimestampType.INSTANCE.objectToSQLString(value, dialect);
    }

    public Date fromStringValue(String xml) throws HibernateException {
        return TimestampType.INSTANCE.fromStringValue(xml);
    }
}

마지막으로 Hibernate 구성을 초기화 할 때 UtcTimestampType을 유형 재정의로 등록합니다.

configuration.registerTypeOverride(new UtcTimestampType());

이제 타임 스탬프는 데이터베이스를 오가는 JVM의 시간대와 관련이 없어야합니다. HTH.


답변

이 일반적인 문제는 Hibernate에 의해 처리 될 것이라고 생각할 것입니다. 하지만 그렇지 않습니다! 제대로하기위한 몇 가지 “핵”이 있습니다.

내가 사용하는 것은 데이터베이스에 날짜를 Long으로 저장하는 것입니다. 그래서 저는 항상 1/1/70 이후에 밀리 초 단위로 작업하고 있습니다. 그런 다음 내 클래스에 날짜 만 반환 / 수락하는 게터와 세터가 있습니다. 따라서 API는 동일하게 유지됩니다. 단점은 내가 데이터베이스에 갈망한다는 것입니다. 그래서 SQL을 사용하면 멋진 날짜 연산자가 아닌 <,>, = 비교 만 할 수 있습니다.

또 다른 접근 방식은 http://www.hibernate.org/100.html에 설명 된대로 사용자 지정 매핑 유형을 사용하는 것입니다.

나는 이것을 처리하는 올바른 방법은 날짜 대신 달력을 사용하는 것이라고 생각합니다. 캘린더를 사용하면 유지하기 전에 TimeZone을 설정할 수 있습니다.

참고 : 어리석은 stackoverflow는 댓글을 달 수 없습니다. 그래서 여기에 david a에 대한 응답이 있습니다.

시카고에서이 개체를 만드는 경우 :

new Date(0);

Hibernate는 “12/31/1969 18:00:00″으로 유지합니다. 날짜에는 시간대가 없어야하므로 왜 조정해야하는지 잘 모르겠습니다.


답변

여기에는 몇 가지 시간대가 있습니다.

  1. 암시 적 시간대가 UTC 인 Java의 Date 클래스 (util 및 sql)
  2. JVM이 실행중인 시간대 및
  3. 데이터베이스 서버의 기본 시간대.

이 모든 것이 다를 수 있습니다. Hibernate / JPA는 사용자가 시간대 정보가 데이터베이스 서버에 보존되는지 쉽게 확인할 수 없다는 점에서 심각한 설계 결함이 있습니다 (JVM에서 정확한 시간과 날짜를 재구성 할 수 있음).

JPA / Hibernate를 사용하여 시간대를 (쉽게) 저장할 수 없으면 정보가 손실되고 정보가 손실되면이를 구성하는 데 비용이 많이 듭니다 (가능한 경우).

항상 시간대 정보를 저장하는 것이 더 낫고 (기본값이어야 함) 사용자는 시간대를 최적화 할 수있는 선택적인 기능을 가져야합니다 (실제로 디스플레이에만 영향을 주지만 모든 날짜에 여전히 암시 적 시간대가 있음).

죄송합니다.이 게시물은 해결 방법을 제공하지 않지만 (다른 곳에서 답변 됨) 항상 시간대 정보를 저장하는 것이 중요한 이유를 합리화합니다. 안타깝게도 많은 컴퓨터 과학자와 프로그래밍 실무자들이 단순히 “정보 손실”이라는 관점을 인식하지 못하고 그로 인해 국제화와 같은 일을 매우 어렵게 만드는 이유 때문에 시간대에 대한 필요성에 대해 반대하는 것 같습니다. 고객과 조직의 사람들이 전 세계로 이동합니다.