Hibernate не знает о часовых поясах в Dates (потому что их вообще нет), но на самом деле это JDBC-слой, который вызывает проблемы. ResultSet.getTimestamp
и PreparedStatement.setTimestamp
оба говорят в своих документах, что они по умолчанию преобразовывают даты в / из текущего часового пояса JVM при чтении и записи из / в базу данных.
Я нашел решение этой проблемы в Hibernate 3.5, создав подкласс org.hibernate.type.TimestampType
, который заставляет эти методы JDBC использовать UTC вместо местного часового пояса:
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, если вы используете эти типы. Недостатком является то, что вам придется вручную указывать, что эти типы должны использоваться вместо значений по умолчанию в каждом поле Date в ваших 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, все метки времени будут сохраняться и обрабатываться как UTC без необходимости изменения аннотаций в POJO. [Я еще не проверял это]