HHH-17559 - Prefer Java Time handling for all temporal values

This commit is contained in:
Steve Ebersole 2023-12-15 16:38:13 -06:00
parent 480072d4d1
commit 58173f92ee
63 changed files with 1704 additions and 25 deletions

View File

@ -31,6 +31,7 @@ import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.selector.spi.StrategySelectionException;
import org.hibernate.boot.registry.selector.spi.StrategySelector;
import org.hibernate.boot.spi.BootstrapContext;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.cache.internal.NoCachingRegionFactory;
import org.hibernate.cache.internal.StandardTimestampsCacheFactory;
@ -216,6 +217,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
private final SqmTranslatorFactory sqmTranslatorFactory;
private final Boolean useOfJdbcNamedParametersEnabled;
private boolean namedQueryStartupCheckingEnabled;
private boolean preferJavaTimeJdbcTypes;
private final int preferredSqlTypeCodeForBoolean;
private final int preferredSqlTypeCodeForDuration;
private final int preferredSqlTypeCodeForUuid;
@ -428,6 +430,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
this.useOfJdbcNamedParametersEnabled = configurationService.getSetting( CALLABLE_NAMED_PARAMS_ENABLED, BOOLEAN, true );
this.namedQueryStartupCheckingEnabled = configurationService.getSetting( QUERY_STARTUP_CHECKING, BOOLEAN, true );
this.preferJavaTimeJdbcTypes = MetadataBuildingContext.isPreferJavaTimeJdbcTypesEnabled( configurationService );
this.preferredSqlTypeCodeForBoolean = ConfigurationHelper.getPreferredSqlTypeCodeForBoolean( serviceRegistry );
this.preferredSqlTypeCodeForDuration = ConfigurationHelper.getPreferredSqlTypeCodeForDuration( serviceRegistry );
this.preferredSqlTypeCodeForUuid = ConfigurationHelper.getPreferredSqlTypeCodeForUuid( serviceRegistry );
@ -1245,6 +1248,11 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
return defaultTimeZoneStorageStrategy;
}
@Override
public boolean isPreferJavaTimeJdbcTypesEnabled() {
return preferJavaTimeJdbcTypes;
}
@Override
public FormatMapper getJsonFormatMapper() {
return jsonFormatMapper;

View File

@ -220,6 +220,11 @@ public class BasicValueBinder implements JdbcTypeIndicators {
return temporalPrecision;
}
@Override
public boolean isPreferJavaTimeJdbcTypesEnabled() {
return buildingContext.isPreferJavaTimeJdbcTypesEnabled();
}
@Override
public int getPreferredSqlTypeCodeForBoolean() {
return resolveJdbcTypeCode( buildingContext.getPreferredSqlTypeCodeForBoolean() );

View File

@ -60,6 +60,11 @@ public class VersionResolution<E> implements BasicValue.Resolution<E> {
return TemporalType.TIMESTAMP;
}
@Override
public boolean isPreferJavaTimeJdbcTypesEnabled() {
return context.isPreferJavaTimeJdbcTypesEnabled();
}
@Override
public TimeZoneStorageStrategy getDefaultTimeZoneStorageStrategy() {
return BasicValue.timeZoneStorageStrategy( timeZoneStorageType, context );

View File

@ -477,6 +477,11 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
return delegate.getDefaultTimeZoneStorageStrategy();
}
@Override
public boolean isPreferJavaTimeJdbcTypesEnabled() {
return delegate.isPreferJavaTimeJdbcTypesEnabled();
}
@Override
public FormatMapper getJsonFormatMapper() {
return delegate.getJsonFormatMapper();

View File

@ -9,7 +9,10 @@ package org.hibernate.boot.spi;
import org.hibernate.Incubating;
import org.hibernate.boot.model.TypeDefinitionRegistry;
import org.hibernate.boot.model.naming.ObjectNameNormalizer;
import org.hibernate.cfg.MappingSettings;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.service.ServiceRegistry;
/**
* Describes the context in which the process of building {@link org.hibernate.boot.Metadata}
@ -77,6 +80,24 @@ public interface MetadataBuildingContext {
return ConfigurationHelper.getPreferredSqlTypeCodeForArray( getBootstrapContext().getServiceRegistry() );
}
@Incubating
default boolean isPreferJavaTimeJdbcTypesEnabled() {
return isPreferJavaTimeJdbcTypesEnabled( getBootstrapContext().getServiceRegistry() );
}
static boolean isPreferJavaTimeJdbcTypesEnabled(ServiceRegistry serviceRegistry) {
return isPreferJavaTimeJdbcTypesEnabled( serviceRegistry.getService( ConfigurationService.class ) );
}
static boolean isPreferJavaTimeJdbcTypesEnabled(ConfigurationService configurationService) {
return ConfigurationHelper.getBoolean(
MappingSettings.PREFER_JAVA_TYPE_JDBC_TYPES,
configurationService.getSettings(),
// todo : true would be better eventually so maybe just rip off that band aid
false
);
}
TypeDefinitionRegistry getTypeDefinitionRegistry();
/**

View File

@ -328,6 +328,8 @@ public interface SessionFactoryOptions extends QueryEngineOptions {
@Incubating
TimeZoneStorageStrategy getDefaultTimeZoneStorageStrategy();
boolean isPreferJavaTimeJdbcTypesEnabled();
/**
* The format mapper to use for serializing/deserializing JSON data.
*

View File

@ -18,6 +18,7 @@ import org.hibernate.id.enhanced.ImplicitDatabaseObjectNamingStrategy;
import org.hibernate.id.enhanced.StandardOptimizerDescriptor;
import org.hibernate.metamodel.CollectionClassification;
import org.hibernate.type.WrapperArrayHandling;
import org.hibernate.type.descriptor.jdbc.JavaTimeJdbcType;
import org.hibernate.type.format.FormatMapper;
import jakarta.persistence.Column;
@ -207,7 +208,8 @@ public interface MappingSettings {
* {@link org.hibernate.annotations.JdbcTypeCode}, and friends.
* <p>
* Can also specify the name of the {@link org.hibernate.type.SqlTypes} constant
* field, for example, {@code hibernate.type.preferred_instant_jdbc_type=TIMESTAMP}.
* field, for example, {@code hibernate.type.preferred_instant_jdbc_type=TIMESTAMP}
* or {@code hibernate.type.preferred_instant_jdbc_type=INSTANT}.
*
* @settingDefault {@link org.hibernate.type.SqlTypes#TIMESTAMP_UTC}.
*
@ -216,6 +218,28 @@ public interface MappingSettings {
@Incubating
String PREFERRED_INSTANT_JDBC_TYPE = "hibernate.type.preferred_instant_jdbc_type";
/**
* Indicates whether to use {@linkplain java.time Java Time} references at the JDBC
* boundary for binding and extracting temporal values to/from the database using
* the support added in JDBC 4.2 via {@linkplain java.sql.PreparedStatement#setObject(int, Object, int)}
* and {@linkplain java.sql.ResultSet#getObject(int, Class)}.
* <p/>
* Used to set the value across the entire system as opposed to scattered, individual
* {@linkplain org.hibernate.annotations.JdbcTypeCode} and {@linkplain org.hibernate.annotations.JdbcType}
* naming specific {@linkplain JavaTimeJdbcType} implementations.
*
* @implNote JDBC 4.2 does not define support for {@linkplain java.time.Instant}, so
* {@linkplain java.time.Instant} is not included in this. Some drivers do implement support for this
* even though not explicitly part of the JDBC specification. To use direct binding and extracting of
* {@linkplain java.time.Instant} references, use {@code hibernate.type.preferred_instant_jdbc_type=INSTANT}.
* See {@linkplain #PREFERRED_INSTANT_JDBC_TYPE}, {@linkplain org.hibernate.type.SqlTypes#INSTANT} and
* {@linkplain org.hibernate.type.descriptor.jdbc.InstantJdbcType}.
*
* @since 6.5
*/
@Incubating
String PREFER_JAVA_TYPE_JDBC_TYPES = "hibernate.type.prefer_java_type_jdbc_types";
/**
* Specifies a {@link org.hibernate.type.format.FormatMapper} used for JSON
* serialization and deserialization, either:

View File

@ -46,7 +46,9 @@ public final class Versioning {
public static Object seed(EntityVersionMapping versionMapping, SharedSessionContractImplementor session) {
final Object seed = versionMapping.getJavaType().seed(
versionMapping.getLength(),
versionMapping.getPrecision(),
versionMapping.getTemporalPrecision() != null
? versionMapping.getTemporalPrecision()
: versionMapping.getPrecision(),
versionMapping.getScale(),
session
);
@ -165,7 +167,9 @@ public final class Versioning {
final Object next = versionType.next(
version,
versionMapping.getLength(),
versionMapping.getPrecision(),
versionMapping.getTemporalPrecision() != null
? versionMapping.getTemporalPrecision()
: versionMapping.getPrecision(),
versionMapping.getScale(),
session
);

View File

@ -1105,6 +1105,11 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
return temporalPrecision;
}
@Override
public boolean isPreferJavaTimeJdbcTypesEnabled() {
return getBuildingContext().isPreferJavaTimeJdbcTypesEnabled();
}
@Override
public Object accept(ValueVisitor visitor) {
return visitor.accept(this);

View File

@ -139,6 +139,13 @@ public interface SelectableConsumer {
return null;
}
@Override
public Integer getTemporalPrecision() {
// we could probably use the details from `base`, but
// this method should really never be called on this object
return null;
}
@Override
public String getCustomReadExpression() {
return null;
@ -204,6 +211,11 @@ public interface SelectableConsumer {
return null;
}
@Override
public Integer getTemporalPrecision() {
return null;
}
@Override
public boolean isFormula() {
return false;

View File

@ -126,4 +126,9 @@ public interface SoftDeleteMapping extends SelectableMapping, VirtualModelPart,
default Integer getScale() {
return null;
}
@Override
default Integer getTemporalPrecision() {
return null;
}
}

View File

@ -18,6 +18,7 @@ public interface SqlTypedMapping {
Long getLength();
Integer getPrecision();
Integer getScale();
Integer getTemporalPrecision();
default boolean isLob() {
return getJdbcMapping().getJdbcType().isLob();
}
@ -26,7 +27,12 @@ public interface SqlTypedMapping {
default Size toSize() {
final Size size = new Size();
size.setLength( getLength() );
if ( getTemporalPrecision() != null ) {
size.setPrecision( getTemporalPrecision() );
}
else {
size.setPrecision( getPrecision() );
}
size.setScale( getScale() );
return size;
}

View File

@ -283,6 +283,7 @@ public abstract class AbstractEmbeddableMapping implements EmbeddableMappingType
final Long length;
final Integer precision;
final Integer scale;
final Integer temporalPrecision;
final boolean isLob;
final boolean nullable;
if ( selectable instanceof Column ) {
@ -291,6 +292,7 @@ public abstract class AbstractEmbeddableMapping implements EmbeddableMappingType
length = column.getLength();
precision = column.getPrecision();
scale = column.getScale();
temporalPrecision = column.getTemporalPrecision();
nullable = column.isNullable();
isLob = column.isSqlTypeLob( creationProcess.getCreationContext().getMetadata() );
selectablePath = basicValue.createSelectablePath( column.getQuotedName( dialect ) );
@ -300,6 +302,7 @@ public abstract class AbstractEmbeddableMapping implements EmbeddableMappingType
length = null;
precision = null;
scale = null;
temporalPrecision = null;
nullable = true;
isLob = false;
selectablePath = basicValue.createSelectablePath( bootPropertyDescriptor.getName() );
@ -323,6 +326,7 @@ public abstract class AbstractEmbeddableMapping implements EmbeddableMappingType
length,
precision,
scale,
temporalPrecision,
isLob,
nullable,
value.isColumnInsertable( 0 ),

View File

@ -183,6 +183,11 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions
return precision;
}
@Override
public Integer getTemporalPrecision() {
return null;
}
@Override
public Integer getScale() {
return scale;

View File

@ -152,6 +152,11 @@ public class AnyKeyPart implements BasicValuedModelPart, FetchOptions {
return scale;
}
@Override
public Integer getTemporalPrecision() {
return null;
}
@Override
public JdbcMapping getJdbcMapping() {
return jdbcMapping;

View File

@ -50,6 +50,7 @@ public class BasicAttributeMapping
private final String tableExpression;
private final String mappedColumnExpression;
private final Integer temporalPrecision;
private final SelectablePath selectablePath;
private final boolean isFormula;
private final String customReadExpression;
@ -86,6 +87,7 @@ public class BasicAttributeMapping
Long length,
Integer precision,
Integer scale,
Integer temporalPrecision,
boolean isLob,
boolean nullable,
boolean insertable,
@ -107,6 +109,7 @@ public class BasicAttributeMapping
this.navigableRole = navigableRole;
this.tableExpression = tableExpression;
this.mappedColumnExpression = mappedColumnExpression;
this.temporalPrecision = temporalPrecision;
if ( selectablePath == null ) {
this.selectablePath = new SelectablePath( mappedColumnExpression );
}
@ -186,6 +189,7 @@ public class BasicAttributeMapping
selectableMapping.getLength(),
selectableMapping.getPrecision(),
selectableMapping.getScale(),
selectableMapping.getTemporalPrecision(),
selectableMapping.isLob(),
selectableMapping.isNullable(),
insertable,
@ -292,6 +296,11 @@ public class BasicAttributeMapping
return scale;
}
@Override
public Integer getTemporalPrecision() {
return temporalPrecision;
}
@Override
public String getContainingTableExpression() {
return tableExpression;

View File

@ -357,6 +357,11 @@ public class BasicEntityIdentifierMappingImpl implements BasicEntityIdentifierMa
return precision;
}
@Override
public Integer getTemporalPrecision() {
return null;
}
@Override
public Integer getScale() {
return scale;

View File

@ -131,6 +131,11 @@ public class BasicValuedCollectionPart
return selectableMapping.getPrecision();
}
@Override
public Integer getTemporalPrecision() {
return selectableMapping.getTemporalPrecision();
}
@Override
public Integer getScale() {
return selectableMapping.getScale();

View File

@ -146,6 +146,11 @@ public class CaseStatementDiscriminatorMappingImpl extends AbstractDiscriminator
return null;
}
@Override
public Integer getTemporalPrecision() {
return null;
}
@Override
public Integer getScale() {
return null;

View File

@ -135,6 +135,11 @@ public class CollectionIdentifierDescriptorImpl implements CollectionIdentifierD
return null;
}
@Override
public Integer getTemporalPrecision() {
return null;
}
@Override
public MappingType getPartMappingType() {
return type;

View File

@ -379,6 +379,7 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
final Long length;
final Integer precision;
final Integer scale;
final Integer temporalPrecision;
final boolean isLob;
final boolean nullable;
if ( selectable instanceof Column ) {
@ -387,6 +388,7 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
length = column.getLength();
precision = column.getPrecision();
scale = column.getScale();
temporalPrecision = column.getTemporalPrecision();
isLob = column.isSqlTypeLob( creationProcess.getCreationContext().getMetadata() );
nullable = bootPropertyDescriptor.isOptional() && column.isNullable() ;
selectablePath = basicValue.createSelectablePath( column.getQuotedName( dialect ) );
@ -396,6 +398,7 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
length = null;
precision = null;
scale = null;
temporalPrecision = null;
isLob = false;
nullable = bootPropertyDescriptor.isOptional();
selectablePath = basicValue.createSelectablePath( bootPropertyDescriptor.getName() );
@ -418,6 +421,7 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
length,
precision,
scale,
temporalPrecision,
isLob,
nullable,
insertability[columnPosition],

View File

@ -218,6 +218,11 @@ public class EntityRowIdMappingImpl implements EntityRowIdMapping {
return null;
}
@Override
public Integer getTemporalPrecision() {
return null;
}
@Override
public boolean isFormula() {
return false;

View File

@ -52,6 +52,7 @@ public class EntityVersionMappingImpl implements EntityVersionMapping, FetchOpti
private final Long length;
private final Integer precision;
private final Integer scale;
private final Integer temporalPrecision;
private final BasicType versionBasicType;
@ -69,6 +70,7 @@ public class EntityVersionMappingImpl implements EntityVersionMapping, FetchOpti
Long length,
Integer precision,
Integer scale,
Integer temporalPrecision,
BasicType<?> versionBasicType,
EntityMappingType declaringType,
MappingModelCreationProcess creationProcess) {
@ -77,6 +79,7 @@ public class EntityVersionMappingImpl implements EntityVersionMapping, FetchOpti
this.length = length;
this.precision = precision;
this.scale = scale;
this.temporalPrecision = temporalPrecision;
this.declaringType = declaringType;
this.columnTableExpression = columnTableExpression;
@ -178,6 +181,11 @@ public class EntityVersionMappingImpl implements EntityVersionMapping, FetchOpti
return scale;
}
@Override
public Integer getTemporalPrecision() {
return temporalPrecision;
}
@Override
public MappingType getPartMappingType() {
return versionBasicType;

View File

@ -134,6 +134,11 @@ public class ExplicitColumnDiscriminatorMappingImpl extends AbstractDiscriminato
return scale;
}
@Override
public Integer getTemporalPrecision() {
return null;
}
@Override
public boolean isFormula() {
return columnFormula != null;

View File

@ -194,6 +194,7 @@ public class MappingModelCreationHelper {
Long length,
Integer precision,
Integer scale,
Integer temporalPrecision,
boolean isLob,
boolean nullable,
boolean insertable,
@ -244,6 +245,7 @@ public class MappingModelCreationHelper {
length,
precision,
scale,
temporalPrecision,
isLob,
nullable,
insertable,

View File

@ -45,6 +45,7 @@ public class SelectableMappingImpl extends SqlTypedMappingImpl implements Select
Long length,
Integer precision,
Integer scale,
Integer temporalPrecision,
boolean isLob,
boolean nullable,
boolean insertable,
@ -52,7 +53,7 @@ public class SelectableMappingImpl extends SqlTypedMappingImpl implements Select
boolean partitioned,
boolean isFormula,
JdbcMapping jdbcMapping) {
super( columnDefinition, length, precision, scale, jdbcMapping );
super( columnDefinition, length, precision, scale, temporalPrecision, jdbcMapping );
assert selectionExpression != null;
// Save memory by using interned strings. Probability is high that we have multiple duplicate strings
this.containingTableExpression = containingTableExpression == null ? null : containingTableExpression.intern();
@ -168,6 +169,7 @@ public class SelectableMappingImpl extends SqlTypedMappingImpl implements Select
final Long length;
final Integer precision;
final Integer scale;
final Integer temporalPrecision;
final String selectableName;
final boolean isLob;
final boolean isNullable;
@ -177,6 +179,7 @@ public class SelectableMappingImpl extends SqlTypedMappingImpl implements Select
length = null;
precision = null;
scale = null;
temporalPrecision = null;
isNullable = true;
isLob = false;
selectableName = selectable.getText();
@ -188,6 +191,7 @@ public class SelectableMappingImpl extends SqlTypedMappingImpl implements Select
length = column.getLength();
precision = column.getPrecision();
scale = column.getScale();
temporalPrecision = column.getTemporalPrecision();
isNullable = forceNotNullable ? false : column.isNullable();
isLob = column.isSqlTypeLob( creationContext.getMetadata() );
@ -205,6 +209,7 @@ public class SelectableMappingImpl extends SqlTypedMappingImpl implements Select
length,
precision,
scale,
temporalPrecision,
isLob,
isNullable,
insertable,

View File

@ -663,6 +663,11 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa
return keySide.getModelPart().getScale();
}
@Override
public Integer getTemporalPrecision() {
return keySide.getModelPart().getTemporalPrecision();
}
@Override
public String getFetchableName() {
return PART_NAME;

View File

@ -18,6 +18,7 @@ public class SqlTypedMappingImpl implements SqlTypedMapping {
private final Long length;
private final Integer precision;
private final Integer scale;
private final Integer temporalPrecision;
private final JdbcMapping jdbcMapping;
public SqlTypedMappingImpl(
@ -25,12 +26,14 @@ public class SqlTypedMappingImpl implements SqlTypedMapping {
Long length,
Integer precision,
Integer scale,
Integer temporalPrecision,
JdbcMapping jdbcMapping) {
// Save memory by using interned strings. Probability is high that we have multiple duplicate strings
this.columnDefinition = columnDefinition == null ? null : columnDefinition.intern();
this.length = length;
this.precision = precision;
this.scale = scale;
this.temporalPrecision = temporalPrecision;
this.jdbcMapping = jdbcMapping;
}
@ -49,6 +52,11 @@ public class SqlTypedMappingImpl implements SqlTypedMapping {
return precision;
}
@Override
public Integer getTemporalPrecision() {
return temporalPrecision;
}
@Override
public Integer getScale() {
return scale;

View File

@ -2088,7 +2088,9 @@ public abstract class AbstractEntityPersister
final Object nextVersion = getVersionJavaType().next(
currentVersion,
versionMapping.getLength(),
versionMapping.getPrecision(),
versionMapping.getTemporalPrecision() != null
? versionMapping.getTemporalPrecision()
: versionMapping.getPrecision(),
versionMapping.getScale(),
session
);
@ -5506,6 +5508,7 @@ public abstract class AbstractEntityPersister
column.getLength(),
column.getPrecision(),
column.getScale(),
column.getTemporalPrecision(),
basicTypeResolution.getLegacyResolvedBasicType(),
entityPersister,
creationProcess
@ -5551,6 +5554,7 @@ public abstract class AbstractEntityPersister
column.getLength(),
column.getPrecision(),
column.getScale(),
column.getTemporalPrecision(),
column.isSqlTypeLob( creationProcess.getCreationContext().getMetadata() ),
column.isNullable(),
value.isColumnInsertable( 0 ),
@ -5570,6 +5574,7 @@ public abstract class AbstractEntityPersister
final Long length;
final Integer precision;
final Integer scale;
final Integer temporalPrecision;
final boolean isLob;
final boolean nullable;
@ -5582,6 +5587,7 @@ public abstract class AbstractEntityPersister
columnDefinition = column.getSqlType();
length = column.getLength();
precision = column.getPrecision();
temporalPrecision = column.getTemporalPrecision();
scale = column.getScale();
isLob = column.isSqlTypeLob( creationProcess.getCreationContext().getMetadata() );
nullable = column.isNullable();
@ -5609,6 +5615,7 @@ public abstract class AbstractEntityPersister
columnDefinition = column.getSqlType();
length = column.getLength();
precision = column.getPrecision();
temporalPrecision = column.getTemporalPrecision();
scale = column.getScale();
nullable = column.isNullable();
isLob = column.isSqlTypeLob( creationContext.getMetadata() );
@ -5622,6 +5629,7 @@ public abstract class AbstractEntityPersister
columnDefinition = null;
length = null;
precision = null;
temporalPrecision = null;
scale = null;
nullable = true;
isLob = false;
@ -5646,6 +5654,7 @@ public abstract class AbstractEntityPersister
length,
precision,
scale,
temporalPrecision,
isLob,
nullable,
value.isColumnInsertable( 0 ),

View File

@ -379,6 +379,11 @@ public class EntityTableMapping implements TableMapping {
return null;
}
@Override
public Integer getTemporalPrecision() {
return null;
}
@Override
public String getCustomReadExpression() {
return null;

View File

@ -176,6 +176,11 @@ public class AnonymousTupleBasicValuedModelPart implements OwnedValuedModelPart,
return null;
}
@Override
public Integer getTemporalPrecision() {
return null;
}
@Override
public MappingType getMappedType() {
return this;

View File

@ -899,12 +899,16 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
persister.getVersionJavaType().next(
persister.getVersionJavaType().seed(
versionMapping.getLength(),
versionMapping.getPrecision(),
versionMapping.getTemporalPrecision() != null
? versionMapping.getTemporalPrecision()
: versionMapping.getPrecision(),
versionMapping.getScale(),
null
),
versionMapping.getLength(),
versionMapping.getPrecision(),
versionMapping.getTemporalPrecision() != null
? versionMapping.getTemporalPrecision()
: versionMapping.getPrecision(),
versionMapping.getScale(),
null
),
@ -6072,6 +6076,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
null,
precision,
0,
null,
( (BasicType<?>) bindable ).getJdbcMapping()
);
}
@ -6082,6 +6087,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
null,
bigDecimal.precision(),
bigDecimal.scale(),
null,
( (BasicType<?>) bindable ).getJdbcMapping()
);
}

View File

@ -5755,7 +5755,9 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
parameter.getJdbcMapping(),
sqlTypedMapping.getColumnDefinition(),
sqlTypedMapping.getLength(),
sqlTypedMapping.getPrecision(),
sqlTypedMapping.getTemporalPrecision() != null
? sqlTypedMapping.getTemporalPrecision()
: sqlTypedMapping.getPrecision(),
sqlTypedMapping.getScale()
);
}

View File

@ -61,6 +61,11 @@ public class CastTarget implements Expression, SqlAstNode, SqlTypedMapping {
return precision;
}
@Override
public Integer getTemporalPrecision() {
return null;
}
public Integer getScale() {
return scale;
}

View File

@ -44,7 +44,9 @@ public class VersionTypeSeedParameterSpecification extends AbstractJdbcParameter
statement,
versionMapping.getJavaType().seed(
versionMapping.getLength(),
versionMapping.getPrecision(),
versionMapping.getTemporalPrecision() != null
? versionMapping.getTemporalPrecision()
: versionMapping.getPrecision(),
versionMapping.getScale(),
executionContext.getSession()
),

View File

@ -498,6 +498,80 @@ public class SqlTypes {
*/
public static final int TIME_UTC = 3007;
// Java Time (java.time) "virtual" JdbcTypes
/**
* A type code representing a "virtual mapping" of {@linkplain java.time.Instant}
* as a JDBC type using {@linkplain java.sql.ResultSet#getObject} and
* {@linkplain java.sql.PreparedStatement#setObject} which JDBC requires compliant
* drivers to support.
*
* @see org.hibernate.type.descriptor.jdbc.InstantJdbcType
*/
public static final int INSTANT = 3008;
/**
* A type code representing a "virtual mapping" of {@linkplain java.time.LocalDateTime}
* as a JDBC type using {@linkplain java.sql.ResultSet#getObject} and
* {@linkplain java.sql.PreparedStatement#setObject} which JDBC requires compliant
* drivers to support.
*
* @see org.hibernate.type.descriptor.jdbc.LocalDateTimeJdbcType
*/
public static final int LOCAL_DATE_TIME = 3009;
/**
* A type code representing a "virtual mapping" of {@linkplain java.time.LocalDate}
* as a JDBC type using {@linkplain java.sql.ResultSet#getObject} and
* {@linkplain java.sql.PreparedStatement#setObject} which JDBC requires compliant
* drivers to support.
*
* @see org.hibernate.type.descriptor.jdbc.LocalDateJdbcType
*/
public static final int LOCAL_DATE = 3010;
/**
* A type code representing a "virtual mapping" of {@linkplain java.time.LocalTime}
* as a JDBC type using {@linkplain java.sql.ResultSet#getObject} and
* {@linkplain java.sql.PreparedStatement#setObject} which JDBC requires compliant
* drivers to support.
*
* @see org.hibernate.type.descriptor.jdbc.LocalTimeJdbcType
*/
public static final int LOCAL_TIME = 3011;
/**
* A type code representing a "virtual mapping" of {@linkplain java.time.OffsetDateTime}
* as a JDBC type using {@linkplain java.sql.ResultSet#getObject} and
* {@linkplain java.sql.PreparedStatement#setObject} which JDBC requires compliant
* drivers to support.
*
* @see org.hibernate.type.descriptor.jdbc.OffsetDateTimeJdbcType
*/
public static final int OFFSET_DATE_TIME = 3012;
/**
* A type code representing a "virtual mapping" of {@linkplain java.time.OffsetTime}
* as a JDBC type using {@linkplain java.sql.ResultSet#getObject} and
* {@linkplain java.sql.PreparedStatement#setObject} which JDBC requires compliant
* drivers to support.
*
* @see org.hibernate.type.descriptor.jdbc.OffsetTimeJdbcType
*/
public static final int OFFSET_TIME = 3013;
/**
* A type code representing a "virtual mapping" of {@linkplain java.time.ZonedDateTime}
* as a JDBC type using {@linkplain java.sql.ResultSet#getObject} and
* {@linkplain java.sql.PreparedStatement#setObject} which JDBC requires compliant
* drivers to support.
*
* @see org.hibernate.type.descriptor.jdbc.ZonedDateTimeJdbcType
*/
public static final int ZONED_DATE_TIME = 3014;
// Interval types
/**
@ -766,6 +840,7 @@ public class SqlTypes {
case TIMESTAMP:
case TIMESTAMP_WITH_TIMEZONE:
case TIMESTAMP_UTC:
case INSTANT:
return true;
default:
return false;

View File

@ -20,6 +20,7 @@ import java.util.GregorianCalendar;
import jakarta.persistence.TemporalType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
@ -47,6 +48,9 @@ public class LocalDateJavaType extends AbstractTemporalJavaType<LocalDate> {
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) {
if ( context.isPreferJavaTimeJdbcTypesEnabled() ) {
return context.getJdbcType( SqlTypes.LOCAL_DATE );
}
return context.getJdbcType( Types.DATE );
}

View File

@ -20,6 +20,7 @@ import jakarta.persistence.TemporalType;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
@ -48,6 +49,9 @@ public class LocalDateTimeJavaType extends AbstractTemporalJavaType<LocalDateTim
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) {
if ( context.isPreferJavaTimeJdbcTypesEnabled() ) {
return context.getJdbcType( SqlTypes.LOCAL_DATE_TIME );
}
return context.getJdbcType( Types.TIMESTAMP );
}

View File

@ -24,6 +24,7 @@ import java.util.GregorianCalendar;
import jakarta.persistence.TemporalType;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.DateTimeUtils;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType;
@ -52,6 +53,9 @@ public class LocalTimeJavaType extends AbstractTemporalJavaType<LocalTime> {
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) {
if ( context.isPreferJavaTimeJdbcTypesEnabled() ) {
return context.getJdbcType( SqlTypes.LOCAL_TIME );
}
return context.getJdbcType( Types.TIME );
}

View File

@ -24,6 +24,7 @@ import java.util.GregorianCalendar;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.CharSequenceHelper;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
@ -69,8 +70,10 @@ public class OffsetDateTimeJavaType extends AbstractTemporalJavaType<OffsetDateT
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators stdIndicators) {
return stdIndicators.getTypeConfiguration().getJdbcTypeRegistry()
.getDescriptor( stdIndicators.getDefaultZonedTimestampSqlType() );
if ( stdIndicators.isPreferJavaTimeJdbcTypesEnabled() ) {
return stdIndicators.getJdbcType( SqlTypes.OFFSET_DATE_TIME );
}
return stdIndicators.getJdbcType( stdIndicators.getDefaultZonedTimestampSqlType() );
}
@Override @SuppressWarnings("unchecked")

View File

@ -8,7 +8,6 @@ package org.hibernate.type.descriptor.java;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
@ -21,15 +20,16 @@ import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import jakarta.persistence.TemporalType;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.DateTimeUtils;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.TemporalType;
/**
* Java type descriptor for the {@link OffsetTime} type.
*
@ -52,8 +52,10 @@ public class OffsetTimeJavaType extends AbstractTemporalJavaType<OffsetTime> {
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators stdIndicators) {
return stdIndicators.getTypeConfiguration().getJdbcTypeRegistry()
.getDescriptor( stdIndicators.getDefaultZonedTimeSqlType() );
if ( stdIndicators.isPreferJavaTimeJdbcTypesEnabled() ) {
return stdIndicators.getJdbcType( SqlTypes.OFFSET_TIME );
}
return stdIndicators.getJdbcType( stdIndicators.getDefaultZonedTimeSqlType() );
}
@Override

View File

@ -21,6 +21,7 @@ import jakarta.persistence.TemporalType;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.ZonedDateTimeComparator;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
@ -50,8 +51,10 @@ public class ZonedDateTimeJavaType extends AbstractTemporalJavaType<ZonedDateTim
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators stdIndicators) {
return stdIndicators.getTypeConfiguration().getJdbcTypeRegistry()
.getDescriptor( stdIndicators.getDefaultZonedTimestampSqlType() );
if ( stdIndicators.isPreferJavaTimeJdbcTypesEnabled() ) {
return stdIndicators.getJdbcType( SqlTypes.ZONED_DATE_TIME );
}
return stdIndicators.getJdbcType( stdIndicators.getDefaultZonedTimestampSqlType() );
}
@Override @SuppressWarnings("unchecked")

View File

@ -42,6 +42,11 @@ public class DelegatingJdbcTypeIndicators implements JdbcTypeIndicators {
return delegate.getTemporalPrecision();
}
@Override
public boolean isPreferJavaTimeJdbcTypesEnabled() {
return delegate.isPreferJavaTimeJdbcTypesEnabled();
}
@Override
public int getPreferredSqlTypeCodeForBoolean() {
return delegate.getPreferredSqlTypeCodeForBoolean();

View File

@ -6,15 +6,39 @@
*/
package org.hibernate.type.descriptor.jdbc;
import java.time.Instant;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.internal.AbstractJavaTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterTemporal;
import jakarta.persistence.TemporalType;
/**
* Descriptor for {@link SqlTypes#TIMESTAMP_UTC TIMESTAMP_UTC} handling.
* Descriptor for handling {@linkplain Instant} directly through the JDBC driver
*
* @deprecated Use {@link TimestampUtcAsInstantJdbcType}
* @author Christian Beikov
* @author Steve Ebersole
*/
@Deprecated(forRemoval = true)
public class InstantJdbcType extends TimestampUtcAsInstantJdbcType {
public class InstantJdbcType extends AbstractJavaTimeJdbcType<Instant> {
public static final InstantJdbcType INSTANCE = new InstantJdbcType();
public InstantJdbcType() {
super( Instant.class );
}
@Override
public int getJdbcTypeCode() {
return SqlTypes.INSTANT;
}
@Override
public int getDdlTypeCode() {
return SqlTypes.TIMESTAMP;
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.TIMESTAMP );
}
}

View File

@ -0,0 +1,17 @@
/*
* 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.type.descriptor.jdbc;
/**
* Common marker interface for mapping {@linkplain java.time Java Time} objects
* directly through the JDBC driver.
*
* @author Steve Ebersole
*/
public interface JavaTimeJdbcType extends JdbcType {
}

View File

@ -69,6 +69,13 @@ public interface JdbcTypeIndicators {
return null;
}
/**
* @see org.hibernate.cfg.MappingSettings#PREFER_JAVA_TYPE_JDBC_TYPES
*/
default boolean isPreferJavaTimeJdbcTypesEnabled() {
return getCurrentBaseSqlTypeIndicators().isPreferJavaTimeJdbcTypesEnabled();
}
/**
* When mapping a boolean type to the database what is the preferred SQL type code to use?
* <p>

View File

@ -0,0 +1,44 @@
/*
* 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.type.descriptor.jdbc;
import java.time.LocalDate;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.internal.AbstractJavaTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterTemporal;
import jakarta.persistence.TemporalType;
/**
* Descriptor for handling {@linkplain LocalDate} directly through the JDBC driver
*
* @author Steve Ebersole
*/
public class LocalDateJdbcType extends AbstractJavaTimeJdbcType<LocalDate> {
public static LocalDateJdbcType INSTANCE = new LocalDateJdbcType();
public LocalDateJdbcType() {
super( LocalDate.class );
}
@Override
public int getJdbcTypeCode() {
return SqlTypes.LOCAL_DATE;
}
@Override
public int getDdlTypeCode() {
return SqlTypes.DATE;
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.DATE );
}
}

View File

@ -0,0 +1,48 @@
/*
* 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.type.descriptor.jdbc;
import java.time.LocalDateTime;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.internal.AbstractJavaTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterTemporal;
import org.hibernate.type.descriptor.jdbc.internal.SetObjectBinder;
import jakarta.persistence.TemporalType;
/**
* Descriptor for handling {@linkplain LocalDateTime} directly through the JDBC driver
*
* @author Steve Ebersole
*/
public class LocalDateTimeJdbcType extends AbstractJavaTimeJdbcType<LocalDateTime> {
public static LocalDateTimeJdbcType INSTANCE = new LocalDateTimeJdbcType();
public LocalDateTimeJdbcType() {
super( LocalDateTime.class );
}
@Override
public int getJdbcTypeCode() {
return SqlTypes.LOCAL_DATE_TIME;
}
@Override
public int getDdlTypeCode() {
return SqlTypes.TIMESTAMP;
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.TIMESTAMP );
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.type.descriptor.jdbc;
import java.time.LocalTime;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.internal.AbstractJavaTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterTemporal;
import jakarta.persistence.TemporalType;
/**
* Descriptor for handling {@linkplain LocalTime} directly through the JDBC driver
*
* @author Steve Ebersole
*/
public class LocalTimeJdbcType extends AbstractJavaTimeJdbcType<LocalTime> {
public static LocalTimeJdbcType INSTANCE = new LocalTimeJdbcType();
public LocalTimeJdbcType() {
super( LocalTime.class );
}
@Override
public int getJdbcTypeCode() {
return SqlTypes.LOCAL_TIME;
}
@Override
public int getDdlTypeCode() {
return SqlTypes.TIME;
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.TIME );
}
}

View File

@ -0,0 +1,45 @@
/*
* 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.type.descriptor.jdbc;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.internal.AbstractJavaTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterTemporal;
import jakarta.persistence.TemporalType;
/**
* Descriptor for handling {@linkplain OffsetDateTime} directly through the JDBC driver
*
* @author Steve Ebersole
*/
public class OffsetDateTimeJdbcType extends AbstractJavaTimeJdbcType<OffsetDateTime> {
public static OffsetDateTimeJdbcType INSTANCE = new OffsetDateTimeJdbcType();
public OffsetDateTimeJdbcType() {
super( OffsetDateTime.class );
}
@Override
public int getJdbcTypeCode() {
return SqlTypes.OFFSET_DATE_TIME;
}
@Override
public int getDdlTypeCode() {
return SqlTypes.TIMESTAMP_WITH_TIMEZONE;
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.TIMESTAMP );
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.type.descriptor.jdbc;
import java.time.OffsetTime;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.internal.AbstractJavaTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterTemporal;
import jakarta.persistence.TemporalType;
/**
* Descriptor for handling {@linkplain OffsetTime} directly through the JDBC driver
*
* @author Steve Ebersole
*/
public class OffsetTimeJdbcType extends AbstractJavaTimeJdbcType<OffsetTime> {
public static OffsetTimeJdbcType INSTANCE = new OffsetTimeJdbcType();
public OffsetTimeJdbcType() {
super( OffsetTime.class );
}
@Override
public int getJdbcTypeCode() {
return SqlTypes.OFFSET_TIME;
}
@Override
public int getDdlTypeCode() {
return SqlTypes.TIME_WITH_TIMEZONE;
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.TIME );
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.type.descriptor.jdbc;
import java.time.ZonedDateTime;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.internal.AbstractJavaTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterTemporal;
import jakarta.persistence.TemporalType;
/**
* Descriptor for handling {@linkplain ZonedDateTime} directly through the JDBC driver
*
* @author Steve Ebersole
*/
public class ZonedDateTimeJdbcType extends AbstractJavaTimeJdbcType<ZonedDateTime> {
public static ZonedDateTimeJdbcType INSTANCE = new ZonedDateTimeJdbcType();
public ZonedDateTimeJdbcType() {
super( ZonedDateTime.class );
}
@Override
public int getJdbcTypeCode() {
return SqlTypes.ZONED_DATE_TIME;
}
@Override
public int getDdlTypeCode() {
return SqlTypes.TIMESTAMP_WITH_TIMEZONE;
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.TIMESTAMP );
}
}

View File

@ -0,0 +1,50 @@
/*
* 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.type.descriptor.jdbc.internal;
import java.time.temporal.Temporal;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JavaTimeJdbcType;
import org.hibernate.type.spi.TypeConfiguration;
/**
* @author Steve Ebersole
*/
public abstract class AbstractJavaTimeJdbcType<T extends Temporal> implements JavaTimeJdbcType {
private final Class<T> javaTimeType;
public AbstractJavaTimeJdbcType(Class<T> javaTimeType) {
this.javaTimeType = javaTimeType;
}
@Override
public Class<T> getPreferredJavaTypeClass(WrapperOptions options) {
return javaTimeType;
}
@Override
public <X> JavaType<X> getJdbcRecommendedJavaTypeMapping(
Integer precision,
Integer scale,
TypeConfiguration typeConfiguration) {
return typeConfiguration.getJavaTypeRegistry().getDescriptor( javaTimeType );
}
@Override
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
return new SetObjectBinder<>( javaType, this, javaTimeType, getDdlTypeCode() );
}
@Override
public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) {
return new GetObjectExtractor<>( javaType, this, javaTimeType );
}
}

View File

@ -0,0 +1,48 @@
/*
* 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.type.descriptor.jdbc.internal;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.BasicExtractor;
import org.hibernate.type.descriptor.jdbc.JavaTimeJdbcType;
/**
* Support for extracting values directly through `getObject` JDBC driver calls.
*
* @author Steve Ebersole
*/
public class GetObjectExtractor<T> extends BasicExtractor<T> {
private final Class<?> baseClass;
public GetObjectExtractor(JavaType<T> javaType, JavaTimeJdbcType jdbcType, Class<?> baseClass) {
super( javaType, jdbcType );
this.baseClass = baseClass;
}
@Override
protected T doExtract(
ResultSet rs,
int paramIndex,
WrapperOptions options) throws SQLException {
return getJavaType().wrap( rs.getObject( paramIndex, baseClass ), options );
}
@Override
protected T doExtract(CallableStatement statement, int paramIndex, WrapperOptions options) throws SQLException {
return getJavaType().wrap( statement.getObject( paramIndex, baseClass ), options );
}
@Override
protected T doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return getJavaType().wrap( statement.getObject( name, baseClass ), options );
}
}

View File

@ -19,11 +19,17 @@ import org.hibernate.type.descriptor.jdbc.DateJdbcType;
import org.hibernate.type.descriptor.jdbc.DecimalJdbcType;
import org.hibernate.type.descriptor.jdbc.DoubleJdbcType;
import org.hibernate.type.descriptor.jdbc.FloatJdbcType;
import org.hibernate.type.descriptor.jdbc.InstantJdbcType;
import org.hibernate.type.descriptor.jdbc.IntegerJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.LocalDateJdbcType;
import org.hibernate.type.descriptor.jdbc.LocalDateTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.LocalTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.LongVarbinaryJdbcType;
import org.hibernate.type.descriptor.jdbc.LongVarcharJdbcType;
import org.hibernate.type.descriptor.jdbc.NumericJdbcType;
import org.hibernate.type.descriptor.jdbc.OffsetDateTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.OffsetTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.RealJdbcType;
import org.hibernate.type.descriptor.jdbc.RowIdJdbcType;
import org.hibernate.type.descriptor.jdbc.SmallIntJdbcType;
@ -34,6 +40,7 @@ import org.hibernate.type.descriptor.jdbc.TimestampWithTimeZoneJdbcType;
import org.hibernate.type.descriptor.jdbc.TinyIntJdbcType;
import org.hibernate.type.descriptor.jdbc.VarbinaryJdbcType;
import org.hibernate.type.descriptor.jdbc.VarcharJdbcType;
import org.hibernate.type.descriptor.jdbc.ZonedDateTimeJdbcType;
/**
* Registers the base {@link JdbcType} instances.
@ -61,6 +68,14 @@ public class JdbcTypeBaseline {
target.addDescriptor( SmallIntJdbcType.INSTANCE );
target.addDescriptor( TinyIntJdbcType.INSTANCE );
target.addDescriptor( InstantJdbcType.INSTANCE );
target.addDescriptor( LocalDateTimeJdbcType.INSTANCE );
target.addDescriptor( LocalDateJdbcType.INSTANCE );
target.addDescriptor( LocalTimeJdbcType.INSTANCE );
target.addDescriptor( OffsetDateTimeJdbcType.INSTANCE );
target.addDescriptor( OffsetTimeJdbcType.INSTANCE );
target.addDescriptor( ZonedDateTimeJdbcType.INSTANCE );
target.addDescriptor( DateJdbcType.INSTANCE );
target.addDescriptor( TimestampJdbcType.INSTANCE );
target.addDescriptor( TimestampWithTimeZoneJdbcType.INSTANCE );

View File

@ -0,0 +1,60 @@
/*
* 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.type.descriptor.jdbc.internal;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.BasicBinder;
import org.hibernate.type.descriptor.jdbc.JdbcType;
/**
* Support for binding values directly through `setObject` JDBC driver calls.
*
* @author Steve Ebersole
*/
public class SetObjectBinder<T> extends BasicBinder<T> {
private final Class<?> baseClass;
private final int jdbcTypeCode;
public SetObjectBinder(
JavaType<T> javaType,
JdbcType jdbcType,
Class<?> baseClass,
int jdbcTypeCode) {
super( javaType, jdbcType );
this.baseClass = baseClass;
this.jdbcTypeCode = jdbcTypeCode;
}
@Override
protected void doBind(PreparedStatement st, T value, int index, WrapperOptions options) throws SQLException {
st.setObject( index, normalize( value, options ), jdbcTypeCode );
}
protected Object normalize(T value, WrapperOptions options) {
return getJavaType().unwrap( value, baseClass, options );
}
@Override
protected void doBind(CallableStatement st, T value, String name, WrapperOptions options) throws SQLException {
st.setObject( name, normalize( value, options ), jdbcTypeCode );
}
@Override
protected void doBindNull(PreparedStatement st, int index, WrapperOptions options) throws SQLException {
st.setNull( index, jdbcTypeCode );
}
@Override
protected void doBindNull(CallableStatement st, String name, WrapperOptions options) throws SQLException {
st.setNull( name, jdbcTypeCode );
}
}

View File

@ -416,6 +416,13 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable {
return typeConfiguration;
}
@Override
public boolean isPreferJavaTimeJdbcTypesEnabled() {
return sessionFactory == null
? metadataBuildingContext.isPreferJavaTimeJdbcTypesEnabled()
: sessionFactory.getSessionFactoryOptions().isPreferJavaTimeJdbcTypesEnabled();
}
@Override
public TimeZoneStorageStrategy getDefaultTimeZoneStorageStrategy() {
return sessionFactory == null

View File

@ -0,0 +1,230 @@
/*
* 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.mapping.javatime;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.cfg.MappingSettings;
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.DerbyDialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.SybaseDialect;
import org.hibernate.mapping.BasicValue;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Value;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.LocalDateJavaType;
import org.hibernate.type.descriptor.jdbc.JavaTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.LocalDateJdbcType;
import org.hibernate.type.descriptor.jdbc.LocalDateTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.LocalTimeJdbcType;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.DomainModelScope;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.type.descriptor.DateTimeUtils.roundToDefaultPrecision;
/**
* Tests for "direct" JDBC handling of {@linkplain java.time Java Time} types.
*
* @author Steve Ebersole
*/
@SuppressWarnings("JUnitMalformedDeclaration")
@ServiceRegistry(
settings = @Setting(name = MappingSettings.PREFER_JAVA_TYPE_JDBC_TYPES, value = "true")
)
@DomainModel( annotatedClasses = GlobalJavaTimeJdbcTypeTests.EntityWithJavaTimeValues.class )
@SessionFactory
@SkipForDialect( dialectClass = SybaseDialect.class, reason = "Sybase drivers do not comply with JDBC 4.2 requirements for support of Java Time objects", matchSubTypes = true )
@SkipForDialect( dialectClass = DB2Dialect.class, reason = "DB2 drivers do not comply with JDBC 4.2 requirements for support of Java Time objects", matchSubTypes = true )
@SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby drivers do not comply with JDBC 4.2 requirements for support of Java Time objects" )
public class GlobalJavaTimeJdbcTypeTests {
@Test
void testMappings(DomainModelScope scope) {
final PersistentClass entityBinding = scope.getEntityBinding( EntityWithJavaTimeValues.class );
checkAttribute( entityBinding, "theLocalDate", LocalDateJdbcType.class );
checkAttribute( entityBinding, "theLocalDateTime", LocalDateTimeJdbcType.class );
checkAttribute( entityBinding, "theLocalTime", LocalTimeJdbcType.class );
}
private void checkAttribute(
PersistentClass entityBinding,
String attributeName,
Class<? extends JavaTimeJdbcType> expectedJdbcTypeDescriptorType) {
final Property property = entityBinding.getProperty( attributeName );
final BasicValue value = (BasicValue) property.getValue();
final BasicValue.Resolution<?> resolution = value.resolve();
final JdbcType jdbcType = resolution.getJdbcType();
assertThat( jdbcType ).isInstanceOf( expectedJdbcTypeDescriptorType );
}
@Test
void testInstant(SessionFactoryScope scope) {
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final Instant start = roundToDefaultPrecision( Instant.EPOCH, dialect );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
entity.id = 1;
entity.theInstant = start;
session.persist( entity );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theInstant ).isEqualTo( start );
entity.theInstant = start.plus( 2000, ChronoUnit.DAYS );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theInstant ).isEqualTo( start.plus( 2000, ChronoUnit.DAYS ) );
entity.theInstant = start.minus( 2000, ChronoUnit.DAYS );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theInstant ).isEqualTo( start.minus( 2000, ChronoUnit.DAYS ) );
} );
}
@Test
void testLocalDateTime(SessionFactoryScope scope) {
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final LocalDateTime start = roundToDefaultPrecision( LocalDateTime.now(), dialect );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
entity.id = 1;
entity.theLocalDateTime = start;
session.persist( entity );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theLocalDateTime ).isEqualTo( start );
entity.theLocalDateTime = start.plusDays( 2000 );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theLocalDateTime ).isEqualTo( start.plusDays( 2000 ) );
entity.theLocalDateTime = start.minusDays( 2000 );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theLocalDateTime ).isEqualTo( start.minusDays( 2000 ) );
} );
}
@Test
void testLocalDate(SessionFactoryScope scope) {
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final LocalDate startTime = roundToDefaultPrecision( LocalDate.now(), dialect );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
entity.id = 1;
entity.theLocalDate = startTime;
session.persist( entity );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theLocalDate ).isEqualTo( startTime );
entity.theLocalDate = startTime.plusDays( 2000 );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theLocalDate ).isEqualTo( startTime.plusDays( 2000 ) );
entity.theLocalDate = startTime.minusDays( 2000 );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theLocalDate ).isEqualTo( startTime.minusDays( 2000 ) );
} );
}
@Test
@SkipForDialect( dialectClass = OracleDialect.class, reason = "Oracle drivers truncate fractional seconds from the LocalTime", matchSubTypes = true)
void testLocalTime(SessionFactoryScope scope) {
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final LocalTime startTime = roundToDefaultPrecision( LocalTime.now(), dialect );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
entity.id = 1;
entity.theLocalTime = startTime;
session.persist( entity );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theLocalTime ).isEqualTo( startTime );
entity.theLocalTime = startTime.plusHours( 2000 );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theLocalTime ).isEqualTo( startTime.plusHours( 2000 ) );
entity.theLocalTime = startTime.plusHours( 2000 );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theLocalTime ).isEqualTo( startTime.plusHours( 2000 ) );
} );
}
@AfterEach
void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
session.createMutationQuery( "delete EntityWithJavaTimeValues" ).executeUpdate();
} );
}
@Entity(name="EntityWithJavaTimeValues")
@Table(name="EntityWithJavaTimeValues")
public static class EntityWithJavaTimeValues {
@Id
private Integer id;
private String name;
private Instant theInstant;
private LocalDateTime theLocalDateTime;
private LocalDate theLocalDate;
private LocalTime theLocalTime;
}
}

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.orm.test.mapping.javatime;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.cfg.MappingSettings;
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.DerbyDialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.SybaseDialect;
import org.hibernate.mapping.BasicValue;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.jdbc.DateJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.TimeJdbcType;
import org.hibernate.type.descriptor.jdbc.TimestampJdbcType;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.DomainModelScope;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.type.descriptor.DateTimeUtils.roundToDefaultPrecision;
/**
* Tests for "direct" JDBC handling of {@linkplain java.time Java Time} types.
*
* @author Steve Ebersole
*/
@SuppressWarnings("JUnitMalformedDeclaration")
@ServiceRegistry(
settings = @Setting(name = MappingSettings.PREFER_JAVA_TYPE_JDBC_TYPES, value = "false")
)
@DomainModel( annotatedClasses = JavaTimeJdbcTypeBaselineTests.EntityWithJavaTimeValues.class )
@SessionFactory
public class JavaTimeJdbcTypeBaselineTests {
@Test
void testMappings(DomainModelScope scope) {
final PersistentClass entityBinding = scope.getEntityBinding( EntityWithJavaTimeValues.class );
checkAttribute( entityBinding, "theLocalDate", DateJdbcType.class );
checkAttribute( entityBinding, "theLocalDateTime", TimestampJdbcType.class );
checkAttribute( entityBinding, "theLocalTime", TimeJdbcType.class );
}
private void checkAttribute(
PersistentClass entityBinding,
String attributeName,
Class<?> expectedJdbcTypeDescriptorType) {
final Property property = entityBinding.getProperty( attributeName );
final BasicValue value = (BasicValue) property.getValue();
final BasicValue.Resolution<?> resolution = value.resolve();
final JdbcType jdbcType = resolution.getJdbcType();
assertThat( jdbcType ).isInstanceOf( expectedJdbcTypeDescriptorType );
}
@Entity(name="EntityWithJavaTimeValues")
@Table(name="EntityWithJavaTimeValues")
public static class EntityWithJavaTimeValues {
@Id
private Integer id;
private String name;
private Instant theInstant;
private LocalDateTime theLocalDateTime;
private LocalDate theLocalDate;
private LocalTime theLocalTime;
}
}

View File

@ -0,0 +1,234 @@
/*
* 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.mapping.javatime;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.cfg.MappingSettings;
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.DerbyDialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.SybaseDialect;
import org.hibernate.mapping.BasicValue;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.jdbc.DateJdbcType;
import org.hibernate.type.descriptor.jdbc.JavaTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.LocalDateJdbcType;
import org.hibernate.type.descriptor.jdbc.LocalDateTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.LocalTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.TimeJdbcType;
import org.hibernate.type.descriptor.jdbc.TimestampJdbcType;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.DomainModelScope;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.type.descriptor.DateTimeUtils.roundToDefaultPrecision;
/**
* Tests for "direct" JDBC handling of {@linkplain java.time Java Time} types.
*
* @author Steve Ebersole
*/
@SuppressWarnings("JUnitMalformedDeclaration")
@ServiceRegistry(
settings = @Setting(name = MappingSettings.PREFER_JAVA_TYPE_JDBC_TYPES, value = "false")
)
@DomainModel( annotatedClasses = JavaTimeJdbcTypeTests.EntityWithJavaTimeValues.class )
@SessionFactory
@SkipForDialect( dialectClass = SybaseDialect.class, reason = "Sybase drivers do not comply with JDBC 4.2 requirements for support of Java Time objects", matchSubTypes = true )
@SkipForDialect( dialectClass = DB2Dialect.class, reason = "DB2 drivers do not comply with JDBC 4.2 requirements for support of Java Time objects", matchSubTypes = true )
@SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby drivers do not comply with JDBC 4.2 requirements for support of Java Time objects" )
public class JavaTimeJdbcTypeTests {
@Test
void testMappings(DomainModelScope scope) {
final PersistentClass entityBinding = scope.getEntityBinding( EntityWithJavaTimeValues.class );
checkAttribute( entityBinding, "theLocalDate", LocalDateJdbcType.class );
checkAttribute( entityBinding, "theLocalDateTime", LocalDateTimeJdbcType.class );
checkAttribute( entityBinding, "theLocalTime", LocalTimeJdbcType.class );
}
private void checkAttribute(
PersistentClass entityBinding,
String attributeName,
Class<?> expectedJdbcTypeDescriptorType) {
final Property property = entityBinding.getProperty( attributeName );
final BasicValue value = (BasicValue) property.getValue();
final BasicValue.Resolution<?> resolution = value.resolve();
final JdbcType jdbcType = resolution.getJdbcType();
assertThat( jdbcType ).isInstanceOf( expectedJdbcTypeDescriptorType );
}
@Test
void testInstant(SessionFactoryScope scope) {
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final Instant start = roundToDefaultPrecision( Instant.EPOCH, dialect );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
entity.id = 1;
entity.theInstant = start;
session.persist( entity );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theInstant ).isEqualTo( start );
entity.theInstant = start.plus( 2000, ChronoUnit.DAYS );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theInstant ).isEqualTo( start.plus( 2000, ChronoUnit.DAYS ) );
entity.theInstant = start.minus( 2000, ChronoUnit.DAYS );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theInstant ).isEqualTo( start.minus( 2000, ChronoUnit.DAYS ) );
} );
}
@Test
void testLocalDateTime(SessionFactoryScope scope) {
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final LocalDateTime start = roundToDefaultPrecision( LocalDateTime.now(), dialect );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
entity.id = 1;
entity.theLocalDateTime = start;
session.persist( entity );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theLocalDateTime ).isEqualTo( start );
entity.theLocalDateTime = start.plusDays( 2000 );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theLocalDateTime ).isEqualTo( start.plusDays( 2000 ) );
entity.theLocalDateTime = start.minusDays( 2000 );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theLocalDateTime ).isEqualTo( start.minusDays( 2000 ) );
} );
}
@Test
void testLocalDate(SessionFactoryScope scope) {
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final LocalDate startTime = roundToDefaultPrecision( LocalDate.now(), dialect );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
entity.id = 1;
entity.theLocalDate = startTime;
session.persist( entity );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theLocalDate ).isEqualTo( startTime );
entity.theLocalDate = startTime.plusDays( 2000 );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theLocalDate ).isEqualTo( startTime.plusDays( 2000 ) );
entity.theLocalDate = startTime.minusDays( 2000 );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theLocalDate ).isEqualTo( startTime.minusDays( 2000 ) );
} );
}
@Test
@SkipForDialect( dialectClass = OracleDialect.class, reason = "Oracle drivers truncate fractional seconds from the LocalTime", matchSubTypes = true)
void testLocalTime(SessionFactoryScope scope) {
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final LocalTime startTime = roundToDefaultPrecision( LocalTime.now(), dialect );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
entity.id = 1;
entity.theLocalTime = startTime;
session.persist( entity );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theLocalTime ).isEqualTo( startTime );
entity.theLocalTime = startTime.plusHours( 2000 );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theLocalTime ).isEqualTo( startTime.plusHours( 2000 ) );
entity.theLocalTime = startTime.plusHours( 2000 );
} );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = session.get( EntityWithJavaTimeValues.class, 1 );
assertThat( entity.theLocalTime ).isEqualTo( startTime.plusHours( 2000 ) );
} );
}
@AfterEach
void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
session.createMutationQuery( "delete EntityWithJavaTimeValues" ).executeUpdate();
} );
}
@Entity(name="EntityWithJavaTimeValues")
@Table(name="EntityWithJavaTimeValues")
public static class EntityWithJavaTimeValues {
@Id
private Integer id;
private String name;
// @JdbcTypeCode(SqlTypes.INSTANT)
private Instant theInstant;
@JdbcTypeCode(SqlTypes.LOCAL_DATE_TIME)
private LocalDateTime theLocalDateTime;
@JdbcTypeCode(SqlTypes.LOCAL_DATE)
private LocalDate theLocalDate;
@JdbcTypeCode(SqlTypes.LOCAL_TIME)
private LocalTime theLocalTime;
}
}

View File

@ -0,0 +1,274 @@
/*
* 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.temporal;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZonedDateTime;
import org.hibernate.annotations.FractionalSeconds;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.DerbyDialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseDialect;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.mapping.BasicValue;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.DateTimeUtils;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.DomainModelScope;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Steve Ebersole
*/
@SuppressWarnings("JUnitMalformedDeclaration")
public class JavaTimeFractionalSecondsTests {
@Test
@DomainModel(annotatedClasses = {TestEntity.class, TestEntity0.class, TestEntity3.class, TestEntity9.class} )
void testMapping(DomainModelScope scope) {
final MetadataImplementor domainModel = scope.getDomainModel();
final Dialect dialect = domainModel.getDatabase().getDialect();
final int defaultPrecision = dialect.getDefaultTimestampPrecision();
final PersistentClass entityBinding = scope.getEntityBinding( TestEntity.class );
checkPrecision( "theInstant", defaultPrecision, entityBinding, domainModel );
checkPrecision( "theLocalDateTime", defaultPrecision, entityBinding, domainModel );
checkPrecision( "theLocalTime", defaultPrecision, entityBinding, domainModel );
checkPrecision( "theOffsetDateTime", defaultPrecision, entityBinding, domainModel );
checkPrecision( "theOffsetTime", defaultPrecision, entityBinding, domainModel );
checkPrecision( "theZonedDateTime", defaultPrecision, entityBinding, domainModel );
final PersistentClass entityBinding0 = scope.getEntityBinding( TestEntity0.class );
checkPrecision( "theInstant", 0, entityBinding0, domainModel );
final PersistentClass entityBinding3 = scope.getEntityBinding( TestEntity3.class );
checkPrecision( "theInstant", 3, entityBinding3, domainModel );
checkPrecision( "theLocalDateTime", 3, entityBinding3, domainModel );
checkPrecision( "theLocalTime", 3, entityBinding3, domainModel );
final PersistentClass entityBinding9 = scope.getEntityBinding( TestEntity9.class );
checkPrecision( "theInstant", 9, entityBinding9, domainModel );
checkPrecision( "theOffsetDateTime", 9, entityBinding9, domainModel );
checkPrecision( "theOffsetTime", 9, entityBinding9, domainModel );
checkPrecision( "theZonedDateTime", 9, entityBinding9, domainModel );
}
private void checkPrecision(
String propertyName,
int expectedMinimumSize,
PersistentClass entityBinding,
MetadataImplementor domainModel) {
final Property theInstant = entityBinding.getProperty( propertyName );
final BasicValue value = (BasicValue) theInstant.getValue();
final Column column = (Column) value.getColumn();
final Size columnSize = column.getColumnSize( value.getDialect(), domainModel );
assertThat( columnSize.getPrecision() ).isEqualTo( expectedMinimumSize );
}
@Test
@DomainModel(annotatedClasses = TestEntity.class)
@SessionFactory
@SkipForDialect( dialectClass = DB2Dialect.class, reason = "Occasional mismatch in rounding versus our code" )
@SkipForDialect(dialectClass = SybaseDialect.class, reason = "Because... Sybase...", matchSubTypes = true)
void testUsage(SessionFactoryScope scope) {
final Instant start = Instant.now();
scope.inTransaction( (session) -> {
final TestEntity testEntity = new TestEntity();
testEntity.id = 1;
testEntity.theInstant = start;
session.persist( testEntity );
} );
scope.inTransaction( (session) -> {
final TestEntity testEntity = session.find( TestEntity.class, 1 );
final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect();
if ( dialect instanceof DerbyDialect
|| dialect instanceof MariaDBDialect ) {
assertThat( testEntity.theInstant ).isEqualTo( start );
}
else {
assertThat( testEntity.theInstant ).isEqualTo( DateTimeUtils.roundToSecondPrecision( start, 6 ) );
}
} );
}
@Test
@DomainModel(annotatedClasses = TestEntity0.class)
@SessionFactory
@SkipForDialect( dialectClass = H2Dialect.class, reason = "Occasional mismatch in rounding versus our code" )
@SkipForDialect( dialectClass = MariaDBDialect.class, reason = "Occasional mismatch in rounding versus our code" )
@SkipForDialect( dialectClass = MySQLDialect.class, reason = "Occasional mismatch in rounding versus our code", matchSubTypes = true )
@SkipForDialect( dialectClass = OracleDialect.class, reason = "Occasional mismatch in rounding versus our code" )
@SkipForDialect( dialectClass = SQLServerDialect.class, reason = "Occasional mismatch in rounding versus our code" )
@SkipForDialect( dialectClass = PostgreSQLDialect.class, reason = "Occasional mismatch in rounding versus our code", matchSubTypes = true )
@SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby does not support sized timestamp" )
@SkipForDialect(dialectClass = SybaseDialect.class, reason = "Because... Sybase...", matchSubTypes = true)
void testUsage0(SessionFactoryScope scope) {
final Instant start = Instant.now();
scope.inTransaction( (session) -> {
final TestEntity0 testEntity = new TestEntity0();
testEntity.id = 1;
testEntity.theInstant = start;
session.persist( testEntity );
} );
scope.inTransaction( (session) -> {
final TestEntity0 testEntity = session.find( TestEntity0.class, 1 );
assertThat( testEntity.theInstant ).isEqualTo( DateTimeUtils.roundToSecondPrecision( start, 0 ) );
} );
}
@Test
@DomainModel(annotatedClasses = TestEntity3.class)
@SessionFactory
@SkipForDialect( dialectClass = MariaDBDialect.class, reason = "Occasional mismatch in rounding versus our code" )
@SkipForDialect( dialectClass = HSQLDialect.class, reason = "Occasional mismatch in rounding versus our code" )
@SkipForDialect( dialectClass = DB2Dialect.class, reason = "Occasional mismatch in rounding versus our code" )
@SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby does not support sized timestamp" )
@SkipForDialect(dialectClass = SybaseDialect.class, reason = "Because... Sybase...", matchSubTypes = true)
void testUsage3(SessionFactoryScope scope) {
final Instant start = Instant.now();
scope.inTransaction( (session) -> {
final TestEntity3 testEntity = new TestEntity3();
testEntity.id = 1;
testEntity.theInstant = start;
session.persist( testEntity );
} );
scope.inTransaction( (session) -> {
final TestEntity3 testEntity = session.find( TestEntity3.class, 1 );
assertThat( testEntity.theInstant ).isEqualTo( DateTimeUtils.roundToSecondPrecision( start, 3 ) );
} );
}
@Test
@DomainModel(annotatedClasses = TestEntity9.class)
@SessionFactory
@SkipForDialect( dialectClass = MariaDBDialect.class, reason = "MariaDB only supports precision <= 6" )
@SkipForDialect( dialectClass = MySQLDialect.class, reason = "MySQL only supports precision <= 6", matchSubTypes = true )
@SkipForDialect( dialectClass = SQLServerDialect.class, reason = "SQL Server only supports precision <= 6" )
@SkipForDialect(dialectClass = SybaseDialect.class, reason = "Because... Sybase...", matchSubTypes = true)
void testUsage9(SessionFactoryScope scope) {
final Instant start = Instant.now();
scope.inTransaction( (session) -> {
final TestEntity9 testEntity = new TestEntity9();
testEntity.id = 1;
testEntity.theInstant = start;
session.persist( testEntity );
} );
scope.inTransaction( (session) -> {
final TestEntity9 testEntity = session.find( TestEntity9.class, 1 );
assertThat( testEntity.theInstant ).isEqualTo( start );
} );
}
@Entity(name="TestEntity")
@Table(name="TestEntity")
public static class TestEntity {
@Id
private Integer id;
private Instant theInstant;
@JdbcTypeCode(SqlTypes.LOCAL_DATE_TIME)
private LocalDateTime theLocalDateTime;
@JdbcTypeCode(SqlTypes.LOCAL_TIME)
private LocalTime theLocalTime;
private OffsetDateTime theOffsetDateTime;
private OffsetTime theOffsetTime;
private ZonedDateTime theZonedDateTime;
}
@Entity(name="TestEntity0")
@Table(name="TestEntity0")
public static class TestEntity0 {
@Id
private Integer id;
@FractionalSeconds(0)
private Instant theInstant;
}
@Entity(name="TestEntity3")
@Table(name="TestEntity3")
public static class TestEntity3 {
@Id
private Integer id;
@FractionalSeconds(3)
private Instant theInstant;
@JdbcTypeCode(SqlTypes.LOCAL_DATE_TIME)
@FractionalSeconds(3)
private LocalDateTime theLocalDateTime;
@JdbcTypeCode(SqlTypes.LOCAL_TIME)
@FractionalSeconds(3)
private LocalTime theLocalTime;
}
@Entity(name="TestEntity9")
@Table(name="TestEntity9")
public static class TestEntity9 {
@Id
private Integer id;
@FractionalSeconds(9)
private Instant theInstant;
@FractionalSeconds(9)
private OffsetDateTime theOffsetDateTime;
@FractionalSeconds(9)
private OffsetTime theOffsetTime;
@FractionalSeconds(9)
private ZonedDateTime theZonedDateTime;
}
}

View File

@ -5,7 +5,7 @@
:versionDocBase: {docsBase}/6.5
:userGuideBase: {versionDocBase}/userguide/html_single/Hibernate_User_Guide.html
:javadocsBase: {versionDocBase}/javadocs
:fn-instant: footnote:instant[JDBC 4.2, curiously, does not define support for Instant to be directly marshalled through the driver.]
This guide discusses migration to Hibernate ORM version 6.5. For migration from
earlier versions, see any other pertinent migration guides as well.
@ -16,3 +16,26 @@ earlier versions, see any other pertinent migration guides as well.
* link:{docsBase}/6.1/migration-guide/migration-guide.html[6.1 Migration guide]
* link:{docsBase}/6.0/migration-guide/migration-guide.html[6.0 Migration guide]
[[java-time]]
== Java Time Handling
6.5 adds support for marshalling Java Time objects directly through the JDBC driver as defined by JDBC 4.2.
In previous versions, Hibernate would handle Java Time objects using `java.sql.Date`, `java.sql.Time` or
`java.sql.Timestamp` references as intermediate forms.
Another behavioral change with this is handling for timezones. `OffsetDateTime`, `OffsetTime` and
`ZonedDateTime` all encode explicit timezone information. With direct marshalling, Hibernate simply
passes along the value as-is. In the legacy behavior, since the `java.sql` variants do not
encode timezone information, Hibernate generally has to specially handle timezones when converting to
those intermediate forms.
For 6.5 this behavior is disabled by default. To opt-in,
[source]
----
hibernate.type.prefer_java_type_jdbc_types=false
----
It is expected the default will flip for 7.0.

View File

@ -393,6 +393,11 @@ public abstract class MockSessionFactory
return null;
}
@Override
public boolean isPreferJavaTimeJdbcTypesEnabled() {
return MetadataBuildingContext.super.isPreferJavaTimeJdbcTypesEnabled();
}
@Override
public FastSessionServices getFastSessionServices() {
throw new UnsupportedOperationException();