diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/BindingTypeHelper.java b/hibernate-core/src/main/java/org/hibernate/query/internal/BindingTypeHelper.java index fcc45471dc15..12d34e14e184 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/BindingTypeHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/BindingTypeHelper.java @@ -41,18 +41,40 @@ public BindableType resolveTemporalPrecision( BindableType declaredParameterType, SessionFactoryImplementor sessionFactory) { if ( precision != null ) { - final SqmExpressible sqmExpressible = declaredParameterType.resolveExpressible( sessionFactory ); - if ( !( JavaTypeHelper.isTemporal( sqmExpressible.getExpressibleJavaType() ) ) ) { - throw new UnsupportedOperationException( - "Cannot treat non-temporal parameter type with temporal precision" - ); + final TemporalJavaType temporalJtd; + if ( declaredParameterType != null ) { + final SqmExpressible sqmExpressible = declaredParameterType.resolveExpressible( sessionFactory ); + if ( !( JavaTypeHelper.isTemporal( sqmExpressible.getExpressibleJavaType() ) ) ) { + throw new UnsupportedOperationException( + "Cannot treat non-temporal parameter type with temporal precision" + ); + } + temporalJtd = (TemporalJavaType) sqmExpressible.getExpressibleJavaType(); + } + else { + temporalJtd = null; } - final TemporalJavaType temporalJtd = (TemporalJavaType) sqmExpressible.getExpressibleJavaType(); - if ( temporalJtd.getPrecision() != precision ) { + if ( temporalJtd == null || temporalJtd.getPrecision() != precision ) { final TypeConfiguration typeConfiguration = sessionFactory.getTypeConfiguration(); + final TemporalJavaType temporalTypeForPrecision; + // Special case java.util.Date, because TemporalJavaType#resolveTypeForPrecision doesn't support widening, + // since the main purpose of that method is to determine the final java type based on the reflective type + // + the explicit @Temporal(TemporalType...) configuration + if ( temporalJtd == null || java.util.Date.class.isAssignableFrom( temporalJtd.getJavaTypeClass() ) ) { + //noinspection unchecked + temporalTypeForPrecision = (TemporalJavaType) typeConfiguration.getJavaTypeRegistry().getDescriptor( + TemporalJavaType.resolveJavaTypeClass( precision ) + ); + } + else { + temporalTypeForPrecision = temporalJtd.resolveTypeForPrecision( + precision, + typeConfiguration + ); + } return typeConfiguration.getBasicTypeRegistry().resolve( - temporalJtd.resolveTypeForPrecision( precision, typeConfiguration ), + temporalTypeForPrecision, TemporalJavaType.resolveJdbcTypeCode( precision ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/TemporalJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/TemporalJavaType.java index 8431a55e9a40..528b99604874 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/TemporalJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/TemporalJavaType.java @@ -33,6 +33,18 @@ static int resolveJdbcTypeCode(TemporalType requestedTemporalPrecision) { throw new UnsupportedOperationException( "Unsupported precision: " + requestedTemporalPrecision ); } + static Class resolveJavaTypeClass(TemporalType requestedTemporalPrecision) { + switch ( requestedTemporalPrecision ) { + case DATE: + return java.sql.Date.class; + case TIME: + return java.sql.Time.class; + case TIMESTAMP: + return java.sql.Timestamp.class; + } + throw new UnsupportedOperationException( "Unsupported precision: " + requestedTemporalPrecision ); + } + /** * The precision represented by this type */ diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/DateTimeParameterTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/DateTimeParameterTest.java index e3a523d0039b..2d4505b3ed7d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/DateTimeParameterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/DateTimeParameterTest.java @@ -6,100 +6,120 @@ */ package org.hibernate.orm.test.jpa.query; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.List; import jakarta.persistence.Entity; -import jakarta.persistence.EntityManager; import jakarta.persistence.Id; +import jakarta.persistence.Parameter; import jakarta.persistence.Query; import jakarta.persistence.Table; import jakarta.persistence.Temporal; import jakarta.persistence.TemporalType; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.List; -import org.junit.Test; +import org.hibernate.SessionFactory; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; /** * @author Steve Ebersole */ -public class DateTimeParameterTest extends BaseEntityManagerFunctionalTestCase { +@Jpa(annotatedClasses = {DateTimeParameterTest.Thing.class}) +public class DateTimeParameterTest { private static GregorianCalendar nowCal = new GregorianCalendar(); private static Date now = new Date( nowCal.getTime().getTime() ); - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { Thing.class }; - } - @Test - public void testBindingCalendarAsDate() { - createTestData(); - - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - - try { - Query query = em.createQuery( "from Thing t where t.someDate = :aDate" ); + public void testBindingCalendarAsDate(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> { + final Query query = entityManager.createQuery( "from Thing t where t.someDate = :aDate" ); query.setParameter( "aDate", nowCal, TemporalType.DATE ); - List list = query.getResultList(); - assertEquals( 1, list.size() ); - } - finally { - em.getTransaction().rollback(); - em.close(); - } - - deleteTestData(); + final List list = query.getResultList(); + assertThat( list.size() ).isEqualTo( 1 ); + } ); } @Test - public void testBindingNulls() { - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - - try { - Query query = em.createQuery( "from Thing t where t.someDate = :aDate or t.someTime = :aTime or t.someTimestamp = :aTimestamp" ); + public void testBindingNulls(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> { + final Query query = entityManager.createQuery( + "from Thing t where t.someDate = :aDate or t.someTime = :aTime or t.someTimestamp = :aTimestamp" + ); query.setParameter( "aDate", (Date) null, TemporalType.DATE ); query.setParameter( "aTime", (Date) null, TemporalType.DATE ); query.setParameter( "aTimestamp", (Date) null, TemporalType.DATE ); - } - finally { - em.getTransaction().rollback(); - em.close(); - } + } ); + + } + + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-17151") + public void testBindingNullNativeQueryPositional(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> { + final Query query = entityManager.createNativeQuery( "update Thing set someDate = ?1 where id = 1" ); + //noinspection deprecation + query.setParameter( 1, (Date) null, TemporalType.DATE ); + assertThat( query.executeUpdate() ).isEqualTo( 1 ); + } ); + scope.inTransaction( entityManager -> assertThat( entityManager.find( Thing.class, 1 ).someDate ).isNull() ); + } + + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-17151") + public void testBindingNullNativeQueryNamed(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> { + final Query query = entityManager.createNativeQuery( "update Thing set someDate = :me where id = 1" ); + Parameter p = new Parameter<>() { + @Override + public String getName() { + return "me"; + } + + @Override + public Integer getPosition() { + return null; + } + + @Override + public Class getParameterType() { + return Date.class; + } + }; + //noinspection deprecation + query.setParameter( p, null, TemporalType.DATE ); + assertThat( query.executeUpdate() ).isEqualTo( 1 ); + } ); + scope.inTransaction( entityManager -> assertThat( entityManager.find( Thing.class, 1 ).someDate ).isNull() ); } - private void createTestData() { - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - em.persist( new Thing( 1, "test", now, now, now ) ); - em.getTransaction().commit(); - em.close(); + @BeforeEach + public void createTestData(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> entityManager.persist( new Thing( 1, "test", now, now, now ) ) ); } - private void deleteTestData() { - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - em.createQuery( "delete Thing" ).executeUpdate(); - em.getTransaction().commit(); - em.close(); + @AfterEach + public void deleteTestData(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> entityManager.createQuery( "delete from Thing" ).executeUpdate() ); } - @Entity( name="Thing" ) - @Table( name = "THING" ) + @Entity(name = "Thing") + @Table(name = "Thing") public static class Thing { @Id public Integer id; public String someString; - @Temporal( TemporalType.DATE ) + @Temporal(TemporalType.DATE) public Date someDate; - @Temporal( TemporalType.TIME ) + @Temporal(TemporalType.TIME) public Date someTime; - @Temporal( TemporalType.TIMESTAMP ) + @Temporal(TemporalType.TIMESTAMP) public Date someTimestamp; public Thing() {