diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java index e00feb694a43..9c8986b775aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java @@ -11,6 +11,7 @@ import java.util.function.Function; import java.util.function.Supplier; +import org.checkerframework.checker.nullness.qual.NonNull; import org.hibernate.MappingException; import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.MetadataBuildingContext; @@ -45,6 +46,7 @@ import static org.hibernate.type.SqlTypes.SMALLINT; import static org.hibernate.type.descriptor.java.JavaTypeHelper.isTemporal; +import static org.hibernate.type.descriptor.java.TemporalJavaType.resolveJdbcTypeCode; /** * BasicValue.Resolution resolver for cases where no explicit @@ -156,23 +158,8 @@ else if ( explicitJdbcType != null ) { // there was not a "legacy" BasicType registration, // so use `JavaType#getRecommendedJdbcType`, if one, // to create a mapping - final JdbcType recommendedJdbcType; - try { - recommendedJdbcType = reflectedJtd.getRecommendedJdbcType( stdIndicators ); - } - catch (JdbcTypeRecommendationException jtre) { - if ( buildingContext.getMetadataCollector() - .getEntityBindingMap().values().stream() - .anyMatch( pc -> pc.getMappedClass().equals(resolvedJavaType) ) ) { - throw new MappingException( "Incorrect use of entity type '" - + resolvedJavaType.getTypeName() - + "' (possibly due to missing association mapping annotation)", - jtre ); - } - else { - throw jtre; - } - } + final JdbcType recommendedJdbcType = + recommendedJdbcType( resolvedJavaType, stdIndicators, buildingContext, reflectedJtd ); if ( recommendedJdbcType != null ) { jdbcMapping = resolveSqlTypeIndicators( stdIndicators, @@ -254,6 +241,27 @@ else if ( column.getLength() != null ) { ); } + private static JdbcType recommendedJdbcType( + Type resolvedJavaType, + JdbcTypeIndicators stdIndicators, + MetadataBuildingContext buildingContext, + JavaType reflectedJtd) { + try { + return reflectedJtd.getRecommendedJdbcType( stdIndicators ); + } + catch (JdbcTypeRecommendationException jtre) { + if ( buildingContext.getMetadataCollector() + .getEntityBindingMap().values().stream() + .anyMatch( pc -> pc.getMappedClass().equals( resolvedJavaType ) ) ) { + throw new MappingException( "Incorrect use of entity type '" + resolvedJavaType.getTypeName() + + "' (possibly due to missing association mapping annotation)", jtre ); + } + else { + throw jtre; + } + } + } + private static BasicType registeredType( JdbcType explicitJdbcType, Function> explicitMutabilityPlanAccess, @@ -485,43 +493,16 @@ public static BasicValue.Resolution fromTemporal( JdbcType explicitJdbcType, Function> explicitMutabilityPlanAccess, JdbcTypeIndicators stdIndicators) { - final var typeConfiguration = stdIndicators.getTypeConfiguration(); - final var basicTypeRegistry = typeConfiguration.getBasicTypeRegistry(); - final var requestedTemporalPrecision = stdIndicators.getTemporalPrecision(); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Case #1 - explicit JavaType if ( explicitJavaType != null ) { - if ( !isTemporal( explicitJavaType ) ) { - throw new MappingException( - "Explicit JavaType [" + explicitJavaType + - "] defined for temporal value must implement TemporalJavaType" - ); - } - - @SuppressWarnings("unchecked") - final var explicitTemporalJtd = (TemporalJavaType) explicitJavaType; - if ( requestedTemporalPrecision != null && explicitTemporalJtd.getPrecision() != requestedTemporalPrecision ) { - throw new MappingException( - "Temporal precision (`jakarta.persistence.TemporalType`) mismatch... requested precision = " + requestedTemporalPrecision + - "; explicit JavaType (`" + explicitTemporalJtd + "`) precision = " + explicitTemporalJtd.getPrecision() - - ); - } - - final var jdbcType = - explicitJdbcType != null - ? explicitJdbcType - : explicitTemporalJtd.getRecommendedJdbcType( stdIndicators ); - final var jdbcMapping = basicTypeRegistry.resolve( explicitTemporalJtd, jdbcType ); - return new InferredBasicValueResolution<>( - jdbcMapping, - explicitTemporalJtd, - explicitTemporalJtd, - jdbcType, - jdbcMapping, - determineMutabilityPlan( explicitMutabilityPlanAccess, explicitTemporalJtd, typeConfiguration ) + return fromTemporalExplicitJavaType( + explicitJavaType, + explicitJdbcType, + explicitMutabilityPlanAccess, + stdIndicators ); } @@ -532,19 +513,10 @@ public static BasicValue.Resolution fromTemporal( // due to the new annotations being used if ( explicitJdbcType != null ) { - final TemporalJavaType temporalJavaType = - requestedTemporalPrecision != null - ? reflectedJtd.resolveTypeForPrecision( requestedTemporalPrecision, typeConfiguration ) - // Avoid using the DateJavaType and prefer the JdbcTimestampJavaType - : reflectedJtd.resolveTypeForPrecision( reflectedJtd.getPrecision(), typeConfiguration ); - final BasicType jdbcMapping = basicTypeRegistry.resolve( temporalJavaType, explicitJdbcType ); - return new InferredBasicValueResolution<>( - jdbcMapping, - temporalJavaType, - temporalJavaType, + return fromTemporalExplicitJdbcType( + reflectedJtd, explicitJdbcType, - jdbcMapping, - temporalJavaType.getMutabilityPlan() + stdIndicators ); } @@ -554,17 +526,31 @@ public static BasicValue.Resolution fromTemporal( // - for the moment continue to use the legacy resolution to registered // BasicType + return fromTemporalImplicit( + reflectedJtd, + explicitMutabilityPlanAccess, + stdIndicators + ); + } + + private static @NonNull InferredBasicValueResolution fromTemporalImplicit( + TemporalJavaType reflectedJtd, + Function> explicitMutabilityPlanAccess, + JdbcTypeIndicators stdIndicators) { + final var typeConfiguration = stdIndicators.getTypeConfiguration(); + final var basicTypeRegistry = typeConfiguration.getBasicTypeRegistry(); + final var requestedTemporalPrecision = stdIndicators.getTemporalPrecision(); + final BasicType basicType; if ( requestedTemporalPrecision != null && requestedTemporalPrecision != reflectedJtd.getPrecision() ) { basicType = basicTypeRegistry.resolve( reflectedJtd.resolveTypeForPrecision( requestedTemporalPrecision, typeConfiguration ), - TemporalJavaType.resolveJdbcTypeCode( requestedTemporalPrecision ) + resolveJdbcTypeCode( requestedTemporalPrecision ) ); } else { basicType = basicTypeRegistry.resolve( - // Avoid using the DateJavaType and prefer the JdbcTimestampJavaType reflectedJtd.resolveTypeForPrecision( reflectedJtd.getPrecision(), typeConfiguration ), reflectedJtd.getRecommendedJdbcType( stdIndicators ) ); @@ -580,6 +566,69 @@ public static BasicValue.Resolution fromTemporal( ); } + private static @NonNull InferredBasicValueResolution fromTemporalExplicitJdbcType( + TemporalJavaType reflectedJtd, + JdbcType explicitJdbcType, + JdbcTypeIndicators stdIndicators) { + final var typeConfiguration = stdIndicators.getTypeConfiguration(); + final var basicTypeRegistry = typeConfiguration.getBasicTypeRegistry(); + final var requestedTemporalPrecision = stdIndicators.getTemporalPrecision(); + + final var temporalJavaType = + requestedTemporalPrecision != null + ? reflectedJtd.resolveTypeForPrecision( requestedTemporalPrecision, typeConfiguration ) + : reflectedJtd.resolveTypeForPrecision( reflectedJtd.getPrecision(), typeConfiguration ); + final var jdbcMapping = basicTypeRegistry.resolve( temporalJavaType, explicitJdbcType ); + return new InferredBasicValueResolution<>( + jdbcMapping, + temporalJavaType, + temporalJavaType, + explicitJdbcType, + jdbcMapping, + temporalJavaType.getMutabilityPlan() + ); + } + + private static @NonNull InferredBasicValueResolution fromTemporalExplicitJavaType( + BasicJavaType explicitJavaType, + JdbcType explicitJdbcType, + Function> explicitMutabilityPlanAccess, + JdbcTypeIndicators stdIndicators) { + final var typeConfiguration = stdIndicators.getTypeConfiguration(); + final var basicTypeRegistry = typeConfiguration.getBasicTypeRegistry(); + final var requestedTemporalPrecision = stdIndicators.getTemporalPrecision(); + + if ( !isTemporal( explicitJavaType ) ) { + throw new MappingException( "Explicit JavaType [" + explicitJavaType + + "] defined for temporal value must implement TemporalJavaType" ); + } + + @SuppressWarnings("unchecked") + final var explicitTemporalJtd = (TemporalJavaType) explicitJavaType; + if ( requestedTemporalPrecision != null + && explicitTemporalJtd.getPrecision() != requestedTemporalPrecision ) { + throw new MappingException( + "Temporal precision (TemporalType) mismatch; requested precision = %s; explicit JavaType (%s) precision = %s" + .formatted( requestedTemporalPrecision, explicitTemporalJtd, explicitTemporalJtd.getPrecision() ) + + ); + } + + final var jdbcType = + explicitJdbcType != null + ? explicitJdbcType + : explicitTemporalJtd.getRecommendedJdbcType( stdIndicators ); + final var jdbcMapping = basicTypeRegistry.resolve( explicitTemporalJtd, jdbcType ); + return new InferredBasicValueResolution<>( + jdbcMapping, + explicitTemporalJtd, + explicitTemporalJtd, + jdbcType, + jdbcMapping, + determineMutabilityPlan( explicitMutabilityPlanAccess, explicitTemporalJtd, typeConfiguration ) + ); + } + private static MutabilityPlan determineMutabilityPlan( Function> explicitMutabilityPlanAccess, JavaType javaType, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractAttribute.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractAttribute.java index 22b999de5b4c..55c45407641a 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractAttribute.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractAttribute.java @@ -63,8 +63,11 @@ public String getName() { @Override public Class getJavaType() { - return valueType instanceof BasicTypeImpl basicType - ? basicType.getJavaType() + // TODO: create a new method to abstract this logic + return valueType instanceof BasicTypeImpl basicType + // handles primitives in basic types + ? (Class) basicType.getJavaType() + // good for everything else : attributeJtd.getJavaTypeClass(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiation.java index 13b1675ae760..c8e79168c930 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiation.java @@ -5,7 +5,6 @@ package org.hibernate.query.sqm.tree.select; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -23,11 +22,15 @@ import org.hibernate.query.sqm.tree.domain.SqmDomainType; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.jpa.AbstractJpaSelection; +import org.hibernate.type.descriptor.java.DateJavaType; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.TemporalJavaType; import org.hibernate.type.spi.TypeConfiguration; import org.jboss.logging.Logger; +import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableList; import static java.util.stream.Collectors.toList; import static org.hibernate.internal.util.NullnessUtil.castNonNull; import static org.hibernate.query.sqm.DynamicInstantiationNature.CLASS; @@ -157,7 +160,7 @@ public boolean checkInstantiation(TypeConfiguration typeConfiguration) { if ( isConstructorCompatible( javaType, argTypes, typeConfiguration ) ) { return true; } - final List> arguments = getArguments(); + final var arguments = getArguments(); final List aliases = new ArrayList<>( arguments.size() ); for ( var argument : arguments ) { final String alias = argument.getAlias(); @@ -182,9 +185,18 @@ private List> argumentTypes() { return getArguments().stream() .map( arg -> { final var expressible = arg.getExpressible(); - return expressible != null && expressible.getExpressibleJavaType() != null ? - expressible.getExpressibleJavaType().getJavaTypeClass() : - Void.class; + if ( expressible != null ) { + final var expressibleJavaType = expressible.getExpressibleJavaType(); + if ( expressibleJavaType != null ) { + return expressibleJavaType instanceof DateJavaType temporalJavaType + // Hack to accommodate a constructor with java.sql parameter + // types when the entity has java.util.Date as its field types. + // (This was requested in HHH-4179 and we fixed it by accident.) + ? TemporalJavaType.resolveJavaTypeClass( temporalJavaType.getPrecision() ) + : expressibleJavaType.getJavaTypeClass(); + } + } + return Void.class; } ).collect( toList() ); } @@ -227,7 +239,7 @@ public SqmDynamicInstantiationTarget getInstantiationTarget() { } public List> getArguments() { - return arguments == null ? Collections.emptyList() : Collections.unmodifiableList( arguments ); + return arguments == null ? emptyList() : unmodifiableList( arguments ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationResultImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationResultImpl.java index 58ac3dd808e1..58294dad65ac 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationResultImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationResultImpl.java @@ -17,8 +17,10 @@ import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.InitializerParent; import org.hibernate.sql.results.graph.instantiation.DynamicInstantiationResult; +import org.hibernate.type.descriptor.java.DateJavaType; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.TemporalJavaType; import org.jboss.logging.Logger; import static java.util.stream.Collectors.toList; @@ -157,7 +159,7 @@ private DomainResultAssembler assembler( final var constructor = findMatchingConstructor( javaType.getJavaTypeClass(), argumentReaders.stream() - .map( reader -> reader.getAssembledJavaType().getJavaTypeClass() ) + .map( reader -> argumentClass( reader ) ) .collect( toList() ), creationState.getSqlAstCreationContext() .getMappingMetamodel() @@ -194,6 +196,16 @@ private DomainResultAssembler assembler( return new DynamicInstantiationAssemblerInjectionImpl<>( javaType, argumentReaders ); } + private static Class argumentClass(ArgumentReader reader) { + final var assembledJavaType = reader.getAssembledJavaType(); + return assembledJavaType instanceof DateJavaType temporalJavaType + // Hack to accommodate a constructor with java.sql parameter + // types when the entity has java.util.Date as its field types. + // (This was requested in HHH-4179 and we fixed it by accident.) + ? TemporalJavaType.resolveJavaTypeClass( temporalJavaType.getPrecision() ) + : assembledJavaType.getJavaTypeClass(); + } + private List signature() { return argumentResults.stream() .map( adt -> adt.getResultJavaType().getTypeName() ) diff --git a/hibernate-core/src/main/java/org/hibernate/type/BasicTypeReference.java b/hibernate-core/src/main/java/org/hibernate/type/BasicTypeReference.java index 3e202366a9ee..9b6d116b67ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/BasicTypeReference.java +++ b/hibernate-core/src/main/java/org/hibernate/type/BasicTypeReference.java @@ -6,6 +6,7 @@ import java.io.Serializable; +import jakarta.persistence.TemporalType; import org.hibernate.query.sqm.SqmBindableType; import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; @@ -24,9 +25,10 @@ public final class BasicTypeReference implements BindableType, Serializabl private final int sqlTypeCode; private final BasicValueConverter converter; private final boolean forceImmutable; + private final TemporalType precision; public BasicTypeReference(String name, Class javaType, int sqlTypeCode) { - this(name, javaType, sqlTypeCode, null); + this( name, javaType, sqlTypeCode, null, null, false ); } public BasicTypeReference( @@ -34,13 +36,22 @@ public BasicTypeReference( Class javaType, int sqlTypeCode, BasicValueConverter converter) { - this( name, javaType, sqlTypeCode, converter, false ); + this( name, javaType, sqlTypeCode, null, converter, false ); + } + + public BasicTypeReference( + String name, + Class javaType, + int sqlTypeCode, + TemporalType precision) { + this( name, javaType, sqlTypeCode, precision, null, false ); } private BasicTypeReference( String name, Class javaType, int sqlTypeCode, + TemporalType precision, BasicValueConverter converter, boolean forceImmutable) { this.name = name; @@ -48,6 +59,7 @@ private BasicTypeReference( this.javaType = (Class) javaType; this.sqlTypeCode = sqlTypeCode; this.converter = converter; + this.precision = precision; this.forceImmutable = forceImmutable; } @@ -73,6 +85,10 @@ public int getSqlTypeCode() { return converter; } + public TemporalType getPrecision() { + return precision; + } + public boolean isForceImmutable() { return forceImmutable; } @@ -82,6 +98,7 @@ public BasicTypeReference asImmutable() { "imm_" + name, javaType, sqlTypeCode, + precision, converter, true ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/BasicTypeRegistry.java b/hibernate-core/src/main/java/org/hibernate/type/BasicTypeRegistry.java index 4b437c0184f4..922aea0555ae 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/BasicTypeRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/type/BasicTypeRegistry.java @@ -18,6 +18,7 @@ import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.TemporalJavaType; import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.DelegatingJdbcTypeIndicators; @@ -89,7 +90,10 @@ else if ( !name.equals( typeReference.getName() ) ) { } private BasicType createBasicType(String name, BasicTypeReference typeReference) { - final var javaType = getJavaTypeRegistry().resolveDescriptor( typeReference.getJavaType() ); + var javaType = getJavaTypeRegistry().resolveDescriptor( typeReference.getJavaType() ); + if ( javaType instanceof TemporalJavaType temporalJavaType ) { + javaType = temporalJavaType.resolveTypeForPrecision( typeReference.getPrecision(), typeConfiguration ); + } final var jdbcType = getJdbcTypeRegistry().getDescriptor( typeReference.getSqlTypeCode() ); final var createdType = createBasicType( typeReference, javaType, jdbcType ); typesByName.put( typeReference.getName(), createdType ); @@ -100,13 +104,13 @@ private BasicType createBasicType(String name, BasicTypeReference type private static BasicType createBasicType( BasicTypeReference typeReference, JavaType javaType, JdbcType jdbcType) { final String name = typeReference.getName(); - if ( typeReference.getConverter() == null ) { + final var converter = typeReference.getConverter(); + if ( converter == null ) { return typeReference.isForceImmutable() ? new ImmutableNamedBasicTypeImpl<>( javaType, jdbcType, name ) : new NamedBasicTypeImpl<>( javaType, jdbcType, name ); } else { - final var converter = typeReference.getConverter(); assert javaType == converter.getDomainJavaType(); return typeReference.isForceImmutable() ? new CustomMutabilityConvertedBasicTypeImpl<>( name, jdbcType, converter, diff --git a/hibernate-core/src/main/java/org/hibernate/type/StandardBasicTypes.java b/hibernate-core/src/main/java/org/hibernate/type/StandardBasicTypes.java index 8c16770b3caf..0bc00da07b70 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/StandardBasicTypes.java +++ b/hibernate-core/src/main/java/org/hibernate/type/StandardBasicTypes.java @@ -11,8 +11,6 @@ import java.sql.Blob; import java.sql.Clob; import java.sql.NClob; -import java.sql.Time; -import java.sql.Timestamp; import java.time.Duration; import java.time.Instant; import java.time.LocalDate; @@ -30,6 +28,7 @@ import java.util.TimeZone; import java.util.UUID; +import jakarta.persistence.TemporalType; import org.hibernate.type.spi.TypeConfiguration; /** @@ -494,8 +493,9 @@ private StandardBasicTypes() { */ public static final BasicTypeReference TIME = new BasicTypeReference<>( "time", - Time.class, - SqlTypes.TIME + java.util.Date.class, + SqlTypes.TIME, + TemporalType.TIME ); /** @@ -504,8 +504,9 @@ private StandardBasicTypes() { */ public static final BasicTypeReference DATE = new BasicTypeReference<>( "date", - java.sql.Date.class, - SqlTypes.DATE + java.util.Date.class, + SqlTypes.DATE, + TemporalType.DATE ); /** @@ -514,8 +515,42 @@ private StandardBasicTypes() { */ public static final BasicTypeReference TIMESTAMP = new BasicTypeReference<>( "timestamp", - Timestamp.class, - SqlTypes.TIMESTAMP + java.util.Date.class, + SqlTypes.TIMESTAMP, + TemporalType.TIMESTAMP + ); + + /** + * The standard Hibernate type for mapping {@link java.sql.Time} to JDBC + * {@link org.hibernate.type.SqlTypes#TIMESTAMP TIMESTAMP}. + */ + public static final BasicTypeReference SQL_TIME = new BasicTypeReference<>( + "sql_time", + java.sql.Time.class, + SqlTypes.TIME, + TemporalType.TIME + ); + + /** + * The standard Hibernate type for mapping {@link java.sql.Date} to JDBC + * {@link org.hibernate.type.SqlTypes#DATE DATE}. + */ + public static final BasicTypeReference SQL_DATE = new BasicTypeReference<>( + "sql_date", + java.sql.Date.class, + SqlTypes.DATE, + TemporalType.DATE + ); + + /** + * The standard Hibernate type for mapping {@link java.sql.Timestamp} to JDBC + * {@link org.hibernate.type.SqlTypes#TIMESTAMP TIMESTAMP}. + */ + public static final BasicTypeReference SQL_TIMESTAMP = new BasicTypeReference<>( + "sql_timestamp", + java.sql.Timestamp.class, + SqlTypes.TIMESTAMP, + TemporalType.TIMESTAMP ); /** @@ -525,7 +560,8 @@ private StandardBasicTypes() { public static final BasicTypeReference CALENDAR = new BasicTypeReference<>( "calendar", Calendar.class, - SqlTypes.TIMESTAMP + SqlTypes.TIMESTAMP, + TemporalType.TIMESTAMP ); /** @@ -535,7 +571,8 @@ private StandardBasicTypes() { public static final BasicTypeReference CALENDAR_DATE = new BasicTypeReference<>( "calendar_date", Calendar.class, - SqlTypes.DATE + SqlTypes.DATE, + TemporalType.DATE ); /** @@ -545,7 +582,8 @@ private StandardBasicTypes() { public static final BasicTypeReference CALENDAR_TIME = new BasicTypeReference<>( "calendar_time", Calendar.class, - SqlTypes.TIME + SqlTypes.TIME, + TemporalType.TIME ); @@ -1174,21 +1212,42 @@ public static void prime(TypeConfiguration typeConfiguration) { DATE, "org.hibernate.type.DateType", basicTypeRegistry, - "date", java.sql.Date.class.getName() + "date" ); handle( TIME, "org.hibernate.type.TimeType", basicTypeRegistry, - "time", java.sql.Time.class.getName() + "time" ); handle( TIMESTAMP, "org.hibernate.type.TimestampType", basicTypeRegistry, - "timestamp", java.sql.Timestamp.class.getName(), Date.class.getName() + "timestamp", Date.class.getName() + ); + + handle( + SQL_DATE, + null, + basicTypeRegistry, + "sql_date", java.sql.Date.class.getName() + ); + + handle( + SQL_TIME, + null, + basicTypeRegistry, + "sql_time", java.sql.Time.class.getName() + ); + + handle( + SQL_TIMESTAMP, + null, + basicTypeRegistry, + "sql_timestamp", java.sql.Timestamp.class.getName() ); handle( diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractTemporalJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractTemporalJavaType.java index d1a0079326ce..ec59cf36a426 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractTemporalJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractTemporalJavaType.java @@ -33,7 +33,7 @@ public AbstractTemporalJavaType( } @Override - public final TemporalJavaType resolveTypeForPrecision( + public TemporalJavaType resolveTypeForPrecision( TemporalType precision, TypeConfiguration typeConfiguration) { if ( precision == null ) { @@ -48,26 +48,25 @@ public final TemporalJavaType resolveTypeForPrecision( } } - private TemporalJavaType forMissingPrecision(TypeConfiguration typeConfiguration) { - //noinspection unchecked,rawtypes - return (TemporalJavaType) this; + private TemporalJavaType forMissingPrecision(TypeConfiguration typeConfiguration) { + return this; } - protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { + protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { throw new UnsupportedOperationException( - this + " as `jakarta.persistence.TemporalType.TIMESTAMP` not supported" + getTypeName() + " as TemporalType.TIMESTAMP not supported" ); } - protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { + protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { throw new UnsupportedOperationException( - this + " as `jakarta.persistence.TemporalType.DATE` not supported" + getTypeName() + " as TemporalType.DATE not supported" ); } - protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { + protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { throw new UnsupportedOperationException( - this + " as `jakarta.persistence.TemporalType.TIME` not supported" + getTypeName() + " as TemporalType.TIME not supported" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarDateJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarDateJavaType.java index 336041f99ba0..757345cb9875 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarDateJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarDateJavaType.java @@ -30,7 +30,7 @@ protected CalendarDateJavaType() { super( Calendar.class, CalendarJavaType.CalendarMutabilityPlan.INSTANCE, CalendarComparator.INSTANCE ); } - @Override + @Override @SuppressWarnings("deprecation") public TemporalType getPrecision() { return TemporalType.DATE; } @@ -40,23 +40,24 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) { return context.getJdbcType( Types.DATE ); } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) this; + @Override + protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { + return this; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) CalendarJavaType.INSTANCE; + @Override + protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { + return CalendarJavaType.INSTANCE; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) CalendarTimeJavaType.INSTANCE; + @Override + protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { + return CalendarTimeJavaType.INSTANCE; } public String toString(Calendar value) { - return JdbcDateJavaType.INSTANCE.toString( value.getTime() ); + return JdbcDateJavaType.INSTANCE.toString( + new java.sql.Date( value.getTime().getTime() ) ); } public Calendar fromString(CharSequence string) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarJavaType.java index 324b4d3d919c..9d69570a57b9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarJavaType.java @@ -39,7 +39,7 @@ protected CalendarJavaType() { super( Calendar.class, CalendarMutabilityPlan.INSTANCE, CalendarComparator.INSTANCE ); } - @Override + @Override @SuppressWarnings("deprecation") public TemporalType getPrecision() { return TemporalType.TIMESTAMP; } @@ -49,19 +49,19 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) { return context.getJdbcType( Types.TIMESTAMP ); } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) this; + @Override + protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { + return this; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) CalendarDateJavaType.INSTANCE; + @Override + protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { + return CalendarDateJavaType.INSTANCE; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) CalendarTimeJavaType.INSTANCE; + @Override + protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { + return CalendarTimeJavaType.INSTANCE; } public String toString(Calendar value) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarTimeJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarTimeJavaType.java index e544c30f2e2c..6fed6017fe7c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarTimeJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarTimeJavaType.java @@ -4,6 +4,7 @@ */ package org.hibernate.type.descriptor.java; +import java.sql.Time; import java.sql.Types; import java.util.Calendar; import java.util.Date; @@ -30,7 +31,7 @@ protected CalendarTimeJavaType() { super( Calendar.class, CalendarJavaType.CalendarMutabilityPlan.INSTANCE, CalendarComparator.INSTANCE ); } - @Override + @Override @SuppressWarnings("deprecation") public TemporalType getPrecision() { return TemporalType.TIME; } @@ -40,23 +41,24 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) { return context.getJdbcType( Types.TIME ); } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) this; + @Override + protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { + return this; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) CalendarJavaType.INSTANCE; + @Override + protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { + return CalendarJavaType.INSTANCE; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) CalendarDateJavaType.INSTANCE; + @Override + protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { + return CalendarDateJavaType.INSTANCE; } public String toString(Calendar value) { - return JdbcTimeJavaType.INSTANCE.toString( value.getTime() ); + return JdbcTimeJavaType.INSTANCE.toString( + new Time( value.getTime().getTime() ) ); } public Calendar fromString(CharSequence string) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DateJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DateJavaType.java index bed568b2643b..f6680becfc12 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DateJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DateJavaType.java @@ -4,18 +4,14 @@ */ package org.hibernate.type.descriptor.java; +import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; -import java.time.format.DateTimeParseException; -import java.time.temporal.ChronoField; -import java.time.temporal.TemporalAccessor; import java.util.Calendar; import java.util.Date; -import java.util.GregorianCalendar; import jakarta.persistence.TemporalType; -import org.hibernate.HibernateException; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.type.descriptor.WrapperOptions; @@ -30,17 +26,54 @@ */ public class DateJavaType extends AbstractTemporalJavaType implements VersionJavaType { public static final DateJavaType INSTANCE = new DateJavaType(); + private final @SuppressWarnings("deprecation") TemporalType precision; public static class DateMutabilityPlan extends MutableMutabilityPlan { - public static final DateMutabilityPlan INSTANCE = new DateMutabilityPlan(); + @SuppressWarnings("deprecation") + public static final DateMutabilityPlan INSTANCE = + new DateMutabilityPlan( TemporalType.TIMESTAMP ); + + private final @SuppressWarnings("deprecation") TemporalType precision; + + public DateMutabilityPlan(@SuppressWarnings("deprecation") TemporalType precision) { + this.precision = precision; + } + @Override public Date deepCopyNotNull(Date value) { - return new Date( value.getTime() ); + if ( value instanceof java.sql.Timestamp timestamp ) { + return JdbcTimestampJavaType.TimestampMutabilityPlan.INSTANCE.deepCopyNotNull( timestamp ); + } + else if ( value instanceof java.sql.Date date ) { + return JdbcDateJavaType.DateMutabilityPlan.INSTANCE.deepCopyNotNull( date ); + } + else if ( value instanceof java.sql.Time time ) { + return JdbcTimeJavaType.TimeMutabilityPlan.INSTANCE.deepCopyNotNull( time ); + } + else { + return switch ( precision ) { + case TIMESTAMP -> toTimestamp( value ); + case DATE -> toDate( value ); + case TIME -> toTime( value ); + }; + } } } + @SuppressWarnings("deprecation") public DateJavaType() { super( Date.class, DateMutabilityPlan.INSTANCE ); + this.precision = TemporalType.TIMESTAMP; + } + + /** + * A {@link Date} may be used to represent a date, time, or timestamp, + * each of which have different semantics at the Java level. Therefore, + * we distinguish these usages based on the given {@code TemporalType}. + */ + private DateJavaType(@SuppressWarnings("deprecation") TemporalType precision) { + super( Date.class, new DateMutabilityPlan( precision ) ); + this.precision = precision; } @Override @@ -54,53 +87,52 @@ public Date cast(Object value) { } @Override - public TemporalType getPrecision() { - return TemporalType.TIMESTAMP; + public @SuppressWarnings("deprecation") TemporalType getPrecision() { + return precision; } @Override - public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) { - // this "Date" is really a timestamp - return dialect.getDefaultTimestampPrecision(); + public TemporalJavaType resolveTypeForPrecision( + @SuppressWarnings("deprecation") TemporalType precision, + TypeConfiguration typeConfiguration) { + return precision == null ? this : new DateJavaType( precision ); } @Override - public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) { - return context.getJdbcType( Types.TIMESTAMP ); - } - - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) JdbcDateJavaType.INSTANCE; - } - - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) JdbcTimestampJavaType.INSTANCE; + public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) { + return switch ( precision ) { + case TIMESTAMP -> JdbcTimestampJavaType.INSTANCE.getDefaultSqlPrecision( dialect, jdbcType ); + case DATE -> JdbcDateJavaType.INSTANCE.getDefaultSqlPrecision( dialect, jdbcType ); + case TIME -> JdbcTimeJavaType.INSTANCE.getDefaultSqlPrecision( dialect, jdbcType ); + }; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) JdbcTimeJavaType.INSTANCE; + @Override + public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) { + return context.getJdbcType( switch ( precision ) { + case TIMESTAMP -> Types.TIMESTAMP; + case DATE -> Types.DATE; + case TIME -> Types.TIME; + } ); } @Override public String toString(Date value) { - return JdbcTimestampJavaType.LITERAL_FORMATTER.format( value.toInstant() ); +// return JdbcTimestampJavaType.LITERAL_FORMATTER.format( value.toInstant() ); + return switch ( precision ) { + case TIMESTAMP -> JdbcTimestampJavaType.INSTANCE.toString( toTimestamp( value ) ); + case DATE -> JdbcDateJavaType.INSTANCE.toString( toDate( value ) ); + case TIME -> JdbcTimeJavaType.INSTANCE.toString( toTime( value ) ); + }; } @Override public Date fromString(CharSequence string) { - try { - final TemporalAccessor accessor = JdbcTimestampJavaType.LITERAL_FORMATTER.parse( string ); - return new Date( - accessor.getLong( ChronoField.INSTANT_SECONDS ) * 1000L - + accessor.get( ChronoField.NANO_OF_SECOND ) / 1_000_000 - ); - } - catch ( DateTimeParseException pe) { - throw new HibernateException( "could not parse timestamp string" + string, pe ); - } + return switch ( precision ) { + case TIMESTAMP -> JdbcTimestampJavaType.INSTANCE.fromString( string ); + case DATE -> JdbcDateJavaType.INSTANCE.fromString( string ); + case TIME -> JdbcTimeJavaType.INSTANCE.fromString( string ); + }; } @Override @@ -108,78 +140,68 @@ public boolean areEqual(Date one, Date another) { if ( one == another) { return true; } - return !( one == null || another == null ) && one.getTime() == another.getTime(); - - } - - @Override + return one != null && another != null + && switch ( precision ) { + case DATE -> JdbcDateJavaType.INSTANCE.areEqual( toDate( one ), toDate( another ) ); + case TIME -> JdbcTimeJavaType.INSTANCE.areEqual( toTime( one ), toTime( another ) ); + case TIMESTAMP -> + // emulate legacy behavior (good or not) + one instanceof Timestamp timestamp && another instanceof Timestamp anotherTimestamp + ? JdbcTimestampJavaType.INSTANCE.areEqual( timestamp, anotherTimestamp ) + : one.getTime() == another.getTime(); + }; + } + + @Override @SuppressWarnings("deprecation") public int extractHashCode(Date value) { - Calendar calendar = Calendar.getInstance(); + var calendar = Calendar.getInstance(); calendar.setTime( value ); - return CalendarJavaType.INSTANCE.extractHashCode( calendar ); + int hashCode = 1; + if ( precision == TemporalType.TIMESTAMP ) { + hashCode = 31 * hashCode + calendar.get(Calendar.MILLISECOND); + } + if ( precision != TemporalType.DATE ) { + hashCode = 31 * hashCode + calendar.get(Calendar.SECOND); + hashCode = 31 * hashCode + calendar.get(Calendar.MINUTE); + hashCode = 31 * hashCode + calendar.get(Calendar.HOUR_OF_DAY); + } + if ( precision != TemporalType.TIME ) { + hashCode = 31 * hashCode + calendar.get(Calendar.DAY_OF_MONTH); + hashCode = 31 * hashCode + calendar.get(Calendar.MONTH); + hashCode = 31 * hashCode + calendar.get(Calendar.YEAR); + } + return hashCode; } @Override public X unwrap(Date value, Class type, WrapperOptions options) { - if ( value == null ) { - return null; - } - if ( java.sql.Date.class.isAssignableFrom( type ) ) { - final java.sql.Date rtn = value instanceof java.sql.Date - ? ( java.sql.Date ) value - : new java.sql.Date( value.getTime() ); - return type.cast( rtn ); - } - if ( java.sql.Time.class.isAssignableFrom( type ) ) { - final java.sql.Time rtn = value instanceof java.sql.Time - ? ( java.sql.Time ) value - : new java.sql.Time( value.getTime() % 86_400_000 ); - return type.cast( rtn ); - } - if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) { - final java.sql.Timestamp rtn = value instanceof Timestamp - ? ( java.sql.Timestamp ) value - : new java.sql.Timestamp( value.getTime() ); - return type.cast( rtn ); - } - if ( Date.class.isAssignableFrom( type ) ) { - return type.cast( value ); - } - if ( Calendar.class.isAssignableFrom( type ) ) { - final GregorianCalendar cal = new GregorianCalendar(); - cal.setTimeInMillis( value.getTime() ); - return type.cast( cal ); - } - if ( Long.class.isAssignableFrom( type ) ) { - return type.cast( value.getTime() ); - } - throw unknownUnwrap( type ); + return switch ( precision ) { + case TIMESTAMP -> JdbcTimestampJavaType.INSTANCE.unwrap( toTimestamp( value ), type, options ); + case DATE -> JdbcDateJavaType.INSTANCE.unwrap( toDate( value ), type, options ); + case TIME -> JdbcTimeJavaType.INSTANCE.unwrap( toTime( value ), type, options ); + }; } + @Override public Date wrap(X value, WrapperOptions options) { - if ( value == null ) { - return null; - } - if (value instanceof Date date) { - return date; - } - - if (value instanceof Long longValue) { - return new Date( longValue ); - } - - if (value instanceof Calendar calendar) { - return new Date( calendar.getTimeInMillis() ); - } + return switch ( precision ) { + case TIMESTAMP -> JdbcTimestampJavaType.INSTANCE.wrap( value, options ); + case DATE -> JdbcDateJavaType.INSTANCE.wrap( value, options ); + case TIME -> JdbcTimeJavaType.INSTANCE.wrap( value, options ); + }; + } - throw unknownWrap( value.getClass() ); + @Override + public Object coerce(Object value) { + return wrap( value, null ); } @Override public boolean isWider(JavaType javaType) { - return switch ( javaType.getTypeName() ) { - case "java.sql.Date", "java.sql.Timestamp", "java.util.Calendar" -> true; - default -> false; + return switch ( precision ) { + case TIMESTAMP -> JdbcTimestampJavaType.INSTANCE.isWider( javaType ); + case DATE -> JdbcDateJavaType.INSTANCE.isWider( javaType ); + case TIME -> JdbcTimeJavaType.INSTANCE.isWider( javaType ); }; } @@ -199,4 +221,21 @@ public Date seed( Integer precision, Integer scale, SharedSessionContractImplementor session) { return Timestamp.from( ClockHelper.forPrecision( precision, session ).instant() ); } + + private static Timestamp toTimestamp(Date date) { + return date instanceof Timestamp timestamp + ? timestamp + : JdbcTimestampJavaType.wrapSqlTimestamp( date ); + } + + private static Time toTime(Date date) { + return date instanceof Time time + ? time + : JdbcTimeJavaType.toTime( date ); + } + + private static java.sql.Date toDate(java.util.Date value) { + return JdbcDateJavaType.toDate( value ); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaType.java index 7a8b7b46406e..9c75946c7b47 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaType.java @@ -52,24 +52,24 @@ public Instant cast(Object value) { return (Instant) value; } - @Override + @Override @SuppressWarnings("deprecation") public TemporalType getPrecision() { return TemporalType.TIMESTAMP; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) this; + @Override + protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { + return this; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) this; + @Override + protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { + return this; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) this; + @Override + protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { + return this; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateJavaType.java index 9258833af50a..ebad46f62753 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateJavaType.java @@ -12,7 +12,7 @@ import java.time.format.DateTimeParseException; import java.time.temporal.TemporalAccessor; import java.util.Calendar; -import java.util.Date; +import java.sql.Date; import java.util.GregorianCalendar; import org.hibernate.HibernateException; @@ -55,22 +55,20 @@ public JdbcDateJavaType() { super( Date.class, DateMutabilityPlan.INSTANCE ); } - @Override + @Override @SuppressWarnings("deprecation") public TemporalType getPrecision() { return TemporalType.DATE; } @Override public Class getJavaType() { - // wrong, but needed for backward compatibility - //noinspection unchecked, rawtypes - return (Class) java.sql.Date.class; + return java.sql.Date.class; } @Override public boolean isInstance(Object value) { // this check holds true for java.sql.Date as well - return value instanceof Date + return value instanceof java.util.Date && !( value instanceof java.sql.Time ); } @@ -130,7 +128,7 @@ public X unwrap(Date value, Class type, WrapperOptions options) { } if ( java.sql.Date.class.isAssignableFrom( type ) ) { - return type.cast( unwrapSqlDate( value ) ); + return type.cast( value ); } if ( java.util.Date.class.isAssignableFrom( type ) ) { @@ -138,7 +136,7 @@ public X unwrap(Date value, Class type, WrapperOptions options) { } if ( Long.class.isAssignableFrom( type ) ) { - return type.cast( unwrapDateEpoch( value ) ); + return type.cast( toDateEpoch( value ) ); } if ( String.class.isAssignableFrom( type ) ) { @@ -147,49 +145,25 @@ public X unwrap(Date value, Class type, WrapperOptions options) { if ( Calendar.class.isAssignableFrom( type ) ) { final var gregorianCalendar = new GregorianCalendar(); - gregorianCalendar.setTimeInMillis( unwrapDateEpoch( value ) ); + gregorianCalendar.setTimeInMillis( toDateEpoch( value ) ); return type.cast( gregorianCalendar ); } if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) { - return type.cast( new java.sql.Timestamp( unwrapDateEpoch( value ) ) ); + return type.cast( new java.sql.Timestamp( toDateEpoch( value ) ) ); } if ( java.sql.Time.class.isAssignableFrom( type ) ) { - throw new IllegalArgumentException( "Illegal attempt to treat `java.sql.Date` as `java.sql.Time`" ); + throw new IllegalArgumentException( "Illegal attempt to treat 'java.sql.Date' as 'java.sql.Time'" ); } throw unknownUnwrap( type ); } - private LocalDate unwrapLocalDate(Date value) { + private LocalDate unwrapLocalDate(java.util.Date value) { return value instanceof java.sql.Date date ? date.toLocalDate() - : new java.sql.Date( unwrapDateEpoch( value ) ).toLocalDate(); - } - - private java.sql.Date unwrapSqlDate(Date value) { - if ( value instanceof java.sql.Date date ) { - final long dateEpoch = toDateEpoch( date.getTime() ); - return dateEpoch == date.getTime() ? date : new java.sql.Date( dateEpoch ); - } - else { - return new java.sql.Date( unwrapDateEpoch( value ) ); - } - } - - private static long unwrapDateEpoch(Date value) { - return toDateEpoch( value.getTime() ); - } - - private static long toDateEpoch(long value) { - final var calendar = Calendar.getInstance(); - calendar.setTimeInMillis( value ); - calendar.set(Calendar.HOUR_OF_DAY, 0); - calendar.clear(Calendar.MINUTE); - calendar.clear(Calendar.SECOND); - calendar.clear(Calendar.MILLISECOND); - return calendar.getTimeInMillis(); + : new java.sql.Date( toDateEpoch( value ) ).toLocalDate(); } @Override @@ -210,8 +184,8 @@ public Date wrap(Object value, WrapperOptions options) { return new java.sql.Date( toDateEpoch( calendar.getTimeInMillis() ) ); } - if ( value instanceof Date date ) { - return unwrapSqlDate( date ); + if ( value instanceof java.util.Date date ) { + return toDate( date ); } if ( value instanceof LocalDate localDate ) { @@ -221,7 +195,32 @@ public Date wrap(Object value, WrapperOptions options) { throw unknownWrap( value.getClass() ); } - private static TemporalAccessor fromDate(Date value) { + static java.sql.Date toDate(java.util.Date value) { + if ( value instanceof java.sql.Date date ) { + final long millis = date.getTime(); + final long dateEpoch = toDateEpoch( millis ); + return dateEpoch == millis ? date : new java.sql.Date( dateEpoch ); + } + else { + return new java.sql.Date( toDateEpoch( value ) ); + } + } + + static long toDateEpoch(java.util.Date value) { + return toDateEpoch( value.getTime() ); + } + + static long toDateEpoch(long value) { + final var calendar = Calendar.getInstance(); + calendar.setTimeInMillis( value ); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.clear(Calendar.MINUTE); + calendar.clear(Calendar.SECOND); + calendar.clear(Calendar.MILLISECOND); + return calendar.getTimeInMillis(); + } + + private static TemporalAccessor fromDate(java.util.Date value) { return value instanceof java.sql.Date date ? date.toLocalDate() : LocalDate.ofInstant( value.toInstant(), ZoneOffset.systemDefault() ); @@ -265,9 +264,8 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) { } @Override - protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { - //noinspection unchecked - return (TemporalJavaType) this; + protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { + return this; } public static class DateMutabilityPlan extends MutableMutabilityPlan { @@ -275,9 +273,7 @@ public static class DateMutabilityPlan extends MutableMutabilityPlan { @Override public Date deepCopyNotNull(Date value) { - return value instanceof java.sql.Date - ? value - : new java.sql.Date( value.getTime() ); + return new java.sql.Date( value.getTime() ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeJavaType.java index af0af15fe7f0..1d868df7fc70 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeJavaType.java @@ -37,7 +37,7 @@ * to {@link Time} values. This capability is shared with * {@link JdbcDateJavaType} and {@link JdbcTimestampJavaType}. */ -public class JdbcTimeJavaType extends AbstractTemporalJavaType { +public class JdbcTimeJavaType extends AbstractTemporalJavaType