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

This commit is contained in:
Vlad Mihalcea 2016-09-01 14:28:28 +03:00
parent d229534647
commit 553942d2f9
27 changed files with 1122 additions and 16 deletions

View File

@ -7,6 +7,7 @@
package org.hibernate;
import java.sql.Connection;
import java.util.TimeZone;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.hibernate.resource.jdbc.spi.StatementInspector;
@ -174,4 +175,5 @@ public interface SessionBuilder<T extends SessionBuilder> {
*/
T clearEventListeners();
T jdbcTimeZone(TimeZone timeZone);
}

View File

@ -6,12 +6,14 @@
*/
package org.hibernate.boot.internal;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import org.hibernate.ConnectionAcquisitionMode;
import org.hibernate.ConnectionReleaseMode;
@ -77,6 +79,7 @@ import static org.hibernate.cfg.AvailableSettings.FLUSH_BEFORE_COMPLETION;
import static org.hibernate.cfg.AvailableSettings.GENERATE_STATISTICS;
import static org.hibernate.cfg.AvailableSettings.HQL_BULK_ID_STRATEGY;
import static org.hibernate.cfg.AvailableSettings.INTERCEPTOR;
import static org.hibernate.cfg.AvailableSettings.JDBC_TIME_ZONE;
import static org.hibernate.cfg.AvailableSettings.JPAQL_STRICT_COMPLIANCE;
import static org.hibernate.cfg.AvailableSettings.JTA_TRACK_BY_THREAD;
import static org.hibernate.cfg.AvailableSettings.LOG_SESSION_METRICS;
@ -591,6 +594,7 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
private boolean commentsEnabled;
private PhysicalConnectionHandlingMode connectionHandlingMode;
private boolean wrapResultSetsEnabled;
private TimeZone jdbcTimeZone;
private Map<String, SQLFunction> sqlFunctions;
@ -767,6 +771,22 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
configurationSettings,
false
);
Object jdbcTimeZoneValue = configurationSettings.get(
JDBC_TIME_ZONE
);
if ( jdbcTimeZoneValue instanceof TimeZone ) {
this.jdbcTimeZone = (TimeZone) jdbcTimeZoneValue;
}
else if ( jdbcTimeZoneValue instanceof ZoneId ) {
this.jdbcTimeZone = TimeZone.getTimeZone( (ZoneId) jdbcTimeZoneValue );
}
else if ( jdbcTimeZoneValue instanceof String ) {
this.jdbcTimeZone = TimeZone.getTimeZone( ZoneId.of((String) jdbcTimeZoneValue) );
}
else if ( jdbcTimeZoneValue != null ) {
throw new IllegalArgumentException( "Configuration property " + JDBC_TIME_ZONE + " value [" + jdbcTimeZoneValue + "] is not supported!" );
}
}
private static Interceptor determineInterceptor(Map configurationSettings, StrategySelector strategySelector) {
@ -1180,6 +1200,11 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
public boolean isPreferUserTransaction() {
return this.preferUserTransaction;
}
@Override
public TimeZone getJdbcTimeZone() {
return this.jdbcTimeZone;
}
}
@Override
@ -1480,4 +1505,9 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
public boolean isPreferUserTransaction() {
return options.isPreferUserTransaction();
}
@Override
public TimeZone getJdbcTimeZone() {
return options.getJdbcTimeZone();
}
}

View File

@ -7,6 +7,7 @@
package org.hibernate.boot.internal;
import java.util.Map;
import java.util.TimeZone;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.CustomEntityDirtinessStrategy;
@ -118,6 +119,7 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions {
private final boolean commentsEnabled;
private final PhysicalConnectionHandlingMode physicalConnectionHandlingMode;
private final boolean wrapResultSetsEnabled;
private final TimeZone jdbcTimeZone;
private final Map<String, SQLFunction> sqlFunctions;
@ -191,6 +193,8 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions {
this.commentsEnabled = state.isCommentsEnabled();
this.sqlFunctions = state.getCustomSqlFunctionMap();
this.jdbcTimeZone = state.getJdbcTimeZone();
}
@Override
@ -496,4 +500,9 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions {
public boolean isPreferUserTransaction() {
return preferUserTransaction;
}
@Override
public TimeZone getJdbcTimeZone() {
return jdbcTimeZone;
}
}

View File

@ -7,6 +7,7 @@
package org.hibernate.boot.internal;
import java.util.Map;
import java.util.TimeZone;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.CustomEntityDirtinessStrategy;
@ -165,4 +166,6 @@ public interface SessionFactoryOptionsState {
Map<String, SQLFunction> getCustomSqlFunctionMap();
boolean isPreferUserTransaction();
TimeZone getJdbcTimeZone();
}

View File

@ -7,6 +7,7 @@
package org.hibernate.boot.spi;
import java.util.Map;
import java.util.TimeZone;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.CustomEntityDirtinessStrategy;
@ -348,4 +349,9 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
public Class<? extends Interceptor> getStatelessInterceptorImplementor() {
return delegate.getStatelessInterceptorImplementor();
}
@Override
public TimeZone getJdbcTimeZone() {
return delegate.getJdbcTimeZone();
}
}

View File

@ -7,6 +7,7 @@
package org.hibernate.boot.spi;
import java.util.Map;
import java.util.TimeZone;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.CustomEntityDirtinessStrategy;
@ -19,7 +20,6 @@ import org.hibernate.SessionFactoryObserver;
import org.hibernate.boot.SchemaAutoTooling;
import org.hibernate.boot.TempTableDdlTransactionHandling;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.cache.spi.CacheKeysFactory;
import org.hibernate.cache.spi.QueryCacheFactory;
import org.hibernate.cfg.BaselineSessionEventsListenerBuilder;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
@ -205,4 +205,6 @@ public interface SessionFactoryOptions {
boolean isAllowOutOfTransactionUpdateOperations();
boolean isReleaseResourcesOnCloseEnabled();
TimeZone getJdbcTimeZone();
}

View File

@ -710,6 +710,11 @@ public interface AvailableSettings {
*/
String BATCH_VERSIONED_DATA = "hibernate.jdbc.batch_versioned_data";
/**
* Default JDBC TimeZone.
*/
String JDBC_TIME_ZONE = "hibernate.jdbc.time_zone";
/**
* Enable automatic session close at end of transaction
*/

View File

@ -7,6 +7,7 @@
package org.hibernate.engine.spi;
import java.sql.Connection;
import java.util.TimeZone;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.Interceptor;
@ -99,4 +100,10 @@ public abstract class AbstractDelegatingSessionBuilder implements SessionBuilder
delegate.clearEventListeners();
return this;
}
@Override
public SessionBuilder jdbcTimeZone(TimeZone timeZone) {
delegate.jdbcTimeZone(timeZone);
return this;
}
}

View File

@ -12,6 +12,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import javax.persistence.EntityGraph;
import javax.persistence.EntityManagerFactory;
@ -1166,4 +1167,9 @@ public class SessionDelegatorBaseImpl implements SessionImplementor {
public void setJdbcBatchSize(Integer jdbcBatchSize) {
delegate.setJdbcBatchSize( jdbcBatchSize );
}
@Override
public TimeZone getJdbcTimeZone() {
return delegate.getJdbcTimeZone();
}
}

View File

@ -12,6 +12,7 @@ import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.sql.SQLException;
import java.util.List;
import java.util.TimeZone;
import java.util.UUID;
import javax.persistence.FlushModeType;
import javax.persistence.Tuple;
@ -109,6 +110,8 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
private final boolean isTransactionCoordinatorShared;
private final Interceptor interceptor;
private final TimeZone jdbcTimeZone;
private FlushMode flushMode;
private boolean autoJoinTransactions;
@ -152,6 +155,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
}
this.interceptor = interpret( options.getInterceptor() );
this.jdbcTimeZone = options.getJdbcTimeZone();
final StatementInspector statementInspector = interpret( options.getStatementInspector() );
this.jdbcSessionContext = new JdbcSessionContextImpl( this, statementInspector );
@ -487,6 +491,11 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
return remapped == null ? sqlTypeDescriptor : remapped;
}
@Override
public TimeZone getJdbcTimeZone() {
return jdbcTimeZone;
}
@Override
public JdbcServices getJdbcServices() {
return getFactory().getJdbcServices();

View File

@ -7,6 +7,7 @@
package org.hibernate.internal;
import java.sql.Connection;
import java.util.TimeZone;
import org.hibernate.FlushMode;
import org.hibernate.Interceptor;
@ -42,6 +43,7 @@ public interface SessionCreationOptions {
String getTenantIdentifier();
TimeZone getJdbcTimeZone();
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// deprecations
@ -62,5 +64,4 @@ public interface SessionCreationOptions {
AfterCompletionAction getAfterCompletionAction();
ManagedFlushChecker getManagedFlushChecker();
}

View File

@ -18,6 +18,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.persistence.EntityGraph;
@ -1073,6 +1074,8 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor {
private boolean autoClose;
private boolean autoClear;
private String tenantIdentifier;
private TimeZone jdbcTimeZone;
private List<SessionEventListener> listeners;
//todo : expose setting
@ -1094,6 +1097,7 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor {
if ( sessionFactory.getCurrentTenantIdentifierResolver() != null ) {
tenantIdentifier = sessionFactory.getCurrentTenantIdentifierResolver().resolveCurrentTenantIdentifier();
}
this.jdbcTimeZone = sessionFactory.getSessionFactoryOptions().getJdbcTimeZone();
listeners = sessionFactory.getSessionFactoryOptions().getBaselineSessionEventsListenerBuilder().buildBaselineList();
}
@ -1184,6 +1188,10 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor {
return tenantIdentifier;
}
@Override
public TimeZone getJdbcTimeZone() {
return jdbcTimeZone;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// SessionBuilder
@ -1304,6 +1312,12 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor {
listeners.clear();
return (T) this;
}
@Override
public T jdbcTimeZone(TimeZone timeZone) {
jdbcTimeZone = timeZone;
return (T) this;
}
}
public static class StatelessSessionBuilderImpl implements StatelessSessionBuilder, SessionCreationOptions {
@ -1382,6 +1396,11 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor {
return tenantIdentifier;
}
@Override
public TimeZone getJdbcTimeZone() {
return sessionFactory.getSessionFactoryOptions().getJdbcTimeZone();
}
@Override
public SessionOwner getSessionOwner() {
return null;

View File

@ -6,6 +6,10 @@
*/
package org.hibernate.type.descriptor;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.TimeZone;
import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
@ -39,4 +43,14 @@ public interface WrapperOptions {
* @return The remapped descriptor. May be the same as the known descriptor indicating no remapping.
*/
public SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor);
/**
* The JDBC {@link TimeZone} used when persisting Timestamp and DateTime properties into the database.
* This setting is used when storing timestamps using the {@link java.sql.PreparedStatement#setTimestamp(int, Timestamp, Calendar)} method.
*
* This way, the storage {@link TimeZone} can differ from the default JVM TimeZone given by {@link TimeZone#getDefault()}.
*
* @return JDBC {@link TimeZone}
*/
public TimeZone getJdbcTimeZone();
}

View File

@ -49,6 +49,9 @@ public class TimeTypeDescriptor implements SqlTypeDescriptor {
if ( value instanceof Calendar ) {
st.setTime( index, time, (Calendar) value );
}
else if (options.getJdbcTimeZone() != null) {
st.setTime( index, time, Calendar.getInstance( options.getJdbcTimeZone() ) );
}
else {
st.setTime( index, time );
}
@ -61,6 +64,9 @@ public class TimeTypeDescriptor implements SqlTypeDescriptor {
if ( value instanceof Calendar ) {
st.setTime( name, time, (Calendar) value );
}
else if (options.getJdbcTimeZone() != null) {
st.setTime( name, time, Calendar.getInstance( options.getJdbcTimeZone() ) );
}
else {
st.setTime( name, time );
}
@ -73,17 +79,23 @@ public class TimeTypeDescriptor implements SqlTypeDescriptor {
return new BasicExtractor<X>( javaTypeDescriptor, this ) {
@Override
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( rs.getTime( name ), options );
return options.getJdbcTimeZone() != null ?
javaTypeDescriptor.wrap( rs.getTime( name, Calendar.getInstance( options.getJdbcTimeZone() ) ), options ) :
javaTypeDescriptor.wrap( rs.getTime( name ), options );
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getTime( index ), options );
return options.getJdbcTimeZone() != null ?
javaTypeDescriptor.wrap( statement.getTime( index, Calendar.getInstance( options.getJdbcTimeZone() ) ), options ) :
javaTypeDescriptor.wrap( statement.getTime( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getTime( name ), options );
return options.getJdbcTimeZone() != null ?
javaTypeDescriptor.wrap( statement.getTime( name, Calendar.getInstance( options.getJdbcTimeZone() ) ), options ) :
javaTypeDescriptor.wrap( statement.getTime( name ), options );
}
};
}

View File

@ -49,6 +49,9 @@ public class TimestampTypeDescriptor implements SqlTypeDescriptor {
if ( value instanceof Calendar ) {
st.setTimestamp( index, timestamp, (Calendar) value );
}
else if (options.getJdbcTimeZone() != null) {
st.setTimestamp( index, timestamp, Calendar.getInstance( options.getJdbcTimeZone() ) );
}
else {
st.setTimestamp( index, timestamp );
}
@ -61,6 +64,9 @@ public class TimestampTypeDescriptor implements SqlTypeDescriptor {
if ( value instanceof Calendar ) {
st.setTimestamp( name, timestamp, (Calendar) value );
}
else if (options.getJdbcTimeZone() != null) {
st.setTimestamp( name, timestamp, Calendar.getInstance( options.getJdbcTimeZone() ) );
}
else {
st.setTimestamp( name, timestamp );
}
@ -73,17 +79,23 @@ public class TimestampTypeDescriptor implements SqlTypeDescriptor {
return new BasicExtractor<X>( javaTypeDescriptor, this ) {
@Override
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( rs.getTimestamp( name ), options );
return options.getJdbcTimeZone() != null ?
javaTypeDescriptor.wrap( rs.getTimestamp( name, Calendar.getInstance( options.getJdbcTimeZone() ) ), options ) :
javaTypeDescriptor.wrap( rs.getTimestamp( name ), options );
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getTimestamp( index ), options );
return options.getJdbcTimeZone() != null ?
javaTypeDescriptor.wrap( statement.getTimestamp( index, Calendar.getInstance( options.getJdbcTimeZone() ) ), options ) :
javaTypeDescriptor.wrap( statement.getTimestamp( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getTimestamp( name ), options );
return options.getJdbcTimeZone() != null ?
javaTypeDescriptor.wrap( statement.getTimestamp( name, Calendar.getInstance( options.getJdbcTimeZone() ) ), options ) :
javaTypeDescriptor.wrap( statement.getTimestamp( name ), options );
}
};
}

View File

@ -0,0 +1,140 @@
/*
* 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.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.time.Instant;
import java.time.OffsetTime;
import java.util.Calendar;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* @author Vlad Mihalcea
*/
public class JdbcTimeCustomTimeZoneTest
extends BaseNonConfigCoreFunctionalTestCase {
private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider();
private static final TimeZone TIME_ZONE = TimeZone.getTimeZone(
"America/Los_Angeles" );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class
};
}
@Override
protected void addSettings(Map settings) {
settings.put(
AvailableSettings.CONNECTION_PROVIDER,
connectionProvider
);
settings.put(
AvailableSettings.JDBC_TIME_ZONE,
TIME_ZONE
);
}
@Override
protected void releaseResources() {
super.releaseResources();
connectionProvider.stop();
}
@Test
public void testTimeZone() {
connectionProvider.clear();
doInHibernate( this::sessionFactory, s -> {
Person person = new Person();
person.id = 1L;
s.persist( person );
} );
assertEquals( 1, connectionProvider.getPreparedStatements().size() );
PreparedStatement ps = connectionProvider.getPreparedStatements()
.get( 0 );
try {
ArgumentCaptor<Calendar> calendarArgumentCaptor = ArgumentCaptor.forClass(
Calendar.class );
verify( ps, times( 1 ) ).setTime(
anyInt(),
any( Time.class ),
calendarArgumentCaptor.capture()
);
assertEquals(
TIME_ZONE,
calendarArgumentCaptor.getValue().getTimeZone()
);
}
catch (SQLException e) {
fail( e.getMessage() );
}
connectionProvider.clear();
doInHibernate( this::sessionFactory, s -> {
s.doWork( connection -> {
try (Statement st = connection.createStatement()) {
try (ResultSet rs = st.executeQuery(
"select createdOn from Person" )) {
while ( rs.next() ) {
Time time = rs.getTime( 1 );
Time offsetTime = Time.valueOf( OffsetTime.ofInstant(
Instant.ofEpochMilli( 0 ),
TIME_ZONE.toZoneId()
).toLocalTime() );
assertEquals( offsetTime, time );
}
}
}
} );
Person person = s.find( Person.class, 1L );
assertEquals(
0,
person.createdOn.getTime() % TimeUnit.DAYS.toSeconds( 1 )
);
} );
}
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
private Time createdOn = new Time( 0 );
}
}

View File

@ -0,0 +1,95 @@
/*
* 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.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Time;
import java.util.Map;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.cfg.AvailableSettings;
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 static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* @author Vlad Mihalcea
*/
public class JdbcTimeDefaultTimeZoneTest
extends BaseNonConfigCoreFunctionalTestCase {
private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider();
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class
};
}
@Override
protected void addSettings(Map settings) {
settings.put(
AvailableSettings.CONNECTION_PROVIDER,
connectionProvider
);
}
@Override
protected void releaseResources() {
super.releaseResources();
connectionProvider.stop();
}
@Test
public void testTimeZone() {
connectionProvider.clear();
doInHibernate( this::sessionFactory, s -> {
Person person = new Person();
person.id = 1L;
s.persist( person );
} );
assertEquals( 1, connectionProvider.getPreparedStatements().size() );
PreparedStatement ps = connectionProvider.getPreparedStatements()
.get( 0 );
try {
verify( ps, times( 1 ) ).setTime( anyInt(), any( Time.class ) );
}
catch (SQLException e) {
fail( e.getMessage() );
}
doInHibernate( this::sessionFactory, s -> {
Person person = s.find( Person.class, 1L );
assertEquals( 0, person.createdOn.getTime() );
} );
}
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
private Time createdOn = new Time( 0 );
}
}

View File

@ -0,0 +1,132 @@
/*
* 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.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Map;
import java.util.TimeZone;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernateSessionBuilder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* @author Vlad Mihalcea
*/
public class JdbcTimestampCustomSessionLevelTimeZoneTest
extends BaseNonConfigCoreFunctionalTestCase {
private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider();
private static final TimeZone TIME_ZONE = TimeZone.getTimeZone(
"America/Los_Angeles" );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class
};
}
@Override
protected void addSettings(Map settings) {
settings.put(
AvailableSettings.CONNECTION_PROVIDER,
connectionProvider
);
}
@Override
protected void releaseResources() {
super.releaseResources();
connectionProvider.stop();
}
@Test
public void testTimeZone() {
connectionProvider.clear();
doInHibernateSessionBuilder( () -> {return sessionFactory().withOptions().jdbcTimeZone( TIME_ZONE);}, s -> {
Person person = new Person();
person.id = 1L;
s.persist( person );
} );
assertEquals( 1, connectionProvider.getPreparedStatements().size() );
PreparedStatement ps = connectionProvider.getPreparedStatements()
.get( 0 );
try {
ArgumentCaptor<Calendar> calendarArgumentCaptor = ArgumentCaptor.forClass(
Calendar.class );
verify( ps, times( 1 ) ).setTimestamp(
anyInt(),
any( Timestamp.class ),
calendarArgumentCaptor.capture()
);
assertEquals(
TIME_ZONE,
calendarArgumentCaptor.getValue().getTimeZone()
);
}
catch (SQLException e) {
fail( e.getMessage() );
}
connectionProvider.clear();
doInHibernateSessionBuilder( () -> {return sessionFactory().withOptions().jdbcTimeZone( TIME_ZONE);}, s -> {
s.doWork( connection -> {
try (Statement st = connection.createStatement()) {
try (ResultSet rs = st.executeQuery(
"select createdOn from Person" )) {
while ( rs.next() ) {
Timestamp timestamp = rs.getTimestamp( 1 );
int offsetDiff = TimeZone.getDefault()
.getOffset( 0 ) - TIME_ZONE.getOffset( 0 );
assertEquals(
Math.abs( Long.valueOf( offsetDiff )
.longValue() ),
Math.abs( timestamp.getTime() )
);
}
}
}
} );
Person person = s.find( Person.class, 1L );
assertEquals( 0, person.createdOn.getTime() );
} );
}
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
private Timestamp createdOn = new Timestamp( 0 );
}
}

View File

@ -0,0 +1,136 @@
/*
* 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.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Map;
import java.util.TimeZone;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* @author Vlad Mihalcea
*/
public class JdbcTimestampCustomTimeZoneTest
extends BaseNonConfigCoreFunctionalTestCase {
private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider();
private static final TimeZone TIME_ZONE = TimeZone.getTimeZone(
"America/Los_Angeles" );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class
};
}
@Override
protected void addSettings(Map settings) {
settings.put(
AvailableSettings.CONNECTION_PROVIDER,
connectionProvider
);
settings.put(
AvailableSettings.JDBC_TIME_ZONE,
TIME_ZONE
);
}
@Override
protected void releaseResources() {
super.releaseResources();
connectionProvider.stop();
}
@Test
public void testTimeZone() {
connectionProvider.clear();
doInHibernate( this::sessionFactory, s -> {
Person person = new Person();
person.id = 1L;
s.persist( person );
} );
assertEquals( 1, connectionProvider.getPreparedStatements().size() );
PreparedStatement ps = connectionProvider.getPreparedStatements()
.get( 0 );
try {
ArgumentCaptor<Calendar> calendarArgumentCaptor = ArgumentCaptor.forClass(
Calendar.class );
verify( ps, times( 1 ) ).setTimestamp(
anyInt(),
any( Timestamp.class ),
calendarArgumentCaptor.capture()
);
assertEquals(
TIME_ZONE,
calendarArgumentCaptor.getValue().getTimeZone()
);
}
catch (SQLException e) {
fail( e.getMessage() );
}
connectionProvider.clear();
doInHibernate( this::sessionFactory, s -> {
s.doWork( connection -> {
try (Statement st = connection.createStatement()) {
try (ResultSet rs = st.executeQuery(
"select createdOn from Person" )) {
while ( rs.next() ) {
Timestamp timestamp = rs.getTimestamp( 1 );
int offsetDiff = TimeZone.getDefault()
.getOffset( 0 ) - TIME_ZONE.getOffset( 0 );
assertEquals(
Math.abs( Long.valueOf( offsetDiff )
.longValue() ),
Math.abs( timestamp.getTime() )
);
}
}
}
} );
Person person = s.find( Person.class, 1L );
assertEquals( 0, person.createdOn.getTime() );
} );
}
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
private Timestamp createdOn = new Timestamp( 0 );
}
}

View File

@ -0,0 +1,98 @@
/*
* 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.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Map;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.cfg.AvailableSettings;
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 static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* @author Vlad Mihalcea
*/
public class JdbcTimestampDefaultTimeZoneTest
extends BaseNonConfigCoreFunctionalTestCase {
private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider();
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class
};
}
@Override
protected void addSettings(Map settings) {
settings.put(
AvailableSettings.CONNECTION_PROVIDER,
connectionProvider
);
}
@Override
protected void releaseResources() {
super.releaseResources();
connectionProvider.stop();
}
@Test
public void testTimeZone() {
connectionProvider.clear();
doInHibernate( this::sessionFactory, s -> {
Person person = new Person();
person.id = 1L;
s.persist( person );
} );
assertEquals( 1, connectionProvider.getPreparedStatements().size() );
PreparedStatement ps = connectionProvider.getPreparedStatements()
.get( 0 );
try {
verify( ps, times( 1 ) ).setTimestamp(
anyInt(),
any( Timestamp.class )
);
}
catch (SQLException e) {
fail( e.getMessage() );
}
doInHibernate( this::sessionFactory, s -> {
Person person = s.find( Person.class, 1L );
assertEquals( 0, person.createdOn.getTime() );
} );
}
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
private Timestamp createdOn = new Timestamp( 0 );
}
}

View File

@ -0,0 +1,104 @@
/*
* 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.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;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect( value = PostgreSQL82Dialect.class)
public class JdbcTimestampUTCTimeZoneTest
extends BaseNonConfigCoreFunctionalTestCase {
private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider();
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(
AvailableSettings.CONNECTION_PROVIDER,
connectionProvider
);
settings.put(
AvailableSettings.JDBC_TIME_ZONE,
TIME_ZONE
);
}
@Override
protected void releaseResources() {
super.releaseResources();
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;
}
}

View File

@ -0,0 +1,93 @@
/*
* 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.sql.ResultSet;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Map;
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.TimeZoneConnectionProvider;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertEquals;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect( value = PostgreSQL82Dialect.class)
public class JdbcTimestampWithoutUTCTimeZoneTest
extends BaseNonConfigCoreFunctionalTestCase {
private TimeZoneConnectionProvider connectionProvider = new TimeZoneConnectionProvider( "America/Los_Angeles" );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class
};
}
@Override
protected void addSettings(Map settings) {
settings.put(
AvailableSettings.CONNECTION_PROVIDER,
connectionProvider
);
}
@Override
protected void releaseResources() {
super.releaseResources();
connectionProvider.stop();
}
@Test
public void testTimeZone() {
doInHibernate( this::sessionFactory, s -> {
Person person = new Person();
person.id = 1L;
//Y2K
person.createdOn = new Timestamp(946684800000L);
s.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') " +
"FROM person" )) {
while ( rs.next() ) {
String timestamp = rs.getString( 1 );
assertEquals("1999-12-31 16:00:00.000000", timestamp);
}
}
}
} );
} );
}
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
private Timestamp createdOn;
}
}

View File

@ -8,13 +8,13 @@ package org.hibernate.test.timestamp;
import java.util.Date;
import org.junit.Test;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

View File

@ -9,12 +9,10 @@ import java.sql.Clob;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.junit.Test;
import java.util.TimeZone;
import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.engine.jdbc.NonContextualLobCreator;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
@ -23,6 +21,9 @@ import org.hibernate.type.descriptor.sql.ClobTypeDescriptor;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@ -47,6 +48,11 @@ public class StringValueMappingTest extends BaseUnitTestCase {
public SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) {
return sqlTypeDescriptor;
}
@Override
public TimeZone getJdbcTimeZone() {
return null;
}
};
public static final String COLUMN_NAME = "n/a";

View File

@ -1,6 +1,9 @@
package org.hibernate.test.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.function.Supplier;
/**
* @author Vlad Mihalcea
@ -24,6 +27,26 @@ public class ReflectionUtil {
}
}
/**
* Get a field value from a given object
* @param target Object whose field is being read
* @param name field name
* @return field object
*/
public static <T> T getFieldValue(Object target, String name) {
try {
Field field = target.getClass().getDeclaredField( name );
field.setAccessible( true );
return (T) field.get( target );
}
catch ( NoSuchFieldException e ) {
throw new IllegalArgumentException( "Class " + target.getClass() + " does not contain a " + name + " field", e);
}
catch ( IllegalAccessException e ) {
throw new IllegalArgumentException( "Cannot set field " + name, e);
}
}
/**
* Set target Object field to a certain value
* @param target Object whose field is being set
@ -38,4 +61,37 @@ public class ReflectionUtil {
throw new IllegalArgumentException("Field " + field + " could not be set", e );
}
}
/**
* New target Object instance using the given arguments
* @param constructorSupplier constructor supplier
* @param args Constructor arguments
* @return new Object instance
*/
public static <T> T newInstance(Supplier<Constructor<T>> constructorSupplier, Object... args) {
try {
Constructor constructor = constructorSupplier.get();
constructor.setAccessible( true );
return (T) constructor.newInstance( args );
}
catch ( IllegalAccessException | InstantiationException | InvocationTargetException e ) {
throw new IllegalArgumentException("Constructor could not be called", e );
}
}
/**
* Set target Object field to a certain value
* @param target Object whose field is being set
* @param fieldName Object field naem to set
* @param value the new value for the given field
*/
public static void setField(Object target, String fieldName, Object value) {
try {
Field field = getField(target.getClass(), fieldName);
field.set( target, value );
}
catch ( IllegalAccessException e ) {
throw new IllegalArgumentException("Field " + fieldName + " could not be set", e );
}
}
}

View File

@ -0,0 +1,37 @@
/*
* 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.util.jdbc;
import java.util.TimeZone;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
/**
* This {@link ConnectionProvider} extends any other ConnectionProvider that would be used by default taken the current configuration properties, and it
* just sets a default TimeZone which is different than the current default one.
*
* @author Vlad Mihalcea
*/
public class TimeZoneConnectionProvider
extends ConnectionProviderDelegate {
private final String defaultTimeZone;
private final String customTimeZone;
public TimeZoneConnectionProvider(String customTimeZone) {
this.customTimeZone = customTimeZone;
this.defaultTimeZone = System.setProperty( "user.timezone", customTimeZone);
TimeZone.setDefault(TimeZone.getTimeZone( customTimeZone ));
}
@Override
public void stop() {
super.stop();
System.setProperty( "user.timezone", defaultTimeZone);
TimeZone.setDefault(TimeZone.getTimeZone( defaultTimeZone ));
}
}

View File

@ -14,6 +14,7 @@ import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import org.hibernate.Session;
import org.hibernate.SessionBuilder;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
@ -183,7 +184,7 @@ public class TransactionUtil {
/**
* Execute function in a Hibernate transaction
*
* @param factorySupplier EntityManagerFactory supplier
* @param factorySupplier SessionFactory supplier
* @param function function
* @param <T> result type
*
@ -219,9 +220,9 @@ public class TransactionUtil {
}
/**
* Execute function in a JPA transaction without return value
* Execute function in a Hibernate transaction without return value
*
* @param factorySupplier EntityManagerFactory supplier
* @param factorySupplier SessionFactory supplier
* @param function function
*/
public static void doInHibernate(
@ -250,4 +251,75 @@ public class TransactionUtil {
}
}
}
/**
* Execute function in a Hibernate transaction
*
* @param sessionBuilderSupplier SessionFactory supplier
* @param function function
* @param <T> result type
*
* @return result
*/
public static <T> T doInHibernateSessionBuilder(
Supplier<SessionBuilder> sessionBuilderSupplier,
HibernateTransactionFunction<T> function) {
T result = null;
Session session = null;
Transaction txn = null;
try {
session = sessionBuilderSupplier.get().openSession();
function.beforeTransactionCompletion();
txn = session.beginTransaction();
result = function.apply( session );
txn.commit();
}
catch ( Throwable e ) {
if ( txn != null ) {
txn.rollback();
}
throw e;
}
finally {
function.afterTransactionCompletion();
if ( session != null ) {
session.close();
}
}
return result;
}
/**
* Execute function in a Hibernate transaction without return value
*
* @param sessionBuilderSupplier SessionFactory supplier
* @param function function
*/
public static void doInHibernateSessionBuilder(
Supplier<SessionBuilder> sessionBuilderSupplier,
HibernateTransactionConsumer function) {
Session session = null;
Transaction txn = null;
try {
session = sessionBuilderSupplier.get().openSession();
function.beforeTransactionCompletion();
txn = session.beginTransaction();
function.accept( session );
txn.commit();
}
catch ( Throwable e ) {
if ( txn != null ) {
txn.rollback();
}
throw e;
}
finally {
function.afterTransactionCompletion();
if ( session != null ) {
session.close();
}
}
}
}