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


106

날짜 / 시간을 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로 형식이 지정됩니다.


1
블라드 미할 세아의 대답은 (5.2 이상 최대 절전 모드에 대한) 업데이트 된 대답을 제공합니다
Dinei

답변:


73

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

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

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


19
나는 이것도 썼다. D 자, 누가 Hibernate에서이 기능에 대한 지원을 추가 했는가?
Vlad Mihalcea

아, 지금 나는 ... 이름과 사진 프로필 그 기사에서 동일 좋은 직장 블라드 :) 실현
Dinei

@VladMihalcea가 Mysql의 경우 useTimezone=true연결 문자열에서 사용하여 시간대를 사용하도록 MySql에 알려야 합니다. 그런 다음 속성 설정 만 hibernate.jdbc.time_zone작동합니다
TheCoder

실제로 useLegacyDatetimeCodefalse 로 설정해야합니다
Vlad Mihalcea

2
hibernate.jdbc.time_zone은 PostgreSQL과 함께 사용할 때 무시되거나 효과가없는 것으로 보임
Alex R

48

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

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

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

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

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

5
mitchnull, Hibernate가 날짜 설정을 JDBC 드라이버에 위임하고 각 JDBC 드라이버가 날짜와 시간대를 다르게 처리하기 때문에 솔루션이 모든 경우에 작동하지 않습니다. stackoverflow.com/questions/4123534/…를 참조하십시오 .
Derek Mahar

2
하지만 JVM "-Duser.timezone = + 00 : 00"속성에 알리는 내 앱을 시작하면 동일한 동작이되지 않습니까?
rafa.ferreira

8
내가 알 수있는 한, JVM과 데이터베이스 서버가 다른 시간대에있는 경우를 제외하고 모든 경우에서 작동합니다.
Shane

stevekuo 및 그 이하가 훨씬 더 낫다 divestoclimb 솔루션을 참조 @mitchnull 부작용의 증거 stackoverflow.com/a/3430957/233906
Cerber

HibernateJPA가 "@Factory"및 "@Externalizer"주석을 지원합니까? OpenJPA 라이브러리에서 datetime utc 처리를 수행하는 방법입니다. stackoverflow.com/questions/10819862/…
Whome 2013

44

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로 저장되고 처리됩니다. [아직 테스트하지 않았습니다]


3
Hibernate에게 사용자 정의를 사용하도록 어떻게 지시 UtcTimestampType합니까?
Derek Mahar

divestoclimb, 어떤 버전의 Hibernate와 UtcTimestampType호환됩니까?
Derek Mahar

2
"ResultSet.getTimestamp 및 PreparedStatement.setTimestamp는 모두 데이터베이스에서 읽고 쓸 때 기본적으로 현재 JVM 시간대로 /에서 날짜를 변환한다고 문서에서 말합니다." 참고 문헌이 있습니까? 이러한 메서드에 대한 Java 6 Javadoc에서는 이에 대한 언급이 없습니다. stackoverflow.com/questions/4123534/… 에 따르면 이러한 방법이 특정 시간대에 시간대를 적용하는 방법 Date또는 TimestampJDBC 드라이버에 따라 다릅니다.
Derek Mahar

1
작년에 JVM 시간대 사용에 대해 읽었다 고 맹세 할 수 있었지만 지금은 찾을 수 없습니다. 특정 JDBC 드라이버에 대한 문서에서 발견하고 일반화했을 수 있습니다.
divestoclimb 2011

2
3.6 버전의 예제를 작동하려면 기본적으로 TimeStampType 주위의 래퍼 인 새 유형을 만든 다음 해당 유형을 필드에 설정해야했습니다.
Shaun Stone

17

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

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

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

@Column
private LocalDateTime created;

11

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.


6
JPA 및 Spring 구성에 대한 솔루션을 보는 것이 좋을 것입니다.
Aubergine 2013 년

2
Hibernate 내에서 네이티브 쿼리와 함께이 접근 방식을 사용하는 것에 관한 참고 사항. 이러한 재정의 된 유형을 사용하려면 query.setParameter (int pos, Date value, TemporalType temporalType)이 아닌 query.setParameter (int pos, Object value)로 값을 설정해야합니다. 후자를 사용하는 경우 Hibernate는 하드 코딩 된 원래 유형 구현을 사용합니다.
Nigel

어디에서 configuration.registerTypeOverride (new UtcTimestampType ()); 문을 호출해야합니까? ?
Stony

@Stony Hibernate 구성을 초기화 할 때마다. HibernateUtil (대부분)이 있다면 거기에있을 것입니다.
Shane

1
작동하지만 확인한 후 postgres 서버가 timezone = "UTC"에서 작동하고 모든 기본 유형의 타임 스탬프가 "시간대가있는 타임 스탬프"(자동으로 수행됨)로 작동하는 데 필요하지 않음을 깨달았습니다. 그러나, 완전한 하나의 클래스와 overridding 스프링 공장 콩으로 최대 절전 모드 4.3.5 GA 여기를 고정 버전 pastebin.com/tT4ACXn6
루카스 프란코브스키

10

이 일반적인 문제는 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
부끄러워! 당신이 옳았 고 게시물의 링크가 그것을 잘 설명합니다. 이제 내 대답이 부정적인 평판을받을 만하다고 생각
합니다.

1
전혀. 왜 이것이 문제인지에 대한 매우 명확한 예를 게시하도록 권장했습니다.
codefinger

4
나는 당신이 제안한대로 UTC로 DB에 저장되도록 Calendar 개체를 사용하여 시간을 올바르게 유지할 수있었습니다. 그러나 데이터베이스에서 지속 된 엔터티를 읽을 때 Hibernate는 로컬 시간대에 있고 Calendar 개체가 올바르지 않다고 가정합니다!
John K

1
John K,이 Calendar읽기 문제 를 해결하기 위해 Hibernate 또는 JPA가 각 매핑에 대해 Hibernate가 읽고 쓰는 날짜를 TIMESTAMP열에 변환해야하는 시간대를 지정하는 방법을 제공해야한다고 생각합니다 .
Derek Mahar

joekutner, stackoverflow.com/questions/4123534/… 를 읽은 후 , Timestamp날짜를 저장하는 데 JDBC 드라이버를 반드시 신뢰할 수 없기 때문에 Epoch 이후 데이터베이스에 밀리 초를 저장해야한다는 의견을 공유 했습니다. 우리는 기대할 것입니다.
Derek Mahar

8

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

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

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

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

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

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


1
"Hibernate / JPA에는 심각한 설계 결함이 있습니다."저는 이것이 SQL의 결함이라고 말하고 싶습니다. 이것은 전통적으로 시간대를 암시 적으로 허용했기 때문에 잠재적으로 모든 것이 가능합니다. 어리석은 SQL.
Raedwald

3
실제로 항상 시간대를 저장하는 대신 하나의 시간대 (일반적으로 UTC)로 표준화하고 지속될 때 (그리고 읽을 때 다시) 모든 것을이 시간대로 변환 할 수도 있습니다. 이것이 우리가 보통하는 일입니다. 그러나 JDBC는 직접 지원하지 않습니다 :-/.
sleske

3

표준 SQL 날짜 및 시간 유형과 JSR 310 및 Joda 시간에 대한 사용자 유형이있는 Sourceforge의 내 프로젝트를 살펴보십시오. 모든 유형은 오프셋 문제를 해결하려고합니다. 참조 http://sourceforge.net/projects/usertype/를

편집 :이 의견에 첨부 된 Derek Mahar의 질문에 대한 응답으로 :

"Chris, 사용자 유형이 Hibernate 3 이상에서 작동합니까? – Derek Mahar 2010 년 11 월 7 일 12:30"

예, 이러한 유형은 Hibernate 3.6을 포함한 Hibernate 3.x 버전을 지원합니다.


2

날짜는 어떤 시간대도 아니지만 (모든 사람에게 동일한 시간의 정의 된 순간부터 밀리 초 사무실입니다), 기본 (R) DB는 일반적으로 정치 형식 (년, 월, 일,시, 분, 초,. ..) 시간대에 민감합니다.

심각하기 위해, 최대 절전 모드는 반드시 DB를 날짜가로드 또는 저장이 그것의 자신의 가정하지 않을 때 너무 등 - 및 - 같은 시간대에 있음을 매핑 어떤 형태의 내 이야기되고 허용 될 수 ...


1

DB에 날짜를 UTC로 저장 varchar하고 명시 적 String <-> java.util.Date변환을 사용하지 않거나 전체 Java 앱을 UTC 시간대로 설정 하려고 할 때 동일한 문제가 발생했습니다 (JVM이 다음과 같은 경우 다른 예기치 않은 문제가 발생할 수 있기 때문). 많은 응용 프로그램에서 공유).

따라서 DbAssist데이터베이스에서 읽기 / 쓰기를 UTC 날짜로 쉽게 수정할 수 있는 오픈 소스 프로젝트 가 있습니다. JPA 어노테이션을 사용하여 엔티티의 필드를 맵핑하므로 Maven pom파일에 다음 종속성을 포함하기 만하면 됩니다.

<dependency>
    <groupId>com.montrosesoftware</groupId>
    <artifactId>DbAssist-5.2.2</artifactId>
    <version>1.0-RELEASE</version>
</dependency>

그런 다음 @EnableAutoConfigurationSpring 애플리케이션 클래스 앞에 주석 을 추가하여 수정 (Hibernate + Spring Boot 예제의 경우)을 적용합니다. 다른 설정 설치 지침 및 더 많은 사용 예제는 프로젝트의 github를 참조하십시오 .

좋은 점은 엔티티를 전혀 수정할 필요가 없다는 것입니다. java.util.Date필드를 그대로 둘 수 있습니다 .

5.2.2사용중인 Hibernate 버전과 일치해야합니다. 프로젝트에서 어떤 버전을 사용하고 있는지 잘 모르겠지만 제공된 수정 사항의 전체 목록은 프로젝트의 github 의 위키 페이지에서 확인할 수 있습니다 . 다양한 Hibernate 버전에서 수정 사항이 다른 이유는 Hibernate 작성자가 릴리스간에 API를 몇 번 변경했기 때문입니다.

내부적으로 수정은 divestoclimb, Shane 및 몇 가지 다른 소스의 힌트를 사용하여 사용자 지정 UtcDateType. 그런 다음 필요한 모든 시간대 처리를 처리 java.util.Date하는 사용자 정의로 표준 을 매핑합니다 UtcDateType. 유형의 매핑은 @Typedef제공된 package-info.java파일의 주석을 사용하여 수행 됩니다.

@TypeDef(name = "UtcDateType", defaultForType = Date.class, typeClass = UtcDateType.class),
package com.montrosesoftware.dbassist.types;

여기에서 그러한 시간 이동이 발생하는 이유와이를 해결하기위한 접근 방식을 설명 하는 기사를 찾을 수 있습니다 .


1

Hibernate는 주석이나 다른 수단으로 시간대를 지정하는 것을 허용하지 않습니다. 날짜 대신 달력을 사용하는 경우 HIbernate 속성 AccessType을 사용하여 해결 방법을 구현하고 직접 매핑을 구현할 수 있습니다. 고급 솔루션은 사용자 지정 UserType을 구현하여 날짜 또는 달력을 매핑하는 것입니다. 두 솔루션 모두 내 블로그 게시물 ( http://www.joobik.com/2010/11/mapping-dates-and-time-zones-with.html)에 설명되어 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.