Hibernate mengabaikan hal-hal zona waktu di Tanggal (karena tidak ada), tetapi sebenarnya lapisan JDBC yang menyebabkan masalah. ResultSet.getTimestamp
dan PreparedStatement.setTimestamp
keduanya mengatakan di dokumen mereka bahwa mereka mengubah tanggal ke / dari zona waktu JVM saat ini secara default saat membaca dan menulis dari / ke database.
Saya menemukan solusi untuk ini di Hibernate 3.5 dengan subclassing org.hibernate.type.TimestampType
yang memaksa metode JDBC ini untuk menggunakan UTC, bukan zona waktu lokal:
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));
}
}
Hal yang sama harus dilakukan untuk memperbaiki TimeType dan DateType jika Anda menggunakan tipe tersebut. Kelemahannya adalah Anda harus menentukan secara manual bahwa tipe ini akan digunakan sebagai ganti default pada setiap bidang Tanggal di POJO Anda (dan juga merusak kompatibilitas JPA murni), kecuali seseorang mengetahui metode penggantian yang lebih umum.
UPDATE: Hibernate 3.6 telah mengubah jenis API. Di 3.6, saya menulis kelas UtcTimestampTypeDescriptor untuk mengimplementasikan ini.
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 );
}
};
}
}
Sekarang, saat aplikasi dimulai, jika Anda menyetel TimestampTypeDescriptor.INSTANCE ke instance UtcTimestampTypeDescriptor, semua stempel waktu akan disimpan dan diperlakukan sebagai dalam UTC tanpa harus mengubah anotasi pada POJO. [Saya belum menguji ini]