HHH-14919 - Improve handling for java.sql.Date, Time and Timestamp

This commit is contained in:
Steve Ebersole 2021-11-08 16:37:31 -06:00
parent 9c9a326ae6
commit 599b0ba39f
12 changed files with 425 additions and 308 deletions

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.userguide.mapping.basic;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant;
import java.util.Date;
@ -45,7 +47,7 @@ public class DatePrecisionTests {
final JdbcMapping jdbcMapping = attribute.getJdbcMapping();
final TemporalJavaTypeDescriptor jtd = (TemporalJavaTypeDescriptor) jdbcMapping.getJavaTypeDescriptor();
assertThat( jtd, is( attribute.getJavaTypeDescriptor() ) );
assertThat( jtd.getJavaTypeClass(), equalTo( Date.class ) );
assertThat( jtd.getJavaTypeClass(), equalTo( Timestamp.class ) );
assertThat( jtd.getPrecision(), equalTo( TemporalType.TIMESTAMP ) );
assertThat( jdbcMapping.getJdbcTypeDescriptor().getJdbcTypeCode(), equalTo( Types.TIMESTAMP ) );
}
@ -55,7 +57,7 @@ public class DatePrecisionTests {
final JdbcMapping jdbcMapping = attribute.getJdbcMapping();
final TemporalJavaTypeDescriptor jtd = (TemporalJavaTypeDescriptor) jdbcMapping.getJavaTypeDescriptor();
assertThat( jtd, is( attribute.getJavaTypeDescriptor() ) );
assertThat( jtd.getJavaTypeClass(), equalTo( Date.class ) );
assertThat( jtd.getJavaTypeClass(), equalTo( java.sql.Date.class ) );
assertThat( jtd.getPrecision(), equalTo( TemporalType.DATE ) );
assertThat( jdbcMapping.getJdbcTypeDescriptor().getJdbcTypeCode(), equalTo( Types.DATE ) );
}
@ -65,7 +67,7 @@ public class DatePrecisionTests {
final JdbcMapping jdbcMapping = attribute.getJdbcMapping();
final TemporalJavaTypeDescriptor jtd = (TemporalJavaTypeDescriptor) jdbcMapping.getJavaTypeDescriptor();
assertThat( jtd, is( attribute.getJavaTypeDescriptor() ) );
assertThat( jtd.getJavaTypeClass(), equalTo( Date.class ) );
assertThat( jtd.getJavaTypeClass(), equalTo( Time.class ) );
assertThat( jtd.getPrecision(), equalTo( TemporalType.TIME ) );
assertThat( jdbcMapping.getJdbcTypeDescriptor().getJdbcTypeCode(), equalTo( Types.TIME ) );
}

View File

@ -16,6 +16,7 @@ import org.hibernate.Filter;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.FilterDefinition;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.type.BasicType;
import org.hibernate.type.Type;
/**
@ -74,12 +75,11 @@ public class FilterImpl implements Filter, Serializable {
*/
public Filter setParameter(String name, Object value) throws IllegalArgumentException {
// Make sure this is a defined parameter and check the incoming value type
// TODO: what should be the actual exception type here?
Type type = definition.getParameterType( name );
BasicType<?> type = (BasicType<?>) definition.getParameterType( name );
if ( type == null ) {
throw new IllegalArgumentException( "Undefined filter parameter [" + name + "]" );
}
if ( value != null && !type.getReturnedClass().isAssignableFrom( value.getClass() ) ) {
if ( value != null && !type.getJavaTypeDescriptor().isInstance( value ) ) {
throw new IllegalArgumentException( "Incorrect type for parameter [" + name + "]" );
}
parameters.put( name, value );

View File

@ -180,13 +180,11 @@ public class QueryParameterBindingImpl<T> implements QueryParameterBinding<T>, J
this.bindType = clarifiedType;
}
if ( ! getTypeConfiguration().getSessionFactory().getJpaMetamodel().getJpaCompliance().isLoadByIdComplianceEnabled() ) {
if ( bindType != null ) {
value = bindType.getExpressableJavaTypeDescriptor().coerce( value, this );
}
else if ( queryParameter.getHibernateType() != null ) {
value = queryParameter.getHibernateType().getExpressableJavaTypeDescriptor().coerce( value, this );
}
if ( bindType != null ) {
value = bindType.getExpressableJavaTypeDescriptor().coerce( value, this );
}
else if ( queryParameter.getHibernateType() != null ) {
value = queryParameter.getHibernateType().getExpressableJavaTypeDescriptor().coerce( value, this );
}
if ( isBindingValidationRequired ) {

View File

@ -17,7 +17,7 @@ public class JdbcParameterBindingImpl implements JdbcParameterBinding {
private final Object bindValue;
public JdbcParameterBindingImpl(JdbcMapping jdbcMapping, Object bindValue) {
assert bindValue == null || jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass().isInstance( bindValue );
assert bindValue == null || jdbcMapping.getJavaTypeDescriptor().isInstance( bindValue );
this.jdbcMapping = jdbcMapping;
this.bindValue = bindValue;
}

View File

@ -36,7 +36,7 @@ public abstract class AbstractClassJavaTypeDescriptor<T> implements BasicJavaTyp
* @see #AbstractClassJavaTypeDescriptor(Class, MutabilityPlan)
*/
@SuppressWarnings({ "unchecked" })
protected AbstractClassJavaTypeDescriptor(Class<T> type) {
protected AbstractClassJavaTypeDescriptor(Class<? extends T> type) {
this( type, (MutabilityPlan<T>) ImmutableMutabilityPlan.INSTANCE );
}
@ -47,7 +47,7 @@ public abstract class AbstractClassJavaTypeDescriptor<T> implements BasicJavaTyp
* @param mutabilityPlan The plan for handling mutability aspects of the java type.
*/
@SuppressWarnings({ "unchecked" })
protected AbstractClassJavaTypeDescriptor(Class<T> type, MutabilityPlan<T> mutabilityPlan) {
protected AbstractClassJavaTypeDescriptor(Class<? extends T> type, MutabilityPlan<? extends T> mutabilityPlan) {
this(
type,
mutabilityPlan,
@ -64,13 +64,14 @@ public abstract class AbstractClassJavaTypeDescriptor<T> implements BasicJavaTyp
* @param mutabilityPlan The plan for handling mutability aspects of the java type.
* @param comparator The comparator for handling comparison of values
*/
@SuppressWarnings("unchecked")
protected AbstractClassJavaTypeDescriptor(
Class<T> type,
MutabilityPlan<T> mutabilityPlan,
Comparator<T> comparator) {
this.type = type;
this.mutabilityPlan = mutabilityPlan;
this.comparator = comparator;
Class<? extends T> type,
MutabilityPlan<? extends T> mutabilityPlan,
Comparator<? extends T> comparator) {
this.type = (Class<T>) type;
this.mutabilityPlan = (MutabilityPlan<T>) mutabilityPlan;
this.comparator = (Comparator<T>) comparator;
}
@Override
@ -107,11 +108,11 @@ public abstract class AbstractClassJavaTypeDescriptor<T> implements BasicJavaTyp
return (value == null) ? "null" : value.toString();
}
protected HibernateException unknownUnwrap(Class conversionType) {
protected HibernateException unknownUnwrap(Class<?> conversionType) {
return JavaTypeDescriptorHelper.unknownUnwrap( type, conversionType, this );
}
protected HibernateException unknownWrap(Class conversionType) {
protected HibernateException unknownWrap(Class<?> conversionType) {
return JavaTypeDescriptorHelper.unknownWrap( conversionType, type, this );
}
}

View File

@ -15,20 +15,22 @@ import org.hibernate.type.spi.TypeConfiguration;
/**
* @author Steve Ebersole
*/
public abstract class AbstractTemporalJavaTypeDescriptor<T> extends AbstractClassJavaTypeDescriptor<T> implements TemporalJavaTypeDescriptor<T> {
public abstract class AbstractTemporalJavaTypeDescriptor<T>
extends AbstractClassJavaTypeDescriptor<T>
implements TemporalJavaTypeDescriptor<T> {
protected AbstractTemporalJavaTypeDescriptor(Class<T> type) {
protected AbstractTemporalJavaTypeDescriptor(Class<? extends T> type) {
super( type );
}
protected AbstractTemporalJavaTypeDescriptor(Class<T> type, MutabilityPlan<T> mutabilityPlan) {
protected AbstractTemporalJavaTypeDescriptor(Class<? extends T> type, MutabilityPlan<? extends T> mutabilityPlan) {
super( type, mutabilityPlan );
}
public AbstractTemporalJavaTypeDescriptor(
Class<T> type,
MutabilityPlan<T> mutabilityPlan,
Comparator<T> comparator) {
Class<? extends T> type,
MutabilityPlan<? extends T> mutabilityPlan,
Comparator<? extends T> comparator) {
super( type, mutabilityPlan, comparator );
}
@ -62,19 +64,19 @@ public abstract class AbstractTemporalJavaTypeDescriptor<T> extends AbstractClas
protected <X> TemporalJavaTypeDescriptor<X> forTimestampPrecision(TypeConfiguration typeConfiguration) {
throw new UnsupportedOperationException(
toString() + " as `jakarta.persistence.TemporalType.TIMESTAMP` not supported"
this + " as `jakarta.persistence.TemporalType.TIMESTAMP` not supported"
);
}
protected <X> TemporalJavaTypeDescriptor<X> forDatePrecision(TypeConfiguration typeConfiguration) {
throw new UnsupportedOperationException(
toString() + " as `jakarta.persistence.TemporalType.DATE` not supported"
this + " as `jakarta.persistence.TemporalType.DATE` not supported"
);
}
protected <X> TemporalJavaTypeDescriptor<X> forTimePrecision(TypeConfiguration typeConfiguration) {
throw new UnsupportedOperationException(
toString() + " as `jakarta.persistence.TemporalType.TIME` not supported"
this + " as `jakarta.persistence.TemporalType.TIME` not supported"
);
}

View File

@ -28,13 +28,34 @@ import org.hibernate.type.spi.TypeConfiguration;
*/
public interface JavaType<T> extends Serializable {
/**
* Get the Java type described
* Get the Java type (Type) described
*
* @see #getJavaTypeClass
*/
default Type getJavaType() {
// default on this side since #getJavaTypeClass is the currently implemented method
return getJavaTypeClass();
}
/**
* Get the Java type (Class) described
*
* @see #getJavaType
*/
default Class<T> getJavaTypeClass() {
return ReflectHelper.getClass( getJavaType() );
}
/**
* Is the given value an instance of the described type?
*
* Generally this comes down to {@link #getJavaTypeClass() getJavaTypeClass().}{@link Class#isInstance isInstance()},
* though some descriptors (mainly the java.sql.Date, Time and Timestamp descriptors) might need different semantics
*/
default boolean isInstance(Object value) {
return getJavaTypeClass().isInstance( value );
}
/**
* Retrieve the mutability plan for this Java type.
*/
@ -199,15 +220,6 @@ public interface JavaType<T> extends Serializable {
return (T) value;
}
/**
* Retrieve the Java type handled here.
*
* @return The Java type.
*/
default Class<T> getJavaTypeClass() {
return ReflectHelper.getClass( getJavaType() );
}
/**
* The check constraint that should be added to the column
* definition in generated DDL.

View File

@ -15,18 +15,21 @@ import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import jakarta.persistence.TemporalType;
import org.hibernate.HibernateException;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeDescriptorIndicators;
import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.TemporalType;
/**
* Descriptor for {@link java.sql.Date} handling.
*
* @author Steve Ebersole
* @implSpec Unlike most {@link JavaType} implementations, can handle 2 different "domain
* representations" (most map just a single type): general {@link Date} values in addition
* to {@link java.sql.Date} values. This capability is shared with
* {@link JdbcTimeJavaTypeDescriptor} and {@link JdbcTimestampJavaTypeDescriptor}.
*/
public class JdbcDateJavaTypeDescriptor extends AbstractTemporalJavaTypeDescriptor<Date> {
public static final JdbcDateJavaTypeDescriptor INSTANCE = new JdbcDateJavaTypeDescriptor();
@ -42,19 +45,9 @@ public class JdbcDateJavaTypeDescriptor extends AbstractTemporalJavaTypeDescript
@SuppressWarnings("unused")
public static final DateTimeFormatter LITERAL_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE;
public static class DateMutabilityPlan extends MutableMutabilityPlan<Date> {
public static final DateMutabilityPlan INSTANCE = new DateMutabilityPlan();
@Override
public Date deepCopyNotNull(Date value) {
return value instanceof java.sql.Date
? new java.sql.Date( value.getTime() )
: new Date( value.getTime() );
}
}
@SuppressWarnings("WeakerAccess")
public JdbcDateJavaTypeDescriptor() {
super( Date.class, DateMutabilityPlan.INSTANCE );
super( java.sql.Date.class, DateMutabilityPlan.INSTANCE );
}
@Override
@ -63,29 +56,10 @@ public class JdbcDateJavaTypeDescriptor extends AbstractTemporalJavaTypeDescript
}
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeDescriptorIndicators context) {
return context.getTypeConfiguration().getJdbcTypeDescriptorRegistry().getDescriptor( Types.DATE );
}
@Override
protected <X> TemporalJavaTypeDescriptor<X> forDatePrecision(TypeConfiguration typeConfiguration) {
//noinspection unchecked
return (TemporalJavaTypeDescriptor<X>) this;
}
@Override
public String toString(Date value) {
return new SimpleDateFormat( DATE_FORMAT ).format( value );
}
@Override
public Date fromString(CharSequence string) {
try {
return new Date( new SimpleDateFormat(DATE_FORMAT).parse( string.toString() ).getTime() );
}
catch ( ParseException pe) {
throw new HibernateException( "could not parse date string" + string, pe );
}
public boolean isInstance(Object value) {
// this check holds true for java.sql.Date as well
return value instanceof Date
&& !( value instanceof java.sql.Time );
}
@Override
@ -93,6 +67,7 @@ public class JdbcDateJavaTypeDescriptor extends AbstractTemporalJavaTypeDescript
if ( one == another ) {
return true;
}
if ( one == null || another == null ) {
return false;
}
@ -101,8 +76,8 @@ public class JdbcDateJavaTypeDescriptor extends AbstractTemporalJavaTypeDescript
return true;
}
Calendar calendar1 = Calendar.getInstance();
Calendar calendar2 = Calendar.getInstance();
final Calendar calendar1 = Calendar.getInstance();
final Calendar calendar2 = Calendar.getInstance();
calendar1.setTime( one );
calendar2.setTime( another );
@ -113,7 +88,7 @@ public class JdbcDateJavaTypeDescriptor extends AbstractTemporalJavaTypeDescript
@Override
public int extractHashCode(Date value) {
Calendar calendar = Calendar.getInstance();
final Calendar calendar = Calendar.getInstance();
calendar.setTime( value );
int hashCode = 1;
hashCode = 31 * hashCode + calendar.get( Calendar.MONTH );
@ -122,56 +97,80 @@ public class JdbcDateJavaTypeDescriptor extends AbstractTemporalJavaTypeDescript
return hashCode;
}
@SuppressWarnings({ "unchecked" })
@Override
public <X> X unwrap(Date value, Class<X> type, WrapperOptions options) {
public Date coerce(Object value, CoercionContext coercionContext) {
return wrap( value, null );
}
@SuppressWarnings("unchecked")
@Override
public Object unwrap(Date value, Class type, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( LocalDate.class.isAssignableFrom( type ) ) {
return unwrapLocalDate( value );
}
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 (X) rtn;
return unwrapSqlDate( value );
}
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() );
return (X) rtn;
if ( java.util.Date.class.isAssignableFrom( type ) ) {
return value;
}
if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) {
final java.sql.Timestamp rtn = value instanceof java.sql.Timestamp
? ( java.sql.Timestamp ) value
: new java.sql.Timestamp( value.getTime() );
return (X) rtn;
if ( Long.class.isAssignableFrom( type ) ) {
return unwrapDateEpoch( value );
}
if ( Date.class.isAssignableFrom( type ) ) {
return (X) value;
if ( String.class.isAssignableFrom( type ) ) {
return toString( value );
}
if ( Calendar.class.isAssignableFrom( type ) ) {
final GregorianCalendar cal = new GregorianCalendar();
cal.setTimeInMillis( value.getTime() );
return (X) cal;
cal.setTimeInMillis( unwrapDateEpoch( value ) );
return cal;
}
if ( Long.class.isAssignableFrom( type ) ) {
return (X) Long.valueOf( value.getTime() );
if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) {
return new java.sql.Timestamp( value.getTime() );
}
if ( LocalDate.class.isAssignableFrom( type ) ) {
if ( value instanceof java.sql.Date ) {
return (X) ( (java.sql.Date) value ).toLocalDate();
}
if ( java.sql.Time.class.isAssignableFrom( type ) ) {
throw new IllegalArgumentException( "Illegal attempt to treat `java.sql.Date` as `java.sql.Time`" );
}
throw unknownUnwrap( type );
}
private LocalDate unwrapLocalDate(Date value) {
return value instanceof java.sql.Date
? ( (java.sql.Date) value ).toLocalDate()
: new java.sql.Date( value.getTime() ).toLocalDate();
}
private java.sql.Date unwrapSqlDate(Date value) {
return value instanceof java.sql.Date
? (java.sql.Date) value
: new java.sql.Date( value.getTime() );
}
private static long unwrapDateEpoch(Date value) {
return value.getTime();
}
@Override
public <X> Date wrap(X value, WrapperOptions options) {
public Date wrap(Object value, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( value instanceof java.sql.Date ) {
return (Date) value;
return (java.sql.Date) value;
}
if ( value instanceof Long ) {
@ -192,4 +191,42 @@ public class JdbcDateJavaTypeDescriptor extends AbstractTemporalJavaTypeDescript
throw unknownWrap( value.getClass() );
}
@Override
public String toString(Date value) {
return new SimpleDateFormat( DATE_FORMAT ).format( value );
}
@Override
public Date fromString(CharSequence string) {
try {
return new java.sql.Date( new SimpleDateFormat(DATE_FORMAT).parse( string.toString() ).getTime() );
}
catch ( ParseException pe) {
throw new HibernateException( "could not parse date string" + string, pe );
}
}
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeDescriptorIndicators context) {
return context.getTypeConfiguration().getJdbcTypeDescriptorRegistry().getDescriptor( Types.DATE );
}
@Override
protected <X> TemporalJavaTypeDescriptor<X> forDatePrecision(TypeConfiguration typeConfiguration) {
//noinspection unchecked
return (TemporalJavaTypeDescriptor<X>) this;
}
public static class DateMutabilityPlan extends MutableMutabilityPlan<Date> {
public static final DateMutabilityPlan INSTANCE = new DateMutabilityPlan();
@Override
public Date deepCopyNotNull(Date value) {
if ( value instanceof java.sql.Date ) {
return value;
}
return new java.sql.Date( value.getTime() );
}
}
}

View File

@ -16,8 +16,6 @@ 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.type.descriptor.WrapperOptions;
@ -25,10 +23,15 @@ import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeDescriptorIndicators;
import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.TemporalType;
/**
* Descriptor for {@link Time} handling.
*
* @author Steve Ebersole
* @implSpec Unlike most {@link JavaType} implementations, can handle 2 different "domain
* representations" (most map just a single type): general {@link Date} values in addition
* to {@link Time} values. This capability is shared with
* {@link JdbcDateJavaTypeDescriptor} and {@link JdbcTimestampJavaTypeDescriptor}.
*/
public class JdbcTimeJavaTypeDescriptor extends AbstractTemporalJavaTypeDescriptor<Date> {
public static final JdbcTimeJavaTypeDescriptor INSTANCE = new JdbcTimeJavaTypeDescriptor();
@ -48,19 +51,9 @@ public class JdbcTimeJavaTypeDescriptor extends AbstractTemporalJavaTypeDescript
@SuppressWarnings("unused")
public static final DateTimeFormatter LOGGABLE_FORMATTER = DateTimeFormatter.ISO_LOCAL_TIME;
public static class TimeMutabilityPlan extends MutableMutabilityPlan<Date> {
public static final TimeMutabilityPlan INSTANCE = new TimeMutabilityPlan();
@Override
public Date deepCopyNotNull(Date value) {
return value instanceof Time
? new Time( value.getTime() )
: new Date( value.getTime() );
}
}
@SuppressWarnings("WeakerAccess")
public JdbcTimeJavaTypeDescriptor() {
super( Date.class, TimeMutabilityPlan.INSTANCE );
super( Time.class, TimeMutabilityPlan.INSTANCE );
}
@Override
@ -69,14 +62,125 @@ public class JdbcTimeJavaTypeDescriptor extends AbstractTemporalJavaTypeDescript
}
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeDescriptorIndicators context) {
return context.getTypeConfiguration().getJdbcTypeDescriptorRegistry().getDescriptor( Types.TIME );
public boolean isInstance(Object value) {
// this check holds true for java.sql.Time as well
return value instanceof Date
&& !( value instanceof java.sql.Date );
}
@Override
protected <X> TemporalJavaTypeDescriptor<X> forTimePrecision(TypeConfiguration typeConfiguration) {
//noinspection unchecked
return (TemporalJavaTypeDescriptor<X>) this;
public int extractHashCode(Date value) {
final Calendar calendar = Calendar.getInstance();
calendar.setTime( value );
int hashCode = 1;
hashCode = 31 * hashCode + calendar.get( Calendar.HOUR_OF_DAY );
hashCode = 31 * hashCode + calendar.get( Calendar.MINUTE );
hashCode = 31 * hashCode + calendar.get( Calendar.SECOND );
hashCode = 31 * hashCode + calendar.get( Calendar.MILLISECOND );
return hashCode;
}
@Override
public boolean areEqual(Date one, Date another) {
if ( one == another ) {
return true;
}
if ( one == null || another == null ) {
return false;
}
if ( one.getTime() == another.getTime() ) {
return true;
}
final Calendar calendar1 = Calendar.getInstance();
final Calendar calendar2 = Calendar.getInstance();
calendar1.setTime( one );
calendar2.setTime( another );
return calendar1.get( Calendar.HOUR_OF_DAY ) == calendar2.get( Calendar.HOUR_OF_DAY )
&& calendar1.get( Calendar.MINUTE ) == calendar2.get( Calendar.MINUTE )
&& calendar1.get( Calendar.SECOND ) == calendar2.get( Calendar.SECOND )
&& calendar1.get( Calendar.MILLISECOND ) == calendar2.get( Calendar.MILLISECOND );
}
@Override
public Date coerce(Object value, CoercionContext coercionContext) {
return wrap( value, null );
}
@SuppressWarnings("unchecked")
@Override
public Object unwrap(Date value, Class type, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( LocalTime.class.isAssignableFrom( type ) ) {
return value instanceof java.sql.Time
? ( (java.sql.Time) value ).toLocalTime()
: new java.sql.Time( value.getTime() ).toLocalTime();
}
if ( Time.class.isAssignableFrom( type ) ) {
return value instanceof Time
? value
: new Time( value.getTime() );
}
if ( Date.class.isAssignableFrom( type ) ) {
return value;
}
if ( Long.class.isAssignableFrom( type ) ) {
return value.getTime();
}
if ( String.class.isAssignableFrom( type ) ) {
return toString( value );
}
if ( Calendar.class.isAssignableFrom( type ) ) {
final GregorianCalendar cal = new GregorianCalendar();
cal.setTimeInMillis( value.getTime() );
return cal;
}
if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) {
return new java.sql.Timestamp( value.getTime() );
}
if ( java.sql.Date.class.isAssignableFrom( type ) ) {
throw new IllegalArgumentException( "Illegal attempt to treat `java.sql.Time` as `java.sql.Date`" );
}
throw unknownUnwrap( type );
}
@Override
public Date wrap(Object value, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( value instanceof LocalTime ) {
return Time.valueOf( (LocalTime) value );
}
if ( value instanceof Date ) {
return (Date) value;
}
if ( value instanceof Long ) {
return new Time( (Long) value );
}
if ( value instanceof Calendar ) {
return new Time( ( (Calendar) value ).getTimeInMillis() );
}
throw unknownWrap( value.getClass() );
}
@Override
@ -95,113 +199,28 @@ public class JdbcTimeJavaTypeDescriptor extends AbstractTemporalJavaTypeDescript
}
@Override
public int extractHashCode(Date value) {
Calendar calendar = Calendar.getInstance();
calendar.setTime( value );
int hashCode = 1;
hashCode = 31 * hashCode + calendar.get( Calendar.HOUR_OF_DAY );
hashCode = 31 * hashCode + calendar.get( Calendar.MINUTE );
hashCode = 31 * hashCode + calendar.get( Calendar.SECOND );
hashCode = 31 * hashCode + calendar.get( Calendar.MILLISECOND );
return hashCode;
}
@Override
public boolean areEqual(Date one, Date another) {
if ( one == another ) {
return true;
}
if ( one == null || another == null ) {
return false;
}
if ( one.getTime() == another.getTime() ) {
return true;
}
Calendar calendar1 = Calendar.getInstance();
Calendar calendar2 = Calendar.getInstance();
calendar1.setTime( one );
calendar2.setTime( another );
return calendar1.get( Calendar.HOUR_OF_DAY ) == calendar2.get( Calendar.HOUR_OF_DAY )
&& calendar1.get( Calendar.MINUTE ) == calendar2.get( Calendar.MINUTE )
&& calendar1.get( Calendar.SECOND ) == calendar2.get( Calendar.SECOND )
&& calendar1.get( Calendar.MILLISECOND ) == calendar2.get( Calendar.MILLISECOND );
}
@SuppressWarnings({ "unchecked" })
@Override
public <X> X unwrap(Date value, Class<X> type, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( Time.class.isAssignableFrom( type ) ) {
final Time rtn = value instanceof Time
? ( Time ) value
: new Time( value.getTime() );
return (X) rtn;
}
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 (X) rtn;
}
if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) {
final java.sql.Timestamp rtn = value instanceof java.sql.Timestamp
? ( java.sql.Timestamp ) value
: new java.sql.Timestamp( value.getTime() );
return (X) rtn;
}
if ( Date.class.isAssignableFrom( type ) ) {
return (X) value;
}
if ( Calendar.class.isAssignableFrom( type ) ) {
final GregorianCalendar cal = new GregorianCalendar();
cal.setTimeInMillis( value.getTime() );
return (X) cal;
}
if ( Long.class.isAssignableFrom( type ) ) {
return (X) Long.valueOf( value.getTime() );
}
if ( LocalTime.class.isAssignableFrom( type ) ) {
if ( value instanceof Time ) {
return (X) ( (Time) value ).toLocalTime();
}
}
throw unknownUnwrap( type );
}
@Override
public <X> Date wrap(X value, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( value instanceof Time ) {
return (Time) value;
}
if ( value instanceof Long ) {
return new Time( (Long) value );
}
if ( value instanceof Calendar ) {
return new Time( ( (Calendar) value ).getTimeInMillis() );
}
if ( value instanceof Date ) {
return new Time( ( (Date) value ).getTime() );
}
if ( value instanceof LocalTime ) {
return Time.valueOf( (LocalTime) value );
}
throw unknownWrap( value.getClass() );
public JdbcType getRecommendedJdbcType(JdbcTypeDescriptorIndicators context) {
return context.getTypeConfiguration().getJdbcTypeDescriptorRegistry().getDescriptor( Types.TIME );
}
@Override
public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) {
return 0; //seconds (currently ignored since Dialects don't parameterize time type by precision)
//seconds (currently ignored since Dialects don't parameterize time type by precision)
return 0;
}
@Override
protected <X> TemporalJavaTypeDescriptor<X> forTimePrecision(TypeConfiguration typeConfiguration) {
//noinspection unchecked
return (TemporalJavaTypeDescriptor<X>) this;
}
public static class TimeMutabilityPlan extends MutableMutabilityPlan<Date> {
public static final TimeMutabilityPlan INSTANCE = new TimeMutabilityPlan();
@Override
public Date deepCopyNotNull(Date value) {
return new Time( value.getTime() );
}
}
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.type.descriptor.java;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.ZoneId;
@ -31,7 +32,10 @@ import org.hibernate.type.spi.TypeConfiguration;
/**
* Descriptor for {@link Timestamp} handling.
*
* @author Steve Ebersole
* @implSpec Unlike most {@link JavaType} implementations, can handle 2 different "domain
* representations" (most map just a single type): general {@link Date} values in addition
* to {@link Timestamp} values. This capability is shared with
* {@link JdbcDateJavaTypeDescriptor} and {@link JdbcTimeJavaTypeDescriptor}.
*/
public class JdbcTimestampJavaTypeDescriptor extends AbstractTemporalJavaTypeDescriptor<Date> implements VersionJavaType<Date> {
public static final JdbcTimestampJavaTypeDescriptor INSTANCE = new JdbcTimestampJavaTypeDescriptor();
@ -48,25 +52,9 @@ public class JdbcTimestampJavaTypeDescriptor extends AbstractTemporalJavaTypeDes
public static final DateTimeFormatter LITERAL_FORMATTER = DateTimeFormatter.ofPattern( TIMESTAMP_FORMAT )
.withZone( ZoneId.from( ZoneOffset.UTC ) );
public static class TimestampMutabilityPlan extends MutableMutabilityPlan<Date> {
public static final TimestampMutabilityPlan INSTANCE = new TimestampMutabilityPlan();
@Override
public Date deepCopyNotNull(Date value) {
if ( value instanceof Timestamp ) {
Timestamp orig = (Timestamp) value;
Timestamp ts = new Timestamp( orig.getTime() );
ts.setNanos( orig.getNanos() );
return ts;
}
else {
return new Date( value.getTime() );
}
}
}
@SuppressWarnings("WeakerAccess")
public JdbcTimestampJavaTypeDescriptor() {
super( Date.class, TimestampMutabilityPlan.INSTANCE );
super( Timestamp.class, TimestampMutabilityPlan.INSTANCE );
}
@Override
@ -75,33 +63,9 @@ public class JdbcTimestampJavaTypeDescriptor extends AbstractTemporalJavaTypeDes
}
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeDescriptorIndicators context) {
return context.getTypeConfiguration().getJdbcTypeDescriptorRegistry().getDescriptor( Types.TIMESTAMP );
}
@Override
protected <X> TemporalJavaTypeDescriptor<X> forTimestampPrecision(TypeConfiguration typeConfiguration) {
//noinspection unchecked
return (TemporalJavaTypeDescriptor<X>) this;
}
@Override
public String toString(Date value) {
return LITERAL_FORMATTER.format( value.toInstant() );
}
@Override
public Date fromString(CharSequence string) {
try {
final TemporalAccessor accessor = LITERAL_FORMATTER.parse( string );
return new Timestamp(
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 );
}
public boolean isInstance(Object value) {
// this check holds true for java.sql.Timestamp as well
return value instanceof Date;
}
@Override
@ -143,41 +107,50 @@ public class JdbcTimestampJavaTypeDescriptor extends AbstractTemporalJavaTypeDes
return Long.valueOf( value.getTime() / 1000 ).hashCode();
}
@SuppressWarnings({ "unchecked" })
@Override
public <X> X unwrap(Date value, Class<X> type, WrapperOptions options) {
public Date coerce(Object value, CoercionContext coercionContext) {
return wrap( value, null );
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Object unwrap(Date value, Class type, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( Timestamp.class.isAssignableFrom( type ) ) {
final Timestamp rtn = value instanceof Timestamp
? ( Timestamp ) value
return value instanceof Timestamp
? (Timestamp) value
: new Timestamp( value.getTime() );
return (X) rtn;
}
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 (X) 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() );
return (X) rtn;
}
if ( Date.class.isAssignableFrom( type ) ) {
return (X) value;
return value;
}
if ( Calendar.class.isAssignableFrom( type ) ) {
final GregorianCalendar cal = new GregorianCalendar();
cal.setTimeInMillis( value.getTime() );
return (X) cal;
return cal;
}
if ( Long.class.isAssignableFrom( type ) ) {
return (X) Long.valueOf( value.getTime() );
return value.getTime();
}
if ( java.sql.Date.class.isAssignableFrom( type ) ) {
return value instanceof java.sql.Date
? ( java.sql.Date ) value
: new java.sql.Date( value.getTime() );
}
if ( java.sql.Time.class.isAssignableFrom( type ) ) {
return value instanceof java.sql.Time
? ( java.sql.Time ) value
: new java.sql.Time( value.getTime() );
}
throw unknownUnwrap( type );
}
@ -190,6 +163,10 @@ public class JdbcTimestampJavaTypeDescriptor extends AbstractTemporalJavaTypeDes
return (Timestamp) value;
}
if ( value instanceof Date ) {
return new Timestamp( ( (Date) value ).getTime() );
}
if ( value instanceof Long ) {
return new Timestamp( (Long) value );
}
@ -198,13 +175,39 @@ public class JdbcTimestampJavaTypeDescriptor extends AbstractTemporalJavaTypeDes
return new Timestamp( ( (Calendar) value ).getTimeInMillis() );
}
if ( value instanceof Date ) {
return new Timestamp( ( (Date) value ).getTime() );
}
throw unknownWrap( value.getClass() );
}
@Override
public String toString(Date value) {
return LITERAL_FORMATTER.format( value.toInstant() );
}
@Override
public Date fromString(CharSequence string) {
try {
final TemporalAccessor accessor = LITERAL_FORMATTER.parse( string );
return new Timestamp(
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 );
}
}
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeDescriptorIndicators context) {
return context.getTypeConfiguration().getJdbcTypeDescriptorRegistry().getDescriptor( Types.TIMESTAMP );
}
@Override
protected <X> TemporalJavaTypeDescriptor<X> forTimestampPrecision(TypeConfiguration typeConfiguration) {
//noinspection unchecked
return (TemporalJavaTypeDescriptor<X>) this;
}
@Override
public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) {
return dialect.getDefaultTimestampPrecision();
@ -220,4 +223,21 @@ public class JdbcTimestampJavaTypeDescriptor extends AbstractTemporalJavaTypeDes
return new Timestamp( System.currentTimeMillis() );
}
public static class TimestampMutabilityPlan extends MutableMutabilityPlan<Date> {
public static final TimestampMutabilityPlan INSTANCE = new TimestampMutabilityPlan();
@Override
public Date deepCopyNotNull(Date value) {
if ( value instanceof Timestamp ) {
// make sure to get the nanos
final Timestamp orig = (Timestamp) value;
final Timestamp copy = new Timestamp( orig.getTime() );
copy.setNanos( orig.getNanos() );
return copy;
}
else {
return new Date( value.getTime() );
}
}
}
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.orm.test.bootstrap.binding.annotations.basics;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.Date;
import java.util.Iterator;
@ -61,7 +62,7 @@ public class SimpleEntityTypeResolutionsTests {
case "someDate": {
assertThat(
propertyResolution.getDomainJavaDescriptor().getJavaTypeClass(),
sameInstance( Date.class )
sameInstance( Timestamp.class )
);
assertThat( propertyValue.getTemporalPrecision(), is( TemporalType.TIMESTAMP ) );
break;

View File

@ -1,13 +1,13 @@
package org.hibernate.orm.test.type.descriptor.java;
import java.util.Date;
import java.sql.Date;
import org.hibernate.type.descriptor.java.JdbcDateJavaTypeDescriptor;
import org.hibernate.testing.orm.junit.BaseUnitTest;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
@BaseUnitTest
public class JdbcDateJavaTypeDescriptorTest {
@ -15,7 +15,32 @@ public class JdbcDateJavaTypeDescriptorTest {
@Test
public void testToString() {
final JdbcDateJavaTypeDescriptor javaTypeDescriptor = JdbcDateJavaTypeDescriptor.INSTANCE;
final String actual = javaTypeDescriptor.toString( new Date( 0 ) );
assertEquals( "01 January 1970", actual );
final String utilDate = javaTypeDescriptor.toString( new java.util.Date( 0 ) );
assertThat( utilDate ).isEqualTo( "01 January 1970" );
final String sqlDate = javaTypeDescriptor.toString( new java.sql.Date( 0 ) );
assertThat( sqlDate ).isEqualTo( "01 January 1970" );
}
@Test
public void testIsInstance() {
final JdbcDateJavaTypeDescriptor javaTypeDescriptor = JdbcDateJavaTypeDescriptor.INSTANCE;
javaTypeDescriptor.isInstance( new java.sql.Date( 0 ) );
javaTypeDescriptor.isInstance( new java.util.Date( 0 ) );
}
@Test
public void testWrap() {
final JdbcDateJavaTypeDescriptor javaTypeDescriptor = JdbcDateJavaTypeDescriptor.INSTANCE;
final Date sqlDate = new Date( 0 );
final java.util.Date wrappedSqlDate = javaTypeDescriptor.wrap( sqlDate, null );
assertThat( wrappedSqlDate ).isSameAs( sqlDate );
final java.util.Date utilDate = new java.util.Date( 0 );
final java.util.Date wrappedUtilDate = javaTypeDescriptor.wrap( utilDate, null );
assertThat( wrappedUtilDate ).isInstanceOf( java.sql.Date.class );
}
}