From 30e50a979cc945df1ac2de06f36bcdc1ad9d656f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 11 Mar 2019 16:32:44 +0100 Subject: [PATCH] HHH-13266 Add an abstract class for all java.time tests for this ticket So that we can hopefully factorize the upcoming additions. --- .../test/type/AbstractJavaTimeTypeTest.java | 222 +++++++++++++ .../org/hibernate/test/type/InstantTest.java | 214 ++++--------- .../hibernate/test/type/LocalDateTest.java | 187 +++-------- .../test/type/LocalDateTimeTest.java | 208 ++++--------- .../test/type/OffsetDateTimeTest.java | 266 +++++----------- .../test/type/ZonedDateTimeTest.java | 294 ++++++------------ 6 files changed, 547 insertions(+), 844 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java new file mode 100644 index 0000000000..1b1315cc4e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java @@ -0,0 +1,222 @@ +/* + * 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.type; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.TimeZone; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.PostgreSQL81Dialect; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.junit4.CustomParameterized; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for storage of Instant properties. + * + * @param The time type being tested. + * @param The entity type used in tests. + */ +@RunWith(CustomParameterized.class) +abstract class AbstractJavaTimeTypeTest extends BaseCoreFunctionalTestCase { + + private static Dialect determineDialect() { + try { + return Dialect.getDialect(); + } + catch (Exception e) { + return new Dialect() { + }; + } + } + + protected static final String ENTITY_NAME = "theentity"; + protected static final String ID_COLUMN_NAME = "theid"; + protected static final String PROPERTY_COLUMN_NAME = "thevalue"; + + protected static final ZoneId ZONE_UTC_MINUS_8 = ZoneId.of( "UTC-8" ); + protected static final ZoneId ZONE_PARIS = ZoneId.of( "Europe/Paris" ); + protected static final ZoneId ZONE_GMT = ZoneId.of( "GMT" ); + protected static final ZoneId ZONE_OSLO = ZoneId.of( "Europe/Oslo" ); + protected static final ZoneId ZONE_AMSTERDAM = ZoneId.of( "Europe/Amsterdam" ); + + private final EnvironmentParameters env; + + public AbstractJavaTimeTypeTest(EnvironmentParameters env) { + this.env = env; + } + + @Override + protected final Class[] getAnnotatedClasses() { + return new Class[] { getEntityType() }; + } + + protected abstract Class getEntityType(); + + protected abstract E createEntity(int id); + + protected abstract T getExpectedPropertyValue(); + + protected abstract T getActualPropertyValue(E entity); + + protected abstract Object getExpectedJdbcValue(); + + protected abstract Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException; + + @Before + public void cleanup() { + inTransaction( session -> { + session.createNativeQuery( "DELETE FROM " + ENTITY_NAME ).executeUpdate(); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13266") + public void writeThenRead() { + withDefaultTimeZone( () -> { + inTransaction( session -> { + session.persist( createEntity( 1 ) ); + } ); + inTransaction( session -> { + T read = getActualPropertyValue( session.find( getEntityType(), 1 ) ); + assertEquals( + "Writing then reading a value should return the original value", + getExpectedPropertyValue(), read + ); + } ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13266") + public void writeThenNativeRead() { + withDefaultTimeZone( () -> { + inTransaction( session -> { + session.persist( createEntity( 1 ) ); + } ); + inTransaction( session -> { + session.doWork( connection -> { + final PreparedStatement statement = connection.prepareStatement( + "SELECT " + PROPERTY_COLUMN_NAME + " FROM " + ENTITY_NAME + " WHERE " + ID_COLUMN_NAME + " = ?" + ); + statement.setInt( 1, 1 ); + statement.execute(); + final ResultSet resultSet = statement.getResultSet(); + resultSet.next(); + Object nativeRead = getActualJdbcValue( resultSet, 1 ); + assertEquals( + "Values written by Hibernate ORM should match the original value (same day, hour, ...)", + getExpectedJdbcValue(), + nativeRead + ); + } ); + } ); + } ); + } + + protected final void withDefaultTimeZone(Runnable runnable) { + TimeZone timeZoneBefore = TimeZone.getDefault(); + TimeZone.setDefault( TimeZone.getTimeZone( env.defaultJvmTimeZone ) ); + /* + * Run the code in a new thread, because some libraries (looking at you, h2 JDBC driver) + * cache data dependent on the default timezone in thread local variables, + * and we want this data to be reinitialized with the new default time zone. + */ + try { + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future future = executor.submit( runnable ); + executor.shutdown(); + future.get(); + } + catch (InterruptedException e) { + throw new IllegalStateException( "Interrupted while testing", e ); + } + catch (ExecutionException e) { + Throwable cause = e.getCause(); + if ( cause instanceof RuntimeException ) { + throw (RuntimeException) cause; + } + else if ( cause instanceof Error ) { + throw (Error) cause; + } + else { + throw new IllegalStateException( "Unexpected exception while testing", cause ); + } + } + finally { + TimeZone.setDefault( timeZoneBefore ); + } + } + + protected static abstract class AbstractParametersBuilder> { + + private final Dialect dialect; + + private final List result = new ArrayList<>(); + + protected AbstractParametersBuilder() { + dialect = determineDialect(); + } + + protected final boolean isNanosecondPrecisionSupported() { + // PostgreSQL apparently doesn't support nanosecond precision correctly + return !( dialect instanceof PostgreSQL81Dialect ); + } + + protected final S add(ZoneId defaultJvmTimeZone, Object ... subClassParameters) { + List parameters = new ArrayList<>(); + parameters.add( new EnvironmentParameters( defaultJvmTimeZone ) ); + Collections.addAll( parameters, subClassParameters ); + result.add( parameters.toArray() ); + return thisAsS(); + } + + private S thisAsS() { + return (S) this; + } + + public List build() { + return result; + } + + } + + protected final static class EnvironmentParameters { + + /* + * The default timezone affects conversions done using java.util, + * which is why we take it into account even with timezone-independent types such as Instant. + */ + private final ZoneId defaultJvmTimeZone; + + private EnvironmentParameters(ZoneId defaultJvmTimeZone) { + this.defaultJvmTimeZone = defaultJvmTimeZone; + } + + @Override + public String toString() { + return String.format( "[JVM TZ: %s]", defaultJvmTimeZone ); + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java index a638d96c79..8bd4a95926 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java @@ -6,91 +6,57 @@ */ package org.hibernate.test.type; -import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.SQLException; import java.sql.Timestamp; import java.time.Instant; import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.ZoneOffset; -import java.util.Arrays; import java.util.List; -import java.util.TimeZone; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; -import javax.persistence.Table; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.PostgreSQL81Dialect; - -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.hibernate.testing.junit4.CustomParameterized; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import static org.junit.Assert.assertEquals; - /** * Tests for storage of Instant properties. */ -@RunWith(CustomParameterized.class) -public class InstantTest extends BaseCoreFunctionalTestCase { +public class InstantTest extends AbstractJavaTimeTypeTest { - private static Dialect DIALECT; - private static Dialect determineDialect() { - try { - return Dialect.getDialect(); - } - catch (Exception e) { - return new Dialect() { - }; + private static class ParametersBuilder extends AbstractParametersBuilder { + public ParametersBuilder add(int year, int month, int day, + int hour, int minute, int second, int nanosecond, ZoneId defaultTimeZone) { + if ( !isNanosecondPrecisionSupported() ) { + nanosecond = 0; + } + return add( defaultTimeZone, year, month, day, hour, minute, second, nanosecond ); } } - /* - * The default timezone affects conversions done using java.util, - * which is why we take it into account even when testing Instant. - */ - @Parameterized.Parameters(name = "{0}-{1}-{2}T{3}:{4}:{5}.{6}Z [JVM TZ: {7}]") + @Parameterized.Parameters(name = "{1}-{2}-{3}T{4}:{5}:{6}.{7}Z {0}") public static List data() { - DIALECT = determineDialect(); - return Arrays.asList( + return new ParametersBuilder() // Not affected by HHH-13266 (JDK-8061577) - data( 2017, 11, 6, 19, 19, 1, 0, ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 500, ZoneId.of( "Europe/Paris" ) ), - data( 1970, 1, 1, 0, 0, 0, 0, ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Oslo" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 2, 0, 9, 21, 0, ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Amsterdam" ) ), - data( 1900, 1, 2, 0, 19, 32, 0, ZoneId.of( "Europe/Amsterdam" ) ), + .add( 2017, 11, 6, 19, 19, 1, 0, ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 500, ZONE_PARIS ) + .add( 1970, 1, 1, 0, 0, 0, 0, ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) + .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_PARIS ) + .add( 1900, 1, 2, 0, 9, 21, 0, ZONE_PARIS ) + .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) + .add( 1900, 1, 2, 0, 19, 32, 0, ZONE_AMSTERDAM ) // Affected by HHH-13266 (JDK-8061577) - data( 1892, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Oslo" ) ), - data( 1899, 12, 31, 23, 59, 59, 999_999_999, ZoneId.of( "Europe/Paris" ) ), - data( 1899, 12, 31, 23, 59, 59, 999_999_999, ZoneId.of( "Europe/Amsterdam" ) ), - data( 1600, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Amsterdam" ) ) - ); - } - - private static Object[] data(int year, int month, int day, - int hour, int minute, int second, int nanosecond, ZoneId defaultTimeZone) { - if ( DIALECT instanceof PostgreSQL81Dialect ) { - // PostgreSQL apparently doesn't support nanosecond precision correctly - nanosecond = 0; - } - return new Object[] { year, month, day, hour, minute, second, nanosecond, defaultTimeZone }; + .add( 1892, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) + .add( 1899, 12, 31, 23, 59, 59, 999_999_999, ZONE_PARIS ) + .add( 1899, 12, 31, 23, 59, 59, 999_999_999, ZONE_AMSTERDAM ) + .add( 1600, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) + .build(); } private final int year; @@ -100,10 +66,10 @@ private static Object[] data(int year, int month, int day, private final int minute; private final int second; private final int nanosecond; - private final ZoneId defaultTimeZone; - public InstantTest(int year, int month, int day, - int hour, int minute, int second, int nanosecond, ZoneId defaultTimeZone) { + public InstantTest(EnvironmentParameters env, int year, int month, int day, + int hour, int minute, int second, int nanosecond) { + super( env ); this.year = year; this.month = month; this.day = day; @@ -111,15 +77,31 @@ public InstantTest(int year, int month, int day, this.minute = minute; this.second = second; this.nanosecond = nanosecond; - this.defaultTimeZone = defaultTimeZone; } - private Instant getExpectedInstant() { + @Override + protected Class getEntityType() { + return EntityWithInstant.class; + } + + @Override + protected EntityWithInstant createEntity(int id) { + return new EntityWithInstant( id, getExpectedPropertyValue() ); + } + + @Override + protected Instant getExpectedPropertyValue() { return OffsetDateTime.of( year, month, day, hour, minute, second, nanosecond, ZoneOffset.UTC ).toInstant(); } - private Timestamp getExpectedTimestamp() { - LocalDateTime dateTimeInDefaultTimeZone = getExpectedInstant().atZone( ZoneId.systemDefault() ) + @Override + protected Instant getActualPropertyValue(EntityWithInstant entity) { + return entity.value; + } + + @Override + protected Object getExpectedJdbcValue() { + LocalDateTime dateTimeInDefaultTimeZone = getExpectedPropertyValue().atZone( ZoneId.systemDefault() ) .toLocalDateTime(); return new Timestamp( dateTimeInDefaultTimeZone.getYear() - 1900, dateTimeInDefaultTimeZone.getMonthValue() - 1, @@ -131,104 +113,18 @@ private Timestamp getExpectedTimestamp() { } @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { EntityWithInstant.class }; + protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException { + return resultSet.getTimestamp( columnIndex ); } - @Before - public void cleanup() { - inTransaction( session -> { - session.createNativeQuery( "DELETE FROM theentity" ).executeUpdate(); - } ); - } - - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithInstant( 1, getExpectedInstant() ) ); - } ); - inTransaction( session -> { - Instant read = session.find( EntityWithInstant.class, 1 ).value; - assertEquals( - "Writing then reading a value should return the original value", - getExpectedInstant(), read - ); - } ); - } ); - } - - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenNativeRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithInstant( 1, getExpectedInstant() ) ); - } ); - inTransaction( session -> { - session.doWork( connection -> { - final PreparedStatement statement = connection.prepareStatement( - "SELECT thevalue FROM theentity WHERE theid = ?" - ); - statement.setInt( 1, 1 ); - statement.execute(); - final ResultSet resultSet = statement.getResultSet(); - resultSet.next(); - Timestamp nativeRead = resultSet.getTimestamp( 1 ); - assertEquals( - "Raw values written in database should match the original value (same day, hour, ...)", - getExpectedTimestamp(), - nativeRead - ); - } ); - } ); - } ); - } - - private static void withDefaultTimeZone(ZoneId zoneId, Runnable runnable) { - TimeZone timeZoneBefore = TimeZone.getDefault(); - TimeZone.setDefault( TimeZone.getTimeZone( zoneId ) ); - /* - * Run the code in a new thread, because some libraries (looking at you, h2 JDBC driver) - * cache data dependent on the default timezone in thread local variables, - * and we want this data to be reinitialized with the new default time zone. - */ - try { - ExecutorService executor = Executors.newSingleThreadExecutor(); - Future future = executor.submit( runnable ); - executor.shutdown(); - future.get(); - } - catch (InterruptedException e) { - throw new IllegalStateException( "Interrupted while testing", e ); - } - catch (ExecutionException e) { - Throwable cause = e.getCause(); - if ( cause instanceof RuntimeException ) { - throw (RuntimeException) cause; - } - else if ( cause instanceof Error ) { - throw (Error) cause; - } - else { - throw new IllegalStateException( "Unexpected exception while testing", cause ); - } - } - finally { - TimeZone.setDefault( timeZoneBefore ); - } - } - - @Entity - @Table(name = "theentity") - private static final class EntityWithInstant { + @Entity(name = ENTITY_NAME) + static final class EntityWithInstant { @Id - @Column(name = "theid") + @Column(name = ID_COLUMN_NAME) private Integer id; @Basic - @Column(name = "thevalue") + @Column(name = PROPERTY_COLUMN_NAME) private Instant value; protected EntityWithInstant() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java index 4d0393d6cb..bb3e023eec 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java @@ -7,186 +7,99 @@ package org.hibernate.test.type; import java.sql.Date; -import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.SQLException; import java.time.LocalDate; import java.time.ZoneId; -import java.util.Arrays; import java.util.List; -import java.util.TimeZone; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; -import javax.persistence.Table; import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.hibernate.testing.junit4.CustomParameterized; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import static org.junit.Assert.assertEquals; - /** * Tests for storage of LocalDate properties. */ -@RunWith(CustomParameterized.class) @TestForIssue(jiraKey = "HHH-10371") -public class LocalDateTest extends BaseCoreFunctionalTestCase { +public class LocalDateTest extends AbstractJavaTimeTypeTest { - /* - * The default timezone affects conversions done using java.util, - * which is why we take it into account even when testing LocalDateTime. - */ - @Parameterized.Parameters(name = "{0}-{1}-{2} [JVM TZ: {3}]") - public static List data() { - return Arrays.asList( - // Not affected by HHH-13266 (JDK-8061577) - data( 2017, 11, 6, ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, ZoneId.of( "Europe/Paris" ) ), - data( 1970, 1, 1, ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, ZoneId.of( "Europe/Oslo" ) ), - data( 1900, 1, 2, ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 2, ZoneId.of( "Europe/Amsterdam" ) ), - // Could have been affected by HHH-13266 (JDK-8061577), but was not - data( 1892, 1, 1, ZoneId.of( "Europe/Oslo" ) ), - data( 1900, 1, 1, ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, ZoneId.of( "Europe/Amsterdam" ) ), - data( 1600, 1, 1, ZoneId.of( "Europe/Amsterdam" ) ) - ); + private static class ParametersBuilder extends AbstractParametersBuilder { + public ParametersBuilder add(int year, int month, int day, ZoneId defaultTimeZone) { + return add( defaultTimeZone, year, month, day ); + } } - private static Object[] data(int year, int month, int day, ZoneId defaultTimeZone) { - return new Object[] { year, month, day, defaultTimeZone }; + @Parameterized.Parameters(name = "{1}-{2}-{3} {0}") + public static List data() { + return new ParametersBuilder() + // Not affected by HHH-13266 (JDK-8061577) + .add( 2017, 11, 6, ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, ZONE_PARIS ) + .add( 1970, 1, 1, ZONE_GMT ) + .add( 1900, 1, 1, ZONE_GMT ) + .add( 1900, 1, 1, ZONE_OSLO ) + .add( 1900, 1, 2, ZONE_PARIS ) + .add( 1900, 1, 2, ZONE_AMSTERDAM ) + // Could have been affected by HHH-13266 (JDK-8061577), but was not + .add( 1892, 1, 1, ZONE_OSLO ) + .add( 1900, 1, 1, ZONE_PARIS ) + .add( 1900, 1, 1, ZONE_AMSTERDAM ) + .add( 1600, 1, 1, ZONE_AMSTERDAM ) + .build(); } private final int year; private final int month; private final int day; - private final ZoneId defaultTimeZone; - public LocalDateTest(int year, int month, int day, ZoneId defaultTimeZone) { + public LocalDateTest(EnvironmentParameters env, int year, int month, int day) { + super( env ); this.year = year; this.month = month; this.day = day; - this.defaultTimeZone = defaultTimeZone; } - private LocalDate getExpectedLocalDate() { + @Override + protected Class getEntityType() { + return EntityWithLocalDate.class; + } + + @Override + protected EntityWithLocalDate createEntity(int id) { + return new EntityWithLocalDate( id, getExpectedPropertyValue() ); + } + + @Override + protected LocalDate getExpectedPropertyValue() { return LocalDate.of( year, month, day ); } - private Date getExpectedSqlDate() { + @Override + protected LocalDate getActualPropertyValue(EntityWithLocalDate entity) { + return entity.value; + } + + @Override + protected Object getExpectedJdbcValue() { return new Date( year - 1900, month - 1, day ); } @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { EntityWithLocalDate.class }; + protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException { + return resultSet.getDate( columnIndex ); } - @Before - public void cleanup() { - inTransaction( session -> { - session.createNativeQuery( "DELETE FROM theentity" ).executeUpdate(); - } ); - } - - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithLocalDate( 1, getExpectedLocalDate() ) ); - } ); - inTransaction( session -> { - LocalDate read = session.find( EntityWithLocalDate.class, 1 ).value; - assertEquals( - "Writing then reading a value should return the original value", - getExpectedLocalDate(), read - ); - } ); - } ); - } - - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenNativeRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithLocalDate( 1, getExpectedLocalDate() ) ); - } ); - inTransaction( session -> { - session.doWork( connection -> { - final PreparedStatement statement = connection.prepareStatement( - "SELECT thevalue FROM theentity WHERE theid = ?" - ); - statement.setInt( 1, 1 ); - statement.execute(); - final ResultSet resultSet = statement.getResultSet(); - resultSet.next(); - Date nativeRead = resultSet.getDate( 1 ); - assertEquals( - "Raw values written in database should match the original value (same day, hour, ...)", - getExpectedSqlDate(), - nativeRead - ); - } ); - } ); - } ); - } - - private static void withDefaultTimeZone(ZoneId zoneId, Runnable runnable) { - TimeZone timeZoneBefore = TimeZone.getDefault(); - TimeZone.setDefault( TimeZone.getTimeZone( zoneId ) ); - /* - * Run the code in a new thread, because some libraries (looking at you, h2 JDBC driver) - * cache data dependent on the default timezone in thread local variables, - * and we want this data to be reinitialized with the new default time zone. - */ - try { - ExecutorService executor = Executors.newSingleThreadExecutor(); - Future future = executor.submit( runnable ); - executor.shutdown(); - future.get(); - } - catch (InterruptedException e) { - throw new IllegalStateException( "Interrupted while testing", e ); - } - catch (ExecutionException e) { - Throwable cause = e.getCause(); - if ( cause instanceof RuntimeException ) { - throw (RuntimeException) cause; - } - else if ( cause instanceof Error ) { - throw (Error) cause; - } - else { - throw new IllegalStateException( "Unexpected exception while testing", cause ); - } - } - finally { - TimeZone.setDefault( timeZoneBefore ); - } - } - - @Entity - @Table(name = "theentity") - private static final class EntityWithLocalDate { + @Entity(name = ENTITY_NAME) + static final class EntityWithLocalDate { @Id - @Column(name = "theid") + @Column(name = ID_COLUMN_NAME) private Integer id; @Basic - @Column(name = "thevalue") + @Column(name = PROPERTY_COLUMN_NAME) private LocalDate value; protected EntityWithLocalDate() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java index a0543bf192..a2827ae2d0 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java @@ -6,86 +6,52 @@ */ package org.hibernate.test.type; -import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.SQLException; import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.ZoneId; -import java.util.Arrays; import java.util.List; -import java.util.TimeZone; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; -import javax.persistence.Table; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.PostgreSQL81Dialect; - -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.hibernate.testing.junit4.CustomParameterized; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import static org.junit.Assert.assertEquals; - /** * Tests for storage of LocalDateTime properties. */ -@RunWith(CustomParameterized.class) -public class LocalDateTimeTest extends BaseCoreFunctionalTestCase { +public class LocalDateTimeTest extends AbstractJavaTimeTypeTest { - private static Dialect DIALECT; - private static Dialect determineDialect() { - try { - return Dialect.getDialect(); - } - catch (Exception e) { - return new Dialect() { - }; + private static class ParametersBuilder extends AbstractParametersBuilder { + public ParametersBuilder add(int year, int month, int day, + int hour, int minute, int second, int nanosecond, ZoneId defaultTimeZone) { + if ( !isNanosecondPrecisionSupported() ) { + nanosecond = 0; + } + return add( defaultTimeZone, year, month, day, hour, minute, second, nanosecond ); } } - /* - * The default timezone affects conversions done using java.util, - * which is why we take it into account even when testing LocalDateTime. - */ - @Parameterized.Parameters(name = "{0}-{1}-{2}T{3}:{4}:{5}.{6} [JVM TZ: {7}]") + @Parameterized.Parameters(name = "{1}-{2}-{3}T{4}:{5}:{6}.{7} {0}") public static List data() { - DIALECT = determineDialect(); - return Arrays.asList( + return new ParametersBuilder() // Not affected by HHH-13266 (JDK-8061577) - data( 2017, 11, 6, 19, 19, 1, 0, ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 500, ZoneId.of( "Europe/Paris" ) ), - data( 1970, 1, 1, 0, 0, 0, 0, ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Oslo" ) ), - data( 1900, 1, 2, 0, 9, 21, 0, ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 2, 0, 19, 32, 0, ZoneId.of( "Europe/Amsterdam" ) ), + .add( 2017, 11, 6, 19, 19, 1, 0, ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 500, ZONE_PARIS ) + .add( 1970, 1, 1, 0, 0, 0, 0, ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) + .add( 1900, 1, 2, 0, 9, 21, 0, ZONE_PARIS ) + .add( 1900, 1, 2, 0, 19, 32, 0, ZONE_AMSTERDAM ) // Affected by HHH-13266 (JDK-8061577) - data( 1892, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Oslo" ) ), - data( 1900, 1, 1, 0, 9, 20, 0, ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 31, 0, ZoneId.of( "Europe/Amsterdam" ) ), - data( 1600, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Amsterdam" ) ) - ); - } - - private static Object[] data(int year, int month, int day, - int hour, int minute, int second, int nanosecond, ZoneId defaultTimeZone) { - if ( DIALECT instanceof PostgreSQL81Dialect ) { - // PostgreSQL apparently doesn't support nanosecond precision correctly - nanosecond = 0; - } - return new Object[] { year, month, day, hour, minute, second, nanosecond, defaultTimeZone }; + .add( 1892, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) + .add( 1900, 1, 1, 0, 9, 20, 0, ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 31, 0, ZONE_AMSTERDAM ) + .add( 1600, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) + .build(); } private final int year; @@ -95,10 +61,10 @@ private static Object[] data(int year, int month, int day, private final int minute; private final int second; private final int nanosecond; - private final ZoneId defaultTimeZone; - public LocalDateTimeTest(int year, int month, int day, - int hour, int minute, int second, int nanosecond, ZoneId defaultTimeZone) { + public LocalDateTimeTest(EnvironmentParameters env, int year, int month, int day, + int hour, int minute, int second, int nanosecond) { + super( env ); this.year = year; this.month = month; this.day = day; @@ -106,14 +72,30 @@ public LocalDateTimeTest(int year, int month, int day, this.minute = minute; this.second = second; this.nanosecond = nanosecond; - this.defaultTimeZone = defaultTimeZone; } - private LocalDateTime getExpectedLocalDateTime() { + @Override + protected Class getEntityType() { + return EntityWithLocalDateTime.class; + } + + @Override + protected EntityWithLocalDateTime createEntity(int id) { + return new EntityWithLocalDateTime( id, getExpectedPropertyValue() ); + } + + @Override + protected LocalDateTime getExpectedPropertyValue() { return LocalDateTime.of( year, month, day, hour, minute, second, nanosecond ); } - private Timestamp getExpectedTimestamp() { + @Override + protected LocalDateTime getActualPropertyValue(EntityWithLocalDateTime entity) { + return entity.value; + } + + @Override + protected Object getExpectedJdbcValue() { return new Timestamp( year - 1900, month - 1, day, hour, minute, second, nanosecond @@ -121,104 +103,18 @@ private Timestamp getExpectedTimestamp() { } @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { EntityWithLocalDateTime.class }; + protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException { + return resultSet.getTimestamp( columnIndex ); } - @Before - public void cleanup() { - inTransaction( session -> { - session.createNativeQuery( "DELETE FROM theentity" ).executeUpdate(); - } ); - } - - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithLocalDateTime( 1, getExpectedLocalDateTime() ) ); - } ); - inTransaction( session -> { - LocalDateTime read = session.find( EntityWithLocalDateTime.class, 1 ).value; - assertEquals( - "Writing then reading a value should return the original value", - getExpectedLocalDateTime(), read - ); - } ); - } ); - } - - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenNativeRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithLocalDateTime( 1, getExpectedLocalDateTime() ) ); - } ); - inTransaction( session -> { - session.doWork( connection -> { - final PreparedStatement statement = connection.prepareStatement( - "SELECT thevalue FROM theentity WHERE theid = ?" - ); - statement.setInt( 1, 1 ); - statement.execute(); - final ResultSet resultSet = statement.getResultSet(); - resultSet.next(); - Timestamp nativeRead = resultSet.getTimestamp( 1 ); - assertEquals( - "Raw values written in database should match the original value (same day, hour, ...)", - getExpectedTimestamp(), - nativeRead - ); - } ); - } ); - } ); - } - - private static void withDefaultTimeZone(ZoneId zoneId, Runnable runnable) { - TimeZone timeZoneBefore = TimeZone.getDefault(); - TimeZone.setDefault( TimeZone.getTimeZone( zoneId ) ); - /* - * Run the code in a new thread, because some libraries (looking at you, h2 JDBC driver) - * cache data dependent on the default timezone in thread local variables, - * and we want this data to be reinitialized with the new default time zone. - */ - try { - ExecutorService executor = Executors.newSingleThreadExecutor(); - Future future = executor.submit( runnable ); - executor.shutdown(); - future.get(); - } - catch (InterruptedException e) { - throw new IllegalStateException( "Interrupted while testing", e ); - } - catch (ExecutionException e) { - Throwable cause = e.getCause(); - if ( cause instanceof RuntimeException ) { - throw (RuntimeException) cause; - } - else if ( cause instanceof Error ) { - throw (Error) cause; - } - else { - throw new IllegalStateException( "Unexpected exception while testing", cause ); - } - } - finally { - TimeZone.setDefault( timeZoneBefore ); - } - } - - @Entity - @Table(name = "theentity") - private static final class EntityWithLocalDateTime { + @Entity(name = ENTITY_NAME) + static final class EntityWithLocalDateTime { @Id - @Column(name = "theid") + @Column(name = ID_COLUMN_NAME) private Integer id; @Basic - @Column(name = "thevalue") + @Column(name = PROPERTY_COLUMN_NAME) private LocalDateTime value; protected EntityWithLocalDateTime() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java index 76ebb98a21..59fecaa6db 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java @@ -6,116 +6,84 @@ */ package org.hibernate.test.type; -import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.SQLException; import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.ZoneOffset; -import java.util.Arrays; -import java.util.GregorianCalendar; import java.util.List; -import java.util.TimeZone; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.function.Consumer; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; -import javax.persistence.Table; import org.hibernate.Query; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.type.OffsetDateTimeType; import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; -import org.hibernate.testing.junit4.CustomParameterized; -import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; /** * @author Andrea Boriero */ -@RunWith(CustomParameterized.class) @TestForIssue(jiraKey = "HHH-10372") -public class OffsetDateTimeTest extends BaseNonConfigCoreFunctionalTestCase { +public class OffsetDateTimeTest extends AbstractJavaTimeTypeTest { - private static Dialect DIALECT; - private static Dialect determineDialect() { - try { - return Dialect.getDialect(); - } - catch (Exception e) { - return new Dialect() { - }; + private static class ParametersBuilder extends AbstractParametersBuilder { + public ParametersBuilder add(int year, int month, int day, + int hour, int minute, int second, int nanosecond, String offset, ZoneId defaultTimeZone) { + if ( !isNanosecondPrecisionSupported() ) { + nanosecond = 0; + } + return add( defaultTimeZone, year, month, day, hour, minute, second, nanosecond, offset ); } } - /* - * The default timezone affects conversions done using java.util, - * which is why we take it into account even when testing OffsetDateTime. - */ - @Parameterized.Parameters(name = "{0}-{1}-{2}T{3}:{4}:{5}.{6}[{7}] [JVM TZ: {8}]") + @Parameterized.Parameters(name = "{1}-{2}-{3}T{4}:{5}:{6}.{7}[{8}] {0}") public static List data() { - DIALECT = determineDialect(); - return Arrays.asList( + return new ParametersBuilder() // Not affected by HHH-13266 - data( 2017, 11, 6, 19, 19, 1, 0, "+10:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "+07:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "+01:30", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "+01:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "+00:30", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "-02:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "-06:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "-08:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "+10:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "+07:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "+01:30", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 500, "+01:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "+01:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "+00:30", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "-02:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "-06:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "-08:00", ZoneId.of( "Europe/Paris" ) ), - data( 1970, 1, 1, 0, 0, 0, 0, "+01:00", ZoneId.of( "GMT" ) ), - data( 1970, 1, 1, 0, 0, 0, 0, "+00:00", ZoneId.of( "GMT" ) ), - data( 1970, 1, 1, 0, 0, 0, 0, "-01:00", ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, "+01:00", ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, "+00:00", ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, "-01:00", ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, "+00:00", ZoneId.of( "Europe/Oslo" ) ), - data( 1900, 1, 1, 0, 9, 21, 0, "+00:09:21", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 32, 0, "+00:19:32", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 32, 0, "+00:19:32", ZoneId.of( "Europe/Amsterdam" ) ), + .add( 2017, 11, 6, 19, 19, 1, 0, "+10:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "+07:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "+01:30", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "+01:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "+00:30", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "-02:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "-06:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "-08:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "+10:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "+07:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "+01:30", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 500, "+01:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "+01:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "+00:30", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "-02:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "-06:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "-08:00", ZONE_PARIS ) + .add( 1970, 1, 1, 0, 0, 0, 0, "+01:00", ZONE_GMT ) + .add( 1970, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_GMT ) + .add( 1970, 1, 1, 0, 0, 0, 0, "-01:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "+01:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "-01:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_OSLO ) + .add( 1900, 1, 1, 0, 9, 21, 0, "+00:09:21", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 32, 0, "+00:19:32", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 32, 0, "+00:19:32", ZONE_AMSTERDAM ) // Affected by HHH-13266 - data( 1892, 1, 1, 0, 0, 0, 0, "+00:00", ZoneId.of( "Europe/Oslo" ) ), - data( 1900, 1, 1, 0, 9, 20, 0, "+00:09:21", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZoneId.of( "Europe/Amsterdam" ) ), - data( 1600, 1, 1, 0, 0, 0, 0, "+00:19:32", ZoneId.of( "Europe/Amsterdam" ) ) - ); - } - - private static Object[] data(int year, int month, int day, - int hour, int minute, int second, int nanosecond, String offset, ZoneId defaultTimeZone) { - if ( DIALECT instanceof PostgreSQL81Dialect ) { - // PostgreSQL apparently doesn't support nanosecond precision correctly - nanosecond = 0; - } - return new Object[] { year, month, day, hour, minute, second, nanosecond, offset, defaultTimeZone }; + .add( 1892, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_OSLO ) + .add( 1900, 1, 1, 0, 9, 20, 0, "+00:09:21", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZONE_AMSTERDAM ) + .add( 1600, 1, 1, 0, 0, 0, 0, "+00:19:32", ZONE_AMSTERDAM ) + .build(); } private final int year; @@ -126,10 +94,10 @@ private static Object[] data(int year, int month, int day, private final int second; private final int nanosecond; private final String offset; - private final ZoneId defaultTimeZone; - public OffsetDateTimeTest(int year, int month, int day, - int hour, int minute, int second, int nanosecond, String offset, ZoneId defaultTimeZone) { + public OffsetDateTimeTest(EnvironmentParameters env, int year, int month, int day, + int hour, int minute, int second, int nanosecond, String offset) { + super( env ); this.year = year; this.month = month; this.day = day; @@ -138,18 +106,34 @@ public OffsetDateTimeTest(int year, int month, int day, this.second = second; this.nanosecond = nanosecond; this.offset = offset; - this.defaultTimeZone = defaultTimeZone; + } + + @Override + protected Class getEntityType() { + return EntityWithOffsetDateTime.class; + } + + @Override + protected EntityWithOffsetDateTime createEntity(int id) { + return new EntityWithOffsetDateTime( id, getOriginalOffsetDateTime() ); } private OffsetDateTime getOriginalOffsetDateTime() { return OffsetDateTime.of( year, month, day, hour, minute, second, nanosecond, ZoneOffset.of( offset ) ); } - private OffsetDateTime getExpectedOffsetDateTime() { + @Override + protected OffsetDateTime getExpectedPropertyValue() { return getOriginalOffsetDateTime().atZoneSameInstant( ZoneId.systemDefault() ).toOffsetDateTime(); } - private Timestamp getExpectedTimestamp() { + @Override + protected OffsetDateTime getActualPropertyValue(EntityWithOffsetDateTime entity) { + return entity.value; + } + + @Override + protected Object getExpectedJdbcValue() { LocalDateTime dateTimeInDefaultTimeZone = getOriginalOffsetDateTime().atZoneSameInstant( ZoneId.systemDefault() ) .toLocalDateTime(); return new Timestamp( @@ -162,129 +146,36 @@ private Timestamp getExpectedTimestamp() { } @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { EntityWithOffsetDateTime.class }; - } - - @Before - public void cleanup() { - inTransaction( session -> { - session.createNativeQuery( "DELETE FROM theentity" ).executeUpdate(); - } ); - } - - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithOffsetDateTime( 1, getOriginalOffsetDateTime() ) ); - } ); - inTransaction( session -> { - OffsetDateTime read = session.find( org.hibernate.test.type.OffsetDateTimeTest.EntityWithOffsetDateTime.class, 1 ).value; - assertEquals( - "Writing then reading a value should return the original value", - getExpectedOffsetDateTime(), read - ); - assertTrue( - getExpectedOffsetDateTime().isEqual( read ) - ); - assertEquals( - 0, - OffsetDateTimeType.INSTANCE.getComparator().compare( getExpectedOffsetDateTime(), read ) - ); - } ); - } ); - } - - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenNativeRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithOffsetDateTime( 1, getOriginalOffsetDateTime() ) ); - } ); - inTransaction( session -> { - session.doWork( connection -> { - final PreparedStatement statement = connection.prepareStatement( - "SELECT thevalue FROM theentity WHERE theid = ?" - ); - statement.setInt( 1, 1 ); - statement.execute(); - final ResultSet resultSet = statement.getResultSet(); - resultSet.next(); - Timestamp nativeRead = resultSet.getTimestamp( 1 ); - assertEquals( - "Raw values written in database should match the original value (same instant)", - getExpectedTimestamp(), - nativeRead - ); - } ); - } ); - } ); + protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException { + return resultSet.getTimestamp( columnIndex ); } @Test public void testRetrievingEntityByOffsetDateTime() { - withDefaultTimeZone( defaultTimeZone, () -> { + withDefaultTimeZone( () -> { inTransaction( session -> { session.persist( new EntityWithOffsetDateTime( 1, getOriginalOffsetDateTime() ) ); } ); Consumer checkOneMatch = expected -> inSession( s -> { - Query query = s.createQuery( "from EntityWithOffsetDateTime o where o.value = :date" ); + Query query = s.createQuery( "from " + ENTITY_NAME + " o where o.value = :date" ); query.setParameter( "date", expected, OffsetDateTimeType.INSTANCE ); List list = query.list(); assertThat( list.size(), is( 1 ) ); } ); checkOneMatch.accept( getOriginalOffsetDateTime() ); - checkOneMatch.accept( getExpectedOffsetDateTime() ); - checkOneMatch.accept( getExpectedOffsetDateTime().withOffsetSameInstant( ZoneOffset.UTC ) ); + checkOneMatch.accept( getExpectedPropertyValue() ); + checkOneMatch.accept( getExpectedPropertyValue().withOffsetSameInstant( ZoneOffset.UTC ) ); } ); } - private static void withDefaultTimeZone(ZoneId zoneId, Runnable runnable) { - TimeZone timeZoneBefore = TimeZone.getDefault(); - TimeZone.setDefault( TimeZone.getTimeZone( zoneId ) ); - /* - * Run the code in a new thread, because some libraries (looking at you, h2 JDBC driver) - * cache data dependent on the default timezone in thread local variables, - * and we want this data to be reinitialized with the new default time zone. - */ - try { - ExecutorService executor = Executors.newSingleThreadExecutor(); - Future future = executor.submit( runnable ); - executor.shutdown(); - future.get(); - } - catch (InterruptedException e) { - throw new IllegalStateException( "Interrupted while testing", e ); - } - catch (ExecutionException e) { - Throwable cause = e.getCause(); - if ( cause instanceof RuntimeException ) { - throw (RuntimeException) cause; - } - else if ( cause instanceof Error ) { - throw (Error) cause; - } - else { - throw new IllegalStateException( "Unexpected exception while testing", cause ); - } - } - finally { - TimeZone.setDefault( timeZoneBefore ); - } - } - - @Entity(name = "EntityWithOffsetDateTime") - @Table(name = "theentity") - private static final class EntityWithOffsetDateTime { + @Entity(name = ENTITY_NAME) + static final class EntityWithOffsetDateTime { @Id - @Column(name = "theid") + @Column(name = ID_COLUMN_NAME) private Integer id; @Basic - @Column(name = "thevalue") + @Column(name = PROPERTY_COLUMN_NAME) private OffsetDateTime value; protected EntityWithOffsetDateTime() { @@ -295,9 +186,4 @@ private EntityWithOffsetDateTime(int id, OffsetDateTime value) { this.value = value; } } - - @Override - protected boolean isCleanupTestDataRequired() { - return true; - } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java index 55680baaee..52d603c02a 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java @@ -6,127 +6,96 @@ */ package org.hibernate.test.type; -import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.SQLException; import java.sql.Timestamp; import java.time.LocalDateTime; -import java.time.ZonedDateTime; import java.time.ZoneId; import java.time.ZoneOffset; -import java.util.Arrays; +import java.time.ZonedDateTime; import java.util.List; -import java.util.TimeZone; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.function.Consumer; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; -import javax.persistence.Table; import org.hibernate.Query; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.type.ZonedDateTimeType; import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; -import org.hibernate.testing.junit4.CustomParameterized; -import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; /** * @author Andrea Boriero */ -@RunWith(CustomParameterized.class) @TestForIssue(jiraKey = "HHH-10372") -public class ZonedDateTimeTest extends BaseNonConfigCoreFunctionalTestCase { +public class ZonedDateTimeTest extends AbstractJavaTimeTypeTest { - private static Dialect DIALECT; - private static Dialect determineDialect() { - try { - return Dialect.getDialect(); - } - catch (Exception e) { - return new Dialect() { - }; + private static class ParametersBuilder extends AbstractParametersBuilder { + public ParametersBuilder add(int year, int month, int day, + int hour, int minute, int second, int nanosecond, String zone, ZoneId defaultTimeZone) { + if ( !isNanosecondPrecisionSupported() ) { + nanosecond = 0; + } + return add( defaultTimeZone, year, month, day, hour, minute, second, nanosecond, zone ); } } - /* - * The default timezone affects conversions done using java.util, - * which is why we take it into account even when testing ZonedDateTime. - */ - @Parameterized.Parameters(name = "{0}-{1}-{2}T{3}:{4}:{5}.{6}[{7}] [JVM TZ: {8}]") + @Parameterized.Parameters(name = "{1}-{2}-{3}T{4}:{5}:{6}.{7}[{8}] {0}") public static List data() { - DIALECT = determineDialect(); - return Arrays.asList( + return new ParametersBuilder() // Not affected by HHH-13266 - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+10:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+07:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:30", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "Europe/Paris", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "Europe/London", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+00:30", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT-02:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT-06:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT-08:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+10:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+07:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:30", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 500, "GMT+01:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "Europe/Paris", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "Europe/London", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+00:30", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT-02:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT-06:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT-08:00", ZoneId.of( "Europe/Paris" ) ), - data( 1970, 1, 1, 0, 0, 0, 0, "GMT+01:00", ZoneId.of( "GMT" ) ), - data( 1970, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZoneId.of( "GMT" ) ), - data( 1970, 1, 1, 0, 0, 0, 0, "GMT-01:00", ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, "GMT+01:00", ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, "GMT-01:00", ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZoneId.of( "Europe/Oslo" ) ), - data( 1900, 1, 1, 0, 9, 21, 0, "GMT+00:09:21", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 9, 21, 0, "Europe/Paris", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 31, 0, "Europe/Paris", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 32, 0, "GMT+00:19:32", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 32, 0, "Europe/Amsterdam", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 32, 0, "GMT+00:19:32", ZoneId.of( "Europe/Amsterdam" ) ), - data( 1900, 1, 1, 0, 19, 32, 0, "Europe/Amsterdam", ZoneId.of( "Europe/Amsterdam" ) ), + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+10:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+07:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:30", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "Europe/Paris", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "Europe/London", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+00:30", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT-02:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT-06:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT-08:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+10:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+07:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:30", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 500, "GMT+01:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "Europe/Paris", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "Europe/London", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+00:30", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT-02:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT-06:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT-08:00", ZONE_PARIS ) + .add( 1970, 1, 1, 0, 0, 0, 0, "GMT+01:00", ZONE_GMT ) + .add( 1970, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZONE_GMT ) + .add( 1970, 1, 1, 0, 0, 0, 0, "GMT-01:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "GMT+01:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "GMT-01:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZONE_OSLO ) + .add( 1900, 1, 1, 0, 9, 21, 0, "GMT+00:09:21", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 9, 21, 0, "Europe/Paris", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 31, 0, "Europe/Paris", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 32, 0, "GMT+00:19:32", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 32, 0, "Europe/Amsterdam", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 32, 0, "GMT+00:19:32", ZONE_AMSTERDAM ) + .add( 1900, 1, 1, 0, 19, 32, 0, "Europe/Amsterdam", ZONE_AMSTERDAM ) // Affected by HHH-13266 - data( 1892, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZoneId.of( "Europe/Oslo" ) ), - data( 1892, 1, 1, 0, 0, 0, 0, "Europe/Oslo", ZoneId.of( "Europe/Oslo" ) ), - data( 1900, 1, 1, 0, 9, 20, 0, "GMT+00:09:21", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 9, 20, 0, "Europe/Paris", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 31, 0, "GMT+00:19:32", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 31, 0, "GMT+00:19:32", ZoneId.of( "Europe/Amsterdam" ) ), - data( 1900, 1, 1, 0, 19, 31, 0, "Europe/Amsterdam", ZoneId.of( "Europe/Amsterdam" ) ), - data( 1600, 1, 1, 0, 0, 0, 0, "GMT+00:19:32", ZoneId.of( "Europe/Amsterdam" ) ), - data( 1600, 1, 1, 0, 0, 0, 0, "Europe/Amsterdam", ZoneId.of( "Europe/Amsterdam" ) ) - ); - } - - private static Object[] data(int year, int month, int day, - int hour, int minute, int second, int nanosecond, String zone, ZoneId defaultTimeZone) { - if ( DIALECT instanceof PostgreSQL81Dialect ) { - // PostgreSQL apparently doesn't support nanosecond precision correctly - nanosecond = 0; - } - return new Object[] { year, month, day, hour, minute, second, nanosecond, zone, defaultTimeZone }; + .add( 1892, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZONE_OSLO ) + .add( 1892, 1, 1, 0, 0, 0, 0, "Europe/Oslo", ZONE_OSLO ) + .add( 1900, 1, 1, 0, 9, 20, 0, "GMT+00:09:21", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 9, 20, 0, "Europe/Paris", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 31, 0, "GMT+00:19:32", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 31, 0, "GMT+00:19:32", ZONE_AMSTERDAM ) + .add( 1900, 1, 1, 0, 19, 31, 0, "Europe/Amsterdam", ZONE_AMSTERDAM ) + .add( 1600, 1, 1, 0, 0, 0, 0, "GMT+00:19:32", ZONE_AMSTERDAM ) + .add( 1600, 1, 1, 0, 0, 0, 0, "Europe/Amsterdam", ZONE_AMSTERDAM ) + .build(); } private final int year; @@ -137,10 +106,10 @@ private static Object[] data(int year, int month, int day, private final int second; private final int nanosecond; private final String zone; - private final ZoneId defaultTimeZone; - public ZonedDateTimeTest(int year, int month, int day, - int hour, int minute, int second, int nanosecond, String zone, ZoneId defaultTimeZone) { + public ZonedDateTimeTest(EnvironmentParameters env, int year, int month, int day, + int hour, int minute, int second, int nanosecond, String zone) { + super( env ); this.year = year; this.month = month; this.day = day; @@ -149,18 +118,37 @@ public ZonedDateTimeTest(int year, int month, int day, this.second = second; this.nanosecond = nanosecond; this.zone = zone; - this.defaultTimeZone = defaultTimeZone; + } + + @Override + protected Class getEntityType() { + return EntityWithZonedDateTime.class; + } + + @Override + protected EntityWithZonedDateTime createEntity(int id) { + return new EntityWithZonedDateTime( + id, + getOriginalZonedDateTime() + ); } private ZonedDateTime getOriginalZonedDateTime() { return ZonedDateTime.of( year, month, day, hour, minute, second, nanosecond, ZoneId.of( zone ) ); } - private ZonedDateTime getExpectedZonedDateTime() { + @Override + protected ZonedDateTime getExpectedPropertyValue() { return getOriginalZonedDateTime().withZoneSameInstant( ZoneId.systemDefault() ); } - private Timestamp getExpectedTimestamp() { + @Override + protected ZonedDateTime getActualPropertyValue(EntityWithZonedDateTime entity) { + return entity.value; + } + + @Override + protected Object getExpectedJdbcValue() { LocalDateTime dateTimeInDefaultTimeZone = getOriginalZonedDateTime().withZoneSameInstant( ZoneId.systemDefault() ) .toLocalDateTime(); return new Timestamp( @@ -173,129 +161,36 @@ private Timestamp getExpectedTimestamp() { } @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { EntityWithZonedDateTime.class }; - } - - @Before - public void cleanup() { - inTransaction( session -> { - session.createNativeQuery( "DELETE FROM theentity" ).executeUpdate(); - } ); - } - - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithZonedDateTime( 1, getOriginalZonedDateTime() ) ); - } ); - inTransaction( session -> { - ZonedDateTime read = session.find( ZonedDateTimeTest.EntityWithZonedDateTime.class, 1 ).value; - assertEquals( - "Writing then reading a value should return the original value", - getExpectedZonedDateTime(), read - ); - assertTrue( - getExpectedZonedDateTime().isEqual( read ) - ); - assertEquals( - 0, - ZonedDateTimeType.INSTANCE.getComparator().compare( getExpectedZonedDateTime(), read ) - ); - } ); - } ); - } - - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenNativeRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithZonedDateTime( 1, getOriginalZonedDateTime() ) ); - } ); - inTransaction( session -> { - session.doWork( connection -> { - final PreparedStatement statement = connection.prepareStatement( - "SELECT thevalue FROM theentity WHERE theid = ?" - ); - statement.setInt( 1, 1 ); - statement.execute(); - final ResultSet resultSet = statement.getResultSet(); - resultSet.next(); - Timestamp nativeRead = resultSet.getTimestamp( 1 ); - assertEquals( - "Raw values written in database should match the original value (same instant)", - getExpectedTimestamp(), - nativeRead - ); - } ); - } ); - } ); + protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException { + return resultSet.getTimestamp( columnIndex ); } @Test public void testRetrievingEntityByZonedDateTime() { - withDefaultTimeZone( defaultTimeZone, () -> { + withDefaultTimeZone( () -> { inTransaction( session -> { session.persist( new EntityWithZonedDateTime( 1, getOriginalZonedDateTime() ) ); } ); Consumer checkOneMatch = expected -> inSession( s -> { - Query query = s.createQuery( "from EntityWithZonedDateTime o where o.value = :date" ); + Query query = s.createQuery( "from " + ENTITY_NAME + " o where o.value = :date" ); query.setParameter( "date", expected, ZonedDateTimeType.INSTANCE ); List list = query.list(); assertThat( list.size(), is( 1 ) ); } ); checkOneMatch.accept( getOriginalZonedDateTime() ); - checkOneMatch.accept( getExpectedZonedDateTime() ); - checkOneMatch.accept( getExpectedZonedDateTime().withZoneSameInstant( ZoneOffset.UTC ) ); + checkOneMatch.accept( getExpectedPropertyValue() ); + checkOneMatch.accept( getExpectedPropertyValue().withZoneSameInstant( ZoneOffset.UTC ) ); } ); } - private static void withDefaultTimeZone(ZoneId zoneId, Runnable runnable) { - TimeZone timeZoneBefore = TimeZone.getDefault(); - TimeZone.setDefault( TimeZone.getTimeZone( zoneId ) ); - /* - * Run the code in a new thread, because some libraries (looking at you, h2 JDBC driver) - * cache data dependent on the default timezone in thread local variables, - * and we want this data to be reinitialized with the new default time zone. - */ - try { - ExecutorService executor = Executors.newSingleThreadExecutor(); - Future future = executor.submit( runnable ); - executor.shutdown(); - future.get(); - } - catch (InterruptedException e) { - throw new IllegalStateException( "Interrupted while testing", e ); - } - catch (ExecutionException e) { - Throwable cause = e.getCause(); - if ( cause instanceof RuntimeException ) { - throw (RuntimeException) cause; - } - else if ( cause instanceof Error ) { - throw (Error) cause; - } - else { - throw new IllegalStateException( "Unexpected exception while testing", cause ); - } - } - finally { - TimeZone.setDefault( timeZoneBefore ); - } - } - - @Entity(name = "EntityWithZonedDateTime") - @Table(name = "theentity") - private static final class EntityWithZonedDateTime { + @Entity(name = ENTITY_NAME) + static final class EntityWithZonedDateTime { @Id - @Column(name = "theid") + @Column(name = ID_COLUMN_NAME) private Integer id; @Basic - @Column(name = "thevalue") + @Column(name = PROPERTY_COLUMN_NAME) private ZonedDateTime value; protected EntityWithZonedDateTime() { @@ -306,9 +201,4 @@ private EntityWithZonedDateTime(int id, ZonedDateTime value) { this.value = value; } } - - @Override - protected boolean isCleanupTestDataRequired() { - return true; - } }