Skip to content

Commit 2e6902d

Browse files
committed
HHH-17557 native queries return LocalDate and LocalDateTime instead of java.sql types
... by default, with a setting to recover old behavior.
1 parent 2fc51bd commit 2e6902d

14 files changed

+165
-67
lines changed

hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
209209
private boolean collectionsInDefaultFetchGroupEnabled = true;
210210
private final boolean UnownedAssociationTransientCheck;
211211
private final boolean passProcedureParameterNames;
212+
private final boolean preferJdbcDatetimeTypes;
212213

213214
// JPA callbacks
214215
private final boolean callbacksEnabled;
@@ -635,6 +636,12 @@ else if ( jdbcTimeZoneValue != null ) {
635636
configurationSettings,
636637
false
637638
);
639+
640+
this.preferJdbcDatetimeTypes = ConfigurationHelper.getBoolean(
641+
AvailableSettings.NATIVE_PREFER_JDBC_DATETIME_TYPES,
642+
configurationSettings,
643+
false
644+
);
638645
}
639646

640647
private boolean disallowBatchUpdates(Dialect dialect, ExtractedDatabaseMetaData meta) {
@@ -1326,6 +1333,11 @@ public boolean isPassProcedureParameterNames() {
13261333
return passProcedureParameterNames;
13271334
}
13281335

1336+
@Override
1337+
public boolean isPreferJdbcDatetimeTypesInNativeQueriesEnabled() {
1338+
return preferJdbcDatetimeTypes;
1339+
}
1340+
13291341
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13301342
// In-flight mutation access
13311343

hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,4 +512,9 @@ public FormatMapper getXmlFormatMapper() {
512512
public boolean isPassProcedureParameterNames() {
513513
return delegate.isPassProcedureParameterNames();
514514
}
515+
516+
@Override
517+
public boolean isPreferJdbcDatetimeTypesInNativeQueriesEnabled() {
518+
return delegate.isPreferJdbcDatetimeTypesInNativeQueriesEnabled();
519+
}
515520
}

hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,4 +365,14 @@ default JavaType<Object> getDefaultTenantIdentifierJavaType() {
365365
}
366366

367367
boolean isPassProcedureParameterNames();
368+
369+
/**
370+
* Should native queries return JDBC datetime types
371+
* instead of using {@code java.time} types.
372+
*
373+
* @since 7.0
374+
*
375+
* @see org.hibernate.cfg.QuerySettings#NATIVE_PREFER_JDBC_DATETIME_TYPES
376+
*/
377+
boolean isPreferJdbcDatetimeTypesInNativeQueriesEnabled();
368378
}

hibernate-core/src/main/java/org/hibernate/cfg/QuerySettings.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public interface QuerySettings {
2525
* @since 6.5
2626
*/
2727
String PORTABLE_INTEGER_DIVISION = "hibernate.query.hql.portable_integer_division";
28+
2829
/**
2930
* Specifies a {@link org.hibernate.query.hql.HqlTranslator} to use for HQL query
3031
* translation.
@@ -135,15 +136,28 @@ public interface QuerySettings {
135136
String CRITERIA_COPY_TREE = "hibernate.criteria.copy_tree";
136137

137138
/**
138-
* When set to true, indicates that ordinal parameters (represented by the '?' placeholder) in native queries will be ignored.
139-
* <p>
140-
* By default, this is set to false, i.e. native queries will be checked for ordinal placeholders.
139+
* When enabled, ordinal parameters (represented by the {@code ?} placeholder) in
140+
* native queries will be ignored.
141141
* <p>
142+
* By default, native queries are checked for ordinal placeholders.
142143
*
143144
* @see SessionFactoryOptions#getNativeJdbcParametersIgnored()
144145
*/
145146
String NATIVE_IGNORE_JDBC_PARAMETERS = "hibernate.query.native.ignore_jdbc_parameters";
146147

148+
/**
149+
* When enabled, native queries will return {@link java.sql.Date},
150+
* {@link java.sql.Time}, and {@link java.sql.Timestamp} instead of the
151+
* datetime types from {@link java.time}, recovering the behavior of
152+
* native queries in Hibernate 6 and earlier.
153+
* <p>
154+
* By default, native queries return {@link java.time.LocalDate},
155+
* {@link java.time.LocalTime}, and {@link java.time.LocalDateTime}.
156+
*
157+
* @since 7.0
158+
*/
159+
String NATIVE_PREFER_JDBC_DATETIME_TYPES = "hibernate.query.native.prefer_jdbc_datetime_types";
160+
147161
/**
148162
* When {@linkplain org.hibernate.query.Query#setMaxResults(int) pagination} is used
149163
* in combination with a {@code fetch join} applied to a collection or many-valued

hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/DateJdbcType.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.sql.ResultSet;
1313
import java.sql.SQLException;
1414
import java.sql.Types;
15+
import java.time.LocalDate;
1516
import java.util.Calendar;
1617

1718
import jakarta.persistence.TemporalType;
@@ -54,7 +55,9 @@ public <T> JavaType<T> getJdbcRecommendedJavaTypeMapping(
5455
Integer length,
5556
Integer scale,
5657
TypeConfiguration typeConfiguration) {
57-
return typeConfiguration.getJavaTypeRegistry().getDescriptor( Date.class );
58+
return typeConfiguration.getCurrentBaseSqlTypeIndicators().preferJdbcDatetimeTypes()
59+
? typeConfiguration.getJavaTypeRegistry().getDescriptor( Date.class )
60+
: typeConfiguration.getJavaTypeRegistry().getDescriptor( LocalDate.class );
5861
}
5962

6063
@Override

hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcTypeIndicators.java

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import jakarta.persistence.EnumType;
1010
import jakarta.persistence.TemporalType;
1111

12-
import org.hibernate.AssertionFailure;
1312
import org.hibernate.Incubating;
1413
import org.hibernate.TimeZoneStorageStrategy;
1514
import org.hibernate.dialect.Dialect;
@@ -199,7 +198,7 @@ default JdbcType getJdbcType(int jdbcTypeCode) {
199198

200199
/**
201200
* Resolves the given type code to a possibly different type code, based on context.
202-
*
201+
* <p>
203202
* A database might not support a certain type code in certain scenarios like within a UDT
204203
* and has to resolve to a different type code in such a scenario.
205204
*
@@ -210,6 +209,18 @@ default int resolveJdbcTypeCode(int jdbcTypeCode) {
210209
return jdbcTypeCode;
211210
}
212211

212+
/**
213+
* Should native queries return JDBC datetime types
214+
* instead of using {@code java.time} types.
215+
*
216+
* @since 7.0
217+
*
218+
* @see org.hibernate.cfg.QuerySettings#NATIVE_PREFER_JDBC_DATETIME_TYPES
219+
*/
220+
default boolean preferJdbcDatetimeTypes() {
221+
return false;
222+
}
223+
213224
/**
214225
* Provides access to the {@link TypeConfiguration} for access to various type system related registries.
215226
*/
@@ -221,47 +232,36 @@ private JdbcTypeIndicators getCurrentBaseSqlTypeIndicators() {
221232

222233
/**
223234
* @return the SQL column type used for storing times under the
224-
* given {@linkplain TimeZoneStorageStrategy storage strategy}
235+
* given {@linkplain TimeZoneStorageStrategy storage strategy}
225236
*
226237
* @see SqlTypes#TIME_WITH_TIMEZONE
227238
* @see SqlTypes#TIME
228239
* @see SqlTypes#TIME_UTC
229240
*/
230241
static int getZonedTimeSqlType(TimeZoneStorageStrategy storageStrategy) {
231-
switch ( storageStrategy ) {
232-
case NATIVE:
233-
return SqlTypes.TIME_WITH_TIMEZONE;
234-
case COLUMN:
235-
case NORMALIZE:
236-
return SqlTypes.TIME;
237-
case NORMALIZE_UTC:
238-
return SqlTypes.TIME_UTC;
239-
default:
240-
throw new AssertionFailure( "unknown time zone storage strategy" );
241-
}
242+
return switch (storageStrategy) {
243+
case NATIVE -> SqlTypes.TIME_WITH_TIMEZONE;
244+
case COLUMN, NORMALIZE -> SqlTypes.TIME;
245+
case NORMALIZE_UTC -> SqlTypes.TIME_UTC;
246+
};
242247
}
243248

244249
/**
245250
* @return the SQL column type used for storing datetimes under the
246-
* given {@linkplain TimeZoneStorageStrategy storage strategy}
251+
* given {@linkplain TimeZoneStorageStrategy storage strategy}
247252
*
248253
* @see SqlTypes#TIME_WITH_TIMEZONE
249254
* @see SqlTypes#TIMESTAMP
250255
* @see SqlTypes#TIMESTAMP_UTC
251256
*/
252257
static int getZonedTimestampSqlType(TimeZoneStorageStrategy storageStrategy) {
253-
switch ( storageStrategy ) {
254-
case NATIVE:
255-
return SqlTypes.TIMESTAMP_WITH_TIMEZONE;
256-
case COLUMN:
257-
case NORMALIZE:
258-
return SqlTypes.TIMESTAMP;
259-
case NORMALIZE_UTC:
258+
return switch (storageStrategy) {
259+
case NATIVE -> SqlTypes.TIMESTAMP_WITH_TIMEZONE;
260+
case COLUMN, NORMALIZE -> SqlTypes.TIMESTAMP;
261+
case NORMALIZE_UTC ->
260262
// sensitive to hibernate.type.preferred_instant_jdbc_type
261-
return SqlTypes.TIMESTAMP_UTC;
262-
default:
263-
throw new AssertionFailure( "unknown time zone storage strategy" );
264-
}
263+
SqlTypes.TIMESTAMP_UTC;
264+
};
265265
}
266266

267267
/**
@@ -286,17 +286,13 @@ default int getDefaultZonedTimeSqlType() {
286286
*/
287287
default int getDefaultZonedTimestampSqlType() {
288288
final TemporalType temporalPrecision = getTemporalPrecision();
289-
switch ( temporalPrecision == null ? TemporalType.TIMESTAMP : temporalPrecision ) {
290-
case TIME:
291-
return getZonedTimeSqlType( getDefaultTimeZoneStorageStrategy() );
292-
case DATE:
293-
return Types.DATE;
294-
case TIMESTAMP:
289+
return switch (temporalPrecision == null ? TemporalType.TIMESTAMP : temporalPrecision) {
290+
case TIME -> getZonedTimeSqlType( getDefaultTimeZoneStorageStrategy() );
291+
case DATE -> Types.DATE;
292+
case TIMESTAMP ->
295293
// sensitive to hibernate.timezone.default_storage
296-
return getZonedTimestampSqlType( getDefaultTimeZoneStorageStrategy() );
297-
default:
298-
throw new IllegalArgumentException( "Unexpected jakarta.persistence.TemporalType : " + temporalPrecision);
299-
}
294+
getZonedTimestampSqlType( getDefaultTimeZoneStorageStrategy() );
295+
};
300296
}
301297

302298
Dialect getDialect();

hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/TimeJdbcType.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.sql.SQLException;
1313
import java.sql.Time;
1414
import java.sql.Types;
15+
import java.time.LocalTime;
1516
import java.util.Calendar;
1617

1718
import jakarta.persistence.TemporalType;
@@ -54,7 +55,9 @@ public <T> JavaType<T> getJdbcRecommendedJavaTypeMapping(
5455
Integer length,
5556
Integer scale,
5657
TypeConfiguration typeConfiguration) {
57-
return typeConfiguration.getJavaTypeRegistry().getDescriptor( Time.class );
58+
return typeConfiguration.getCurrentBaseSqlTypeIndicators().preferJdbcDatetimeTypes()
59+
? typeConfiguration.getJavaTypeRegistry().getDescriptor( Time.class )
60+
: typeConfiguration.getJavaTypeRegistry().getDescriptor( LocalTime.class );
5861
}
5962

6063
@Override

hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/TimestampJdbcType.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.sql.SQLException;
1313
import java.sql.Timestamp;
1414
import java.sql.Types;
15+
import java.time.LocalDateTime;
1516
import java.util.Calendar;
1617

1718
import jakarta.persistence.TemporalType;
@@ -54,7 +55,9 @@ public <T> JavaType<T> getJdbcRecommendedJavaTypeMapping(
5455
Integer length,
5556
Integer scale,
5657
TypeConfiguration typeConfiguration) {
57-
return typeConfiguration.getJavaTypeRegistry().getDescriptor( Timestamp.class );
58+
return typeConfiguration.getCurrentBaseSqlTypeIndicators().preferJdbcDatetimeTypes()
59+
? typeConfiguration.getJavaTypeRegistry().getDescriptor( Timestamp.class )
60+
: typeConfiguration.getJavaTypeRegistry().getDescriptor( LocalDateTime.class );
5861
}
5962

6063
@Override

hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,12 @@ public Dialect getDialect() {
482482
: sessionFactory.getJdbcServices().getDialect();
483483
}
484484

485+
@Override
486+
public boolean preferJdbcDatetimeTypes() {
487+
return sessionFactory != null
488+
&& sessionFactory.getSessionFactoryOptions().isPreferJdbcDatetimeTypesInNativeQueriesEnabled();
489+
}
490+
485491
private Scope(TypeConfiguration typeConfiguration) {
486492
this.typeConfiguration = typeConfiguration;
487493
}

hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/NativeQueryResultTypeAutoDiscoveryTest.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -379,12 +379,11 @@ private void createEntityManagerFactory(Class<?> ... entityTypes) {
379379

380380
private Map<Object, Object> buildSettings(Class<?> ... entityTypes) {
381381
Map<Object, Object> settings = new HashMap<>();
382-
383-
settings.put( org.hibernate.cfg.AvailableSettings.HBM2DDL_AUTO, "create-drop" );
384-
settings.put( org.hibernate.cfg.AvailableSettings.DIALECT, DIALECT.getClass().getName() );
382+
settings.put( AvailableSettings.NATIVE_PREFER_JDBC_DATETIME_TYPES, "true" );
383+
settings.put( AvailableSettings.HBM2DDL_AUTO, "create-drop" );
384+
settings.put( AvailableSettings.DIALECT, DIALECT.getClass().getName() );
385385
settings.put( AvailableSettings.LOADED_CLASSES, Arrays.asList( entityTypes ) );
386386
ServiceRegistryUtil.applySettings( settings );
387-
388387
return settings;
389388
}
390389

0 commit comments

Comments
 (0)