diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index 5838943cc7..1520d48d65 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -5577,9 +5577,31 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun /** * Does this dialect support appending table options SQL fragment at the end of the SQL Table creation statement? * - * @return {@code true} indicates it does; {@code false} indicates it does not; + * @return {@code true} indicates it does; {@code false} indicates it does not; */ - public boolean supportsTableOptions(){ + public boolean supportsTableOptions() { + return false; + } + + /** + * Does this dialect support binding {@link Types#NULL} for {@link PreparedStatement#setNull(int, int)}? + * if it does, then call of {@link PreparedStatement#getParameterMetaData()} could be eliminated for better performance. + * + * @return {@code true} indicates it does; {@code false} indicates it does not; + * @see org.hibernate.type.descriptor.jdbc.ObjectNullResolvingJdbcType + */ + public boolean supportsBindingNullSqlTypeForSetNull() { + return false; + } + + /** + * Does this dialect support binding {@code null} for {@link PreparedStatement#setObject(int, Object)}? + * if it does, then call of {@link PreparedStatement#getParameterMetaData()} could be eliminated for better performance. + * + * @return {@code true} indicates it does; {@code false} indicates it does not; + * @see org.hibernate.type.descriptor.jdbc.ObjectNullResolvingJdbcType + */ + public boolean supportsBindingNullForSetObject() { return false; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java index 4e71a59999..92011616a5 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -1007,4 +1007,9 @@ public class H2Dialect extends Dialect { return true; } + @Override + public boolean supportsBindingNullSqlTypeForSetNull() { + return true; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java index ec4c548fcb..5ccacaa7af 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java @@ -1513,4 +1513,9 @@ public class MySQLDialect extends Dialect { ? sqlCheckConstraint + " " + checkConstraint.getOptions() : sqlCheckConstraint; } + + @Override + public boolean supportsBindingNullSqlTypeForSetNull() { + return true; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java index 029bc53d77..2cc8f3ba8b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -1498,4 +1498,9 @@ public class PostgreSQLDialect extends Dialect { public boolean supportsFromClauseInUpdate() { return true; } + + @Override + public boolean supportsBindingNullSqlTypeForSetNull() { + return true; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java index 63962ddd88..2e6a68f5af 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -1136,4 +1136,9 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { private String getCheckConstraintOptions(CheckConstraint checkConstraint) { return isNotEmpty( checkConstraint.getOptions() ) ? checkConstraint.getOptions() + " " : ""; } + + @Override + public boolean supportsBindingNullForSetObject() { + return true; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ObjectNullResolvingJdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ObjectNullResolvingJdbcType.java index 00e6a3f14d..b1fd1bdb01 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ObjectNullResolvingJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ObjectNullResolvingJdbcType.java @@ -42,13 +42,27 @@ public class ObjectNullResolvingJdbcType extends ObjectJdbcType { @Override protected void doBindNull(PreparedStatement st, int index, WrapperOptions options) throws SQLException { - st.setNull( index, st.getParameterMetaData().getParameterType( index ) ); + if ( options.getDialect().supportsBindingNullForSetObject() ) { + st.setObject( index, null ); + } + else { + final int sqlType = options.getDialect().supportsBindingNullSqlTypeForSetNull() ? Types.NULL + : st.getParameterMetaData().getParameterType( index ); + st.setNull( index, sqlType ); + } } @Override protected void doBindNull(CallableStatement st, String name, WrapperOptions options) throws SQLException { - st.setNull( name, Types.JAVA_OBJECT ); + if ( options.getDialect().supportsBindingNullForSetObject() ) { + st.setObject( name, null ); + } + else { + final int sqlType = options.getDialect().supportsBindingNullSqlTypeForSetNull() ? Types.NULL + : Types.JAVA_OBJECT; + st.setNull( name, sqlType ); + } } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/typedescriptor/NullTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/typedescriptor/NullTest.java new file mode 100644 index 0000000000..77aa747e02 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/typedescriptor/NullTest.java @@ -0,0 +1,100 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.orm.test.typedescriptor; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * @author Yanming Zhou + */ +@DomainModel( + annotatedClasses = NullTest.SimpleEntity.class +) +@SessionFactory +public class NullTest { + + @BeforeEach + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( + session -> session.persist( new SimpleEntity() ) + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( + session -> + session.createMutationQuery( "delete from SimpleEntity" ).executeUpdate() + ); + } + + @Test + @JiraKey("HHH-18581") + public void passingNullAsParameterOfNativeQuery(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + SimpleEntity persisted = session.createNativeQuery( + "select * from SimpleEntity where name is null or name=:name", + SimpleEntity.class + ).setParameter( "name", null ).uniqueResult(); + + assertNotNull( persisted ); + } + ); + } + + @Test + public void passingNullAsParameterOfQuery(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + SimpleEntity persisted = session.createQuery( + "from SimpleEntity where name is null or name=:name", + SimpleEntity.class + ).setParameter( "name", null ).uniqueResult(); + + assertNotNull( persisted ); + } + ); + } + + @Entity(name = "SimpleEntity") + static class SimpleEntity { + @Id + @GeneratedValue + private Integer id; + + private String name; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +}