HHH-10399 - Add support for specifying TimeZone for ZonedDateTime type
Add documentation and supply more tests
This commit is contained in:
parent
553942d2f9
commit
62e499538c
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
||||
|
|
Loading…
Reference in New Issue