HHH-10399 - Add support for specifying TimeZone for ZonedDateTime type

Add documentation and supply more tests
This commit is contained in:
Vlad Mihalcea 2016-09-12 17:59:02 +03:00
parent 553942d2f9
commit 62e499538c
6 changed files with 116 additions and 69 deletions

View File

@ -228,6 +228,7 @@ Default is `hbm,class"` which indicates to process `hbm.xml` files followed by a
3+|JDBC-related options
|`hibernate.use_nationalized_character_data` |`true` or `false` (default value) |Enable nationalized character support on all string / clob based attribute ( string, char, clob, text etc ).
|`hibernate.jdbc.lob.non_contextual_creation` |`true` or `false` (default value) |Should we not use contextual LOB creation (aka based on `java.sql.Connection#createBlob()` et al)? The default value for HANA, H2, and PostgreSQL is `true`.
|`hibernate.jdbc.time_zone` | A `java.util.TimeZone`, a `java.time.ZoneId` or a `String` representation of a `ZoneId` |Unless specified, the JDBC Driver uses the default JVM time zone. If a different time zone is configured via this setting, the JDBC https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setTimestamp-int-java.sql.Timestamp-java.util.Calendar-[PreparedStatement#setTimestamp] is going to use a `Calendar` instance according to the specified time zone.
3+|Bean Validation options
|`javax.persistence.validation.factory` |`javax.validation.ValidationFactory` implementation | Specify the `javax.validation.ValidationFactory` implementation to use for Bean Validation.

View File

@ -962,6 +962,57 @@ org.hibernate.AnnotationException: @Temporal should only be set on a java.util.D
----
====
[[basic-datetime-time-zone]]
===== Using a specific time zone
By default, Hibernate is going to use the https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setTimestamp-int-java.sql.Timestamp-[`PreparedStatement.setTimestamp(int parameterIndex, java.sql.Timestamp)`] or
https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setTime-int-java.sql.Time-[`PreparedStatement.setTime(int parameterIndex, java.sql.Time x)`] when saving a `java.sql.Timestamp` or a `java.sql.Time` property.
When the time zone is not specified, the JDBC driver is going to use the underlying JVM default time zone, which might not be suitable if the application is used from all across the globe.
For this reason, it is very common to use a single reference time zone (e.g. UTC) whenever saving/loading data from the database.
One alternative would be to configure all JVMs to use the reference time zone:
Declaratively::
+
[source,java]
----
java -Duser.timezone=UTC ...
----
Programmatically::
+
[source,java]
----
TimeZone.setDefault( TimeZone.getTimeZone( "UTC" ) );
----
However, as explained in http://in.relation.to/2016/09/12/jdbc-time-zone-configuration-property/[this article], this is not always practical especially for front-end nodes.
For this reason, Hibernate offers the `hibernate.jdbc.time_zone` configuration property which can be configured:
Declaratively, at the `SessionFactory` level::
+
[source,java]
----
settings.put(
AvailableSettings.JDBC_TIME_ZONE,
TimeZone.getTimeZone( "UTC" )
);
----
Programmatically, on a per `Session` basis::
+
[source,java]
----
Session session = sessionFactory()
.withOptions()
.jdbcTimeZone( TimeZone.getTimeZone( "UTC" ) )
.openSession();
----
With this configuration property in place, Hibernate is going to call the https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setTimestamp-int-java.sql.Timestamp-java.util.Calendar-[`PreparedStatement.setTimestamp(int parameterIndex, java.sql.Timestamp, Calendar cal)`] or
https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setTime-int-java.sql.Time-java.util.Calendar-[`PreparedStatement.setTime(int parameterIndex, java.sql.Time x, Calendar cal)`], where the `java.util.Calendar` references the time zone provided via the `hibernate.jdbc.time_zone` property.
[[basic-jpa-convert]]
==== JPA 2.1 AttributeConverters

View File

@ -711,7 +711,7 @@ public interface AvailableSettings {
String BATCH_VERSIONED_DATA = "hibernate.jdbc.batch_versioned_data";
/**
* Default JDBC TimeZone.
* Default JDBC TimeZone. Unless specified, the JVM default TimeZone is going to be used by the underlying JDBC Driver.
*/
String JDBC_TIME_ZONE = "hibernate.jdbc.time_zone";

View File

@ -6,43 +6,23 @@
*/
package org.hibernate.test.timestamp;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Map;
import java.util.TimeZone;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.PostgreSQL82Dialect;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertEquals;
import org.hibernate.test.util.jdbc.TimeZoneConnectionProvider;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect( value = PostgreSQL82Dialect.class)
public class JdbcTimestampUTCTimeZoneTest
extends BaseNonConfigCoreFunctionalTestCase {
extends JdbcTimestampWithoutUTCTimeZoneTest {
private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider();
private TimeZoneConnectionProvider connectionProvider = new TimeZoneConnectionProvider( "America/Los_Angeles" );
private static final TimeZone TIME_ZONE = TimeZone.getTimeZone( "UTC" );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class
};
}
@Override
protected void addSettings(Map settings) {
settings.put(
@ -61,44 +41,8 @@ public class JdbcTimestampUTCTimeZoneTest
connectionProvider.stop();
}
@Test
public void testTimeZone() {
connectionProvider.clear();
doInHibernate( this::sessionFactory, s -> {
Person person = new Person();
person.id = 1L;
//Y2K
person.createdOn = new Timestamp(946684800000L);
s.persist( person );
} );
doInHibernate( this::sessionFactory, s -> {
Person person = s.find( Person.class, 1L );
assertEquals( 946684800000L, person.createdOn.getTime() );
s.doWork( connection -> {
try (Statement st = connection.createStatement()) {
try (ResultSet rs = st.executeQuery(
"SELECT " +
" to_char(createdon, 'YYYY-MM-DD HH24:MI:SS.US') " +
"FROM person" )) {
while ( rs.next() ) {
String timestamp = rs.getString( 1 );
assertEquals("2000-01-01 00:00:00.000000", timestamp);
}
}
}
} );
} );
}
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
private Timestamp createdOn;
protected String expectedTimestampValue() {
return "2000-01-01 00:00:00.000000";
}
}

View File

@ -0,0 +1,41 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.timestamp;
import java.util.Map;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.test.util.jdbc.TimeZoneConnectionProvider;
/**
* @author Vlad Mihalcea
*/
public class JdbcTimestampWithDefaultUTCTimeZoneTest
extends JdbcTimestampWithoutUTCTimeZoneTest {
private TimeZoneConnectionProvider connectionProvider = new TimeZoneConnectionProvider( "UTC" );
@Override
protected void addSettings(Map settings) {
settings.put(
AvailableSettings.CONNECTION_PROVIDER,
connectionProvider
);
}
@Override
protected void releaseResources() {
super.releaseResources();
connectionProvider.stop();
}
protected String expectedTimestampValue() {
return "2000-01-01 00:00:00.000000";
}
}

View File

@ -9,6 +9,8 @@ package org.hibernate.test.timestamp;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Map;
import javax.persistence.Entity;
import javax.persistence.Id;
@ -56,24 +58,28 @@ public class JdbcTimestampWithoutUTCTimeZoneTest
@Test
public void testTimeZone() {
doInHibernate( this::sessionFactory, s -> {
doInHibernate( this::sessionFactory, session -> {
Person person = new Person();
person.id = 1L;
//Y2K
person.createdOn = new Timestamp(946684800000L);
s.persist( person );
long y2kMillis = LocalDateTime.of( 2000, 1, 1, 0, 0, 0 )
.atZone( ZoneId.of( "UTC" ) )
.toInstant()
.toEpochMilli();
assertEquals(946684800000L, y2kMillis);
person.createdOn = new Timestamp(y2kMillis);
session.persist( person );
} );
doInHibernate( this::sessionFactory, s -> {
s.doWork( connection -> {
try (Statement st = connection.createStatement()) {
try (ResultSet rs = st.executeQuery(
"SELECT " +
" to_char(createdon, 'YYYY-MM-DD HH24:MI:SS.US') " +
"SELECT to_char(createdon, 'YYYY-MM-DD HH24:MI:SS.US') " +
"FROM person" )) {
while ( rs.next() ) {
String timestamp = rs.getString( 1 );
assertEquals("1999-12-31 16:00:00.000000", timestamp);
assertEquals(expectedTimestampValue(), timestamp);
}
}
}
@ -81,6 +87,10 @@ public class JdbcTimestampWithoutUTCTimeZoneTest
} );
}
protected String expectedTimestampValue() {
return "1999-12-31 16:00:00.000000";
}
@Entity(name = "Person")
public static class Person {