diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java index 2f55aec0addb..848e097a653b 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java @@ -45,6 +45,7 @@ import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.Table; +import org.hibernate.type.BasicType; import org.hibernate.type.CharacterArrayClobType; import org.hibernate.type.CharacterArrayNClobType; import org.hibernate.type.CharacterNCharType; @@ -532,10 +533,15 @@ public void fillSimpleValue() { simpleValue.setTypeName( timeStampVersionType ); } - if ( simpleValue.getTypeName() != null && simpleValue.getTypeName().length() > 0 - && simpleValue.getMetadata().getTypeResolver().basic( simpleValue.getTypeName() ) == null ) { + if ( simpleValue.getTypeName() != null && simpleValue.getTypeName().length() > 0 ) { try { - Class typeClass = buildingContext.getBootstrapContext().getClassLoaderAccess().classForName( simpleValue.getTypeName() ); + BasicType basicType = simpleValue.getMetadata().getTypeResolver().basic( simpleValue.getTypeName() ); + + Class typeClass = ( basicType != null ) ? + basicType.getClass() : + buildingContext.getBootstrapContext() + .getClassLoaderAccess() + .classForName( simpleValue.getTypeName() ); if ( typeClass != null && DynamicParameterizedType.class.isAssignableFrom( typeClass ) ) { Properties parameters = simpleValue.getTypeParameters(); diff --git a/hibernate-core/src/main/java/org/hibernate/type/TimestampType.java b/hibernate-core/src/main/java/org/hibernate/type/TimestampType.java index 0ad68124b2ad..7193561364ad 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TimestampType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TimestampType.java @@ -9,12 +9,15 @@ import java.sql.Timestamp; import java.util.Comparator; import java.util.Date; +import java.util.Properties; import org.hibernate.HibernateException; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.type.descriptor.java.JdbcTimestampTypeDescriptor; +import org.hibernate.type.descriptor.java.SerializableTypeDescriptor; import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor; +import org.hibernate.usertype.DynamicParameterizedType; /** * A type that maps between {@link java.sql.Types#TIMESTAMP TIMESTAMP} and {@link java.sql.Timestamp} @@ -24,7 +27,7 @@ */ public class TimestampType extends AbstractSingleColumnStandardBasicType - implements VersionType, LiteralType { + implements VersionType, LiteralType, DynamicParameterizedType { public static final TimestampType INSTANCE = new TimestampType(); @@ -70,4 +73,13 @@ public String objectToSQLString(Date value, Dialect dialect) throws Exception { public Date fromStringValue(String xml) throws HibernateException { return fromString( xml ); } + + @Override + public void setParameterValues(Properties parameters) { + final ParameterType reader = (ParameterType) parameters.get( PARAMETER_TYPE ); + + if ( reader != null ) { + setJavaTypeDescriptor( new JdbcTimestampTypeDescriptor( reader.getReturnedClass() ) ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/TypeResolver.java b/hibernate-core/src/main/java/org/hibernate/type/TypeResolver.java index c652428cbdb8..fdfa0e932a55 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TypeResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TypeResolver.java @@ -14,6 +14,7 @@ import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.usertype.CompositeUserType; +import org.hibernate.usertype.ParameterizedType; import org.hibernate.usertype.UserType; /** @@ -115,8 +116,11 @@ public Type heuristicType(String typeName) throws MappingException { */ public Type heuristicType(String typeName, Properties parameters) throws MappingException { Type type = basic( typeName ); + if ( type != null ) { - return type; + return ( type instanceof ParameterizedType ) ? + typeFactory.byClass( type.getClass(), parameters ) : + type; } try { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimestampTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimestampTypeDescriptor.java index 1f2fa8e809e1..b5c4dc48cadd 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimestampTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimestampTypeDescriptor.java @@ -41,9 +41,18 @@ public Date deepCopyNotNull(Date value) { } } + private final boolean defaultToTimestamp; + public JdbcTimestampTypeDescriptor() { super( Date.class, TimestampMutabilityPlan.INSTANCE ); + defaultToTimestamp = true; + } + + public JdbcTimestampTypeDescriptor(Class type) { + super( type, TimestampMutabilityPlan.INSTANCE ); + defaultToTimestamp = false; } + @Override public String toString(Date value) { return new SimpleDateFormat( TIMESTAMP_FORMAT ).format( value ); @@ -139,8 +148,12 @@ public Date wrap(X value, WrapperOptions options) { if ( value == null ) { return null; } + if ( Timestamp.class.isInstance( value ) ) { - return (Timestamp) value; + Timestamp timestampValue = (Timestamp) value; + return ( java.util.Date.class.equals( getJavaType() ) ) && !defaultToTimestamp ? + new Date( timestampValue.getTime() ) : + timestampValue; } if ( Long.class.isInstance( value ) ) { @@ -152,7 +165,11 @@ public Date wrap(X value, WrapperOptions options) { } if ( Date.class.isInstance( value ) ) { - return new Timestamp( ( (Date) value ).getTime() ); + Date dateValue = (Date) value; + + return ( java.util.Date.class.equals( getJavaType() ) ) ? + new Date( dateValue.getTime() ) : + new Timestamp( dateValue.getTime() ); } throw unknownWrap( value.getClass() ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/TimestampRetainTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/TimestampRetainTypeTest.java new file mode 100644 index 000000000000..75d244c33fcc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/type/TimestampRetainTypeTest.java @@ -0,0 +1,128 @@ +package org.hibernate.test.type; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.util.Date; +import java.util.Map; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.MySQL5Dialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.RequiresDialect; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class TimestampRetainTypeTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + UserAccount.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + UserAccount user = new UserAccount() + .setId( 1L ) + .setFirstName( "Vlad" ) + .setLastName( "Mihalcea" ) + .setSubscribedOn( + parseTimestamp("2013-09-29 12:30:00") + ); + + entityManager.persist( user ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + UserAccount userAccount = entityManager.find( + UserAccount.class, 1L + ); + + assertEquals( + parseTimestamp("2013-09-29 12:30:00"), + userAccount.getSubscribedOn() + ); + + assertEquals( Date.class, userAccount.getSubscribedOn().getClass() ); + } ); + } + + private final SimpleDateFormat DATE_TIME_FORMAT = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss"); + + private java.util.Date parseTimestamp(String timestamp) { + try { + return DATE_TIME_FORMAT.parse(timestamp); + } catch (ParseException e) { + throw new IllegalArgumentException(e); + } + } + + @Entity(name = "UserAccount") + @Table(name = "user_account") + public static class UserAccount { + + @Id + private Long id; + + @Column(name = "first_name", length = 50) + private String firstName; + + @Column(name = "last_name", length = 50) + private String lastName; + + @Column(name = "subscribed_on") + @Temporal(TemporalType.TIMESTAMP) + private Date subscribedOn; + + public Long getId() { + return id; + } + + public UserAccount setId(Long id) { + this.id = id; + return this; + } + + public String getFirstName() { + return firstName; + } + + public UserAccount setFirstName(String firstName) { + this.firstName = firstName; + return this; + } + + public String getLastName() { + return lastName; + } + + public UserAccount setLastName(String lastName) { + this.lastName = lastName; + return this; + } + + public Date getSubscribedOn() { + return subscribedOn; + } + + public UserAccount setSubscribedOn(Date subscribedOn) { + this.subscribedOn = subscribedOn; + return this; + } + } +}