HHH-17557 native queries return LocalDate and LocalDateTime instead of java.sql types
... by default, with a setting to recover old behavior.
This commit is contained in:
parent
2fc51bd7b2
commit
2e6902ddb2
|
@ -209,6 +209,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
|
|||
private boolean collectionsInDefaultFetchGroupEnabled = true;
|
||||
private final boolean UnownedAssociationTransientCheck;
|
||||
private final boolean passProcedureParameterNames;
|
||||
private final boolean preferJdbcDatetimeTypes;
|
||||
|
||||
// JPA callbacks
|
||||
private final boolean callbacksEnabled;
|
||||
|
@ -635,6 +636,12 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
|
|||
configurationSettings,
|
||||
false
|
||||
);
|
||||
|
||||
this.preferJdbcDatetimeTypes = ConfigurationHelper.getBoolean(
|
||||
AvailableSettings.NATIVE_PREFER_JDBC_DATETIME_TYPES,
|
||||
configurationSettings,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
private boolean disallowBatchUpdates(Dialect dialect, ExtractedDatabaseMetaData meta) {
|
||||
|
@ -1326,6 +1333,11 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
|
|||
return passProcedureParameterNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPreferJdbcDatetimeTypesInNativeQueriesEnabled() {
|
||||
return preferJdbcDatetimeTypes;
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// In-flight mutation access
|
||||
|
||||
|
|
|
@ -512,4 +512,9 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
|
|||
public boolean isPassProcedureParameterNames() {
|
||||
return delegate.isPassProcedureParameterNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPreferJdbcDatetimeTypesInNativeQueriesEnabled() {
|
||||
return delegate.isPreferJdbcDatetimeTypesInNativeQueriesEnabled();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -365,4 +365,14 @@ public interface SessionFactoryOptions extends QueryEngineOptions {
|
|||
}
|
||||
|
||||
boolean isPassProcedureParameterNames();
|
||||
|
||||
/**
|
||||
* Should native queries return JDBC datetime types
|
||||
* instead of using {@code java.time} types.
|
||||
*
|
||||
* @since 7.0
|
||||
*
|
||||
* @see org.hibernate.cfg.QuerySettings#NATIVE_PREFER_JDBC_DATETIME_TYPES
|
||||
*/
|
||||
boolean isPreferJdbcDatetimeTypesInNativeQueriesEnabled();
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ public interface QuerySettings {
|
|||
* @since 6.5
|
||||
*/
|
||||
String PORTABLE_INTEGER_DIVISION = "hibernate.query.hql.portable_integer_division";
|
||||
|
||||
/**
|
||||
* Specifies a {@link org.hibernate.query.hql.HqlTranslator} to use for HQL query
|
||||
* translation.
|
||||
|
@ -135,15 +136,28 @@ public interface QuerySettings {
|
|||
String CRITERIA_COPY_TREE = "hibernate.criteria.copy_tree";
|
||||
|
||||
/**
|
||||
* When set to true, indicates that ordinal parameters (represented by the '?' placeholder) in native queries will be ignored.
|
||||
* <p>
|
||||
* By default, this is set to false, i.e. native queries will be checked for ordinal placeholders.
|
||||
* When enabled, ordinal parameters (represented by the {@code ?} placeholder) in
|
||||
* native queries will be ignored.
|
||||
* <p>
|
||||
* By default, native queries are checked for ordinal placeholders.
|
||||
*
|
||||
* @see SessionFactoryOptions#getNativeJdbcParametersIgnored()
|
||||
*/
|
||||
String NATIVE_IGNORE_JDBC_PARAMETERS = "hibernate.query.native.ignore_jdbc_parameters";
|
||||
|
||||
/**
|
||||
* When enabled, native queries will return {@link java.sql.Date},
|
||||
* {@link java.sql.Time}, and {@link java.sql.Timestamp} instead of the
|
||||
* datetime types from {@link java.time}, recovering the behavior of
|
||||
* native queries in Hibernate 6 and earlier.
|
||||
* <p>
|
||||
* By default, native queries return {@link java.time.LocalDate},
|
||||
* {@link java.time.LocalTime}, and {@link java.time.LocalDateTime}.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
String NATIVE_PREFER_JDBC_DATETIME_TYPES = "hibernate.query.native.prefer_jdbc_datetime_types";
|
||||
|
||||
/**
|
||||
* When {@linkplain org.hibernate.query.Query#setMaxResults(int) pagination} is used
|
||||
* in combination with a {@code fetch join} applied to a collection or many-valued
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.sql.PreparedStatement;
|
|||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Calendar;
|
||||
|
||||
import jakarta.persistence.TemporalType;
|
||||
|
@ -54,7 +55,9 @@ public class DateJdbcType implements JdbcType {
|
|||
Integer length,
|
||||
Integer scale,
|
||||
TypeConfiguration typeConfiguration) {
|
||||
return typeConfiguration.getJavaTypeRegistry().getDescriptor( Date.class );
|
||||
return typeConfiguration.getCurrentBaseSqlTypeIndicators().preferJdbcDatetimeTypes()
|
||||
? typeConfiguration.getJavaTypeRegistry().getDescriptor( Date.class )
|
||||
: typeConfiguration.getJavaTypeRegistry().getDescriptor( LocalDate.class );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -9,7 +9,6 @@ package org.hibernate.type.descriptor.jdbc;
|
|||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.TemporalType;
|
||||
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.Incubating;
|
||||
import org.hibernate.TimeZoneStorageStrategy;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
|
@ -199,7 +198,7 @@ public interface JdbcTypeIndicators {
|
|||
|
||||
/**
|
||||
* Resolves the given type code to a possibly different type code, based on context.
|
||||
*
|
||||
* <p>
|
||||
* A database might not support a certain type code in certain scenarios like within a UDT
|
||||
* and has to resolve to a different type code in such a scenario.
|
||||
*
|
||||
|
@ -210,6 +209,18 @@ public interface JdbcTypeIndicators {
|
|||
return jdbcTypeCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should native queries return JDBC datetime types
|
||||
* instead of using {@code java.time} types.
|
||||
*
|
||||
* @since 7.0
|
||||
*
|
||||
* @see org.hibernate.cfg.QuerySettings#NATIVE_PREFER_JDBC_DATETIME_TYPES
|
||||
*/
|
||||
default boolean preferJdbcDatetimeTypes() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to the {@link TypeConfiguration} for access to various type system related registries.
|
||||
*/
|
||||
|
@ -228,17 +239,11 @@ public interface JdbcTypeIndicators {
|
|||
* @see SqlTypes#TIME_UTC
|
||||
*/
|
||||
static int getZonedTimeSqlType(TimeZoneStorageStrategy storageStrategy) {
|
||||
switch ( storageStrategy ) {
|
||||
case NATIVE:
|
||||
return SqlTypes.TIME_WITH_TIMEZONE;
|
||||
case COLUMN:
|
||||
case NORMALIZE:
|
||||
return SqlTypes.TIME;
|
||||
case NORMALIZE_UTC:
|
||||
return SqlTypes.TIME_UTC;
|
||||
default:
|
||||
throw new AssertionFailure( "unknown time zone storage strategy" );
|
||||
}
|
||||
return switch (storageStrategy) {
|
||||
case NATIVE -> SqlTypes.TIME_WITH_TIMEZONE;
|
||||
case COLUMN, NORMALIZE -> SqlTypes.TIME;
|
||||
case NORMALIZE_UTC -> SqlTypes.TIME_UTC;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -250,18 +255,13 @@ public interface JdbcTypeIndicators {
|
|||
* @see SqlTypes#TIMESTAMP_UTC
|
||||
*/
|
||||
static int getZonedTimestampSqlType(TimeZoneStorageStrategy storageStrategy) {
|
||||
switch ( storageStrategy ) {
|
||||
case NATIVE:
|
||||
return SqlTypes.TIMESTAMP_WITH_TIMEZONE;
|
||||
case COLUMN:
|
||||
case NORMALIZE:
|
||||
return SqlTypes.TIMESTAMP;
|
||||
case NORMALIZE_UTC:
|
||||
return switch (storageStrategy) {
|
||||
case NATIVE -> SqlTypes.TIMESTAMP_WITH_TIMEZONE;
|
||||
case COLUMN, NORMALIZE -> SqlTypes.TIMESTAMP;
|
||||
case NORMALIZE_UTC ->
|
||||
// sensitive to hibernate.type.preferred_instant_jdbc_type
|
||||
return SqlTypes.TIMESTAMP_UTC;
|
||||
default:
|
||||
throw new AssertionFailure( "unknown time zone storage strategy" );
|
||||
}
|
||||
SqlTypes.TIMESTAMP_UTC;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -286,17 +286,13 @@ public interface JdbcTypeIndicators {
|
|||
*/
|
||||
default int getDefaultZonedTimestampSqlType() {
|
||||
final TemporalType temporalPrecision = getTemporalPrecision();
|
||||
switch ( temporalPrecision == null ? TemporalType.TIMESTAMP : temporalPrecision ) {
|
||||
case TIME:
|
||||
return getZonedTimeSqlType( getDefaultTimeZoneStorageStrategy() );
|
||||
case DATE:
|
||||
return Types.DATE;
|
||||
case TIMESTAMP:
|
||||
return switch (temporalPrecision == null ? TemporalType.TIMESTAMP : temporalPrecision) {
|
||||
case TIME -> getZonedTimeSqlType( getDefaultTimeZoneStorageStrategy() );
|
||||
case DATE -> Types.DATE;
|
||||
case TIMESTAMP ->
|
||||
// sensitive to hibernate.timezone.default_storage
|
||||
return getZonedTimestampSqlType( getDefaultTimeZoneStorageStrategy() );
|
||||
default:
|
||||
throw new IllegalArgumentException( "Unexpected jakarta.persistence.TemporalType : " + temporalPrecision);
|
||||
}
|
||||
getZonedTimestampSqlType( getDefaultTimeZoneStorageStrategy() );
|
||||
};
|
||||
}
|
||||
|
||||
Dialect getDialect();
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.sql.ResultSet;
|
|||
import java.sql.SQLException;
|
||||
import java.sql.Time;
|
||||
import java.sql.Types;
|
||||
import java.time.LocalTime;
|
||||
import java.util.Calendar;
|
||||
|
||||
import jakarta.persistence.TemporalType;
|
||||
|
@ -54,7 +55,9 @@ public class TimeJdbcType implements JdbcType {
|
|||
Integer length,
|
||||
Integer scale,
|
||||
TypeConfiguration typeConfiguration) {
|
||||
return typeConfiguration.getJavaTypeRegistry().getDescriptor( Time.class );
|
||||
return typeConfiguration.getCurrentBaseSqlTypeIndicators().preferJdbcDatetimeTypes()
|
||||
? typeConfiguration.getJavaTypeRegistry().getDescriptor( Time.class )
|
||||
: typeConfiguration.getJavaTypeRegistry().getDescriptor( LocalTime.class );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.sql.ResultSet;
|
|||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import java.sql.Types;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Calendar;
|
||||
|
||||
import jakarta.persistence.TemporalType;
|
||||
|
@ -54,7 +55,9 @@ public class TimestampJdbcType implements JdbcType {
|
|||
Integer length,
|
||||
Integer scale,
|
||||
TypeConfiguration typeConfiguration) {
|
||||
return typeConfiguration.getJavaTypeRegistry().getDescriptor( Timestamp.class );
|
||||
return typeConfiguration.getCurrentBaseSqlTypeIndicators().preferJdbcDatetimeTypes()
|
||||
? typeConfiguration.getJavaTypeRegistry().getDescriptor( Timestamp.class )
|
||||
: typeConfiguration.getJavaTypeRegistry().getDescriptor( LocalDateTime.class );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -482,6 +482,12 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable {
|
|||
: sessionFactory.getJdbcServices().getDialect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preferJdbcDatetimeTypes() {
|
||||
return sessionFactory != null
|
||||
&& sessionFactory.getSessionFactoryOptions().isPreferJdbcDatetimeTypesInNativeQueriesEnabled();
|
||||
}
|
||||
|
||||
private Scope(TypeConfiguration typeConfiguration) {
|
||||
this.typeConfiguration = typeConfiguration;
|
||||
}
|
||||
|
|
|
@ -379,12 +379,11 @@ public class NativeQueryResultTypeAutoDiscoveryTest {
|
|||
|
||||
private Map<Object, Object> buildSettings(Class<?> ... entityTypes) {
|
||||
Map<Object, Object> settings = new HashMap<>();
|
||||
|
||||
settings.put( org.hibernate.cfg.AvailableSettings.HBM2DDL_AUTO, "create-drop" );
|
||||
settings.put( org.hibernate.cfg.AvailableSettings.DIALECT, DIALECT.getClass().getName() );
|
||||
settings.put( AvailableSettings.NATIVE_PREFER_JDBC_DATETIME_TYPES, "true" );
|
||||
settings.put( AvailableSettings.HBM2DDL_AUTO, "create-drop" );
|
||||
settings.put( AvailableSettings.DIALECT, DIALECT.getClass().getName() );
|
||||
settings.put( AvailableSettings.LOADED_CLASSES, Arrays.asList( entityTypes ) );
|
||||
ServiceRegistryUtil.applySettings( settings );
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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.orm.test.jpa.query;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import org.hibernate.dialect.OracleDialect;
|
||||
import org.hibernate.dialect.PostgresPlusDialect;
|
||||
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.Jpa;
|
||||
import org.hibernate.testing.orm.junit.SkipForDialect;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
|
||||
@Jpa(annotatedClasses = NativeQueryWithDatetimesTest.Datetimes.class)
|
||||
public class NativeQueryWithDatetimesTest {
|
||||
@SkipForDialect(dialectClass = PostgresPlusDialect.class)
|
||||
@SkipForDialect(dialectClass = OracleDialect.class)
|
||||
@Test void test(EntityManagerFactoryScope scope) {
|
||||
scope.inTransaction(s -> s.persist(new Datetimes()));
|
||||
Object[] result = scope.fromTransaction(s -> (Object[]) s.createNativeQuery("select ctime, cdate, cdatetime from tdatetimes", Object[].class).getSingleResult());
|
||||
assertInstanceOf(LocalTime.class, result[0]);
|
||||
assertInstanceOf(LocalDate.class, result[1]);
|
||||
assertInstanceOf(LocalDateTime.class, result[2]);
|
||||
// result = scope.fromTransaction(s -> (Object[]) s.createNativeQuery("select current_time, current_date, current_timestamp from tdatetimes", Object[].class).getSingleResult());
|
||||
// assertInstanceOf(LocalTime.class, result[0]);
|
||||
// assertInstanceOf(LocalDate.class, result[1]);
|
||||
// assertInstanceOf(LocalDateTime.class, result[2]);
|
||||
}
|
||||
|
||||
@Entity @Table(name = "tdatetimes")
|
||||
static class Datetimes {
|
||||
@Id
|
||||
long id;
|
||||
@Column(nullable = false, name = "ctime")
|
||||
LocalTime localTime = LocalTime.now();
|
||||
@Column(nullable = false, name = "cdate")
|
||||
LocalDate localDate = LocalDate.now();
|
||||
@Column(nullable = false, name = "cdatetime")
|
||||
LocalDateTime localDateTime = LocalDateTime.now();
|
||||
}
|
||||
}
|
|
@ -6,8 +6,6 @@
|
|||
*/
|
||||
package org.hibernate.orm.test.type;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import org.hibernate.dialect.Dialect;
|
||||
|
@ -39,7 +37,6 @@ import jakarta.persistence.Table;
|
|||
import jakarta.persistence.TypedQuery;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.anyOf;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
|
||||
/**
|
||||
|
@ -164,16 +161,16 @@ public class DateArrayTest {
|
|||
assertThat(
|
||||
tuple[1],
|
||||
is( new Object[] {
|
||||
new Timestamp( Date.valueOf( date1 ).getTime() ),
|
||||
new Timestamp( Date.valueOf( date2 ).getTime() ),
|
||||
new Timestamp( Date.valueOf( date3 ).getTime() )
|
||||
date1,
|
||||
date2,
|
||||
date3
|
||||
} )
|
||||
);
|
||||
}
|
||||
else {
|
||||
assertThat(
|
||||
tuple[1],
|
||||
is( new Date[] { Date.valueOf( date1 ), Date.valueOf( date2 ), Date.valueOf( date3 ) } )
|
||||
is( new LocalDate[] { date1, date2, date3 } )
|
||||
);
|
||||
}
|
||||
} );
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
*/
|
||||
package org.hibernate.orm.test.type;
|
||||
|
||||
import java.sql.Time;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.LocalTime;
|
||||
|
||||
import org.hibernate.dialect.Dialect;
|
||||
|
@ -158,16 +156,16 @@ public class TimeArrayTest {
|
|||
assertThat(
|
||||
tuple[1],
|
||||
is( new Object[] {
|
||||
new Timestamp( Time.valueOf( time1 ).getTime() ),
|
||||
new Timestamp( Time.valueOf( time2 ).getTime() ),
|
||||
new Timestamp( Time.valueOf( time3 ).getTime() )
|
||||
time1,
|
||||
time2,
|
||||
time3
|
||||
} )
|
||||
);
|
||||
}
|
||||
else {
|
||||
assertThat(
|
||||
tuple[1],
|
||||
is( new Time[] { Time.valueOf( time1 ), Time.valueOf( time2 ), Time.valueOf( time3 ) } )
|
||||
is( new LocalTime[] { time1, time2, time3 } )
|
||||
);
|
||||
}
|
||||
} );
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
package org.hibernate.orm.test.type;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Month;
|
||||
|
||||
|
@ -162,19 +161,19 @@ public class TimestampArrayTest {
|
|||
assertThat(
|
||||
tuple[1],
|
||||
is( new Object[] {
|
||||
Timestamp.valueOf( time1 ),
|
||||
Timestamp.valueOf( time2 ),
|
||||
Timestamp.valueOf( time3 )
|
||||
time1,
|
||||
time2,
|
||||
time3
|
||||
} )
|
||||
);
|
||||
}
|
||||
else {
|
||||
assertThat(
|
||||
tuple[1],
|
||||
is( new Timestamp[] {
|
||||
Timestamp.valueOf( time1 ),
|
||||
Timestamp.valueOf( time2 ),
|
||||
Timestamp.valueOf( time3 )
|
||||
is( new LocalDateTime[] {
|
||||
time1,
|
||||
time2,
|
||||
time3
|
||||
} )
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue