HHH-18581 Introduce `supportsBindingNullSqlTypeForSetNull()` and `supportsBindingNullForSetObject()` for `Dialect` to optimize binding null

The method `PreparedStatement.getParameterMetaData().getParameterType(int)` call is expensive for some JDBC driver such as pgJDBC, we should avoid it if the driver supports binding `Types.NULL` for `setNull()` or `null` for `setObject()`.
This commit is contained in:
Yanming Zhou 2024-09-09 11:35:38 +08:00 committed by Christian Beikov
parent 2e54d95707
commit 3c4a340c5e
7 changed files with 160 additions and 4 deletions

View File

@ -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? * 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; return false;
} }

View File

@ -1007,4 +1007,9 @@ public class H2Dialect extends Dialect {
return true; return true;
} }
@Override
public boolean supportsBindingNullSqlTypeForSetNull() {
return true;
}
} }

View File

@ -1513,4 +1513,9 @@ public class MySQLDialect extends Dialect {
? sqlCheckConstraint + " " + checkConstraint.getOptions() ? sqlCheckConstraint + " " + checkConstraint.getOptions()
: sqlCheckConstraint; : sqlCheckConstraint;
} }
@Override
public boolean supportsBindingNullSqlTypeForSetNull() {
return true;
}
} }

View File

@ -1498,4 +1498,9 @@ public class PostgreSQLDialect extends Dialect {
public boolean supportsFromClauseInUpdate() { public boolean supportsFromClauseInUpdate() {
return true; return true;
} }
@Override
public boolean supportsBindingNullSqlTypeForSetNull() {
return true;
}
} }

View File

@ -1136,4 +1136,9 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
private String getCheckConstraintOptions(CheckConstraint checkConstraint) { private String getCheckConstraintOptions(CheckConstraint checkConstraint) {
return isNotEmpty( checkConstraint.getOptions() ) ? checkConstraint.getOptions() + " " : ""; return isNotEmpty( checkConstraint.getOptions() ) ? checkConstraint.getOptions() + " " : "";
} }
@Override
public boolean supportsBindingNullForSetObject() {
return true;
}
} }

View File

@ -42,13 +42,27 @@ public class ObjectNullResolvingJdbcType extends ObjectJdbcType {
@Override @Override
protected void doBindNull(PreparedStatement st, int index, WrapperOptions options) protected void doBindNull(PreparedStatement st, int index, WrapperOptions options)
throws SQLException { 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 @Override
protected void doBindNull(CallableStatement st, String name, WrapperOptions options) protected void doBindNull(CallableStatement st, String name, WrapperOptions options)
throws SQLException { 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 @Override

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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;
}
}
}