Hibernate nie zna stref czasowych w datach (ponieważ ich nie ma), ale w rzeczywistości to warstwa JDBC powoduje problemy. ResultSet.getTimestamp
i PreparedStatement.setTimestamp
obaj twierdzą w swoich dokumentach, że domyślnie przekształcają daty do / z bieżącej strefy czasowej JVM podczas odczytu i zapisu z / do bazy danych.
Wymyśliłem rozwiązanie tego problemu w Hibernate 3.5 przez podklasę, org.hibernate.type.TimestampType
która zmusza te metody JDBC do używania UTC zamiast lokalnej strefy czasowej:
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));
}
}
To samo należy zrobić, aby naprawić TimeType i DateType, jeśli używasz tych typów. Wadą jest to, że będziesz musiał ręcznie określić, że te typy mają być używane zamiast wartości domyślnych w każdym polu daty w twoich POJO (a także łamie czystą kompatybilność JPA), chyba że ktoś zna bardziej ogólną metodę nadpisywania.
AKTUALIZACJA: Hibernate 3.6 zmienił API typów. W 3.6 napisałem klasę UtcTimestampTypeDescriptor, aby to zaimplementować.
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 );
}
};
}
}
Teraz, gdy aplikacja zostanie uruchomiona, jeśli ustawisz TimestampTypeDescriptor.INSTANCE na wystąpienie UtcTimestampTypeDescriptor, wszystkie sygnatury czasowe będą przechowywane i traktowane jako podane w czasie UTC bez konieczności zmiany adnotacji w POJO. [Jeszcze tego nie testowałem]