cleaner approach to work around Oracle setNull(BOOLEAN) bug

- introduce doBindNull() in BasicBinder
- use WrapperOptions and FastSessionServices
- use getPreferredSqlTypeCodeForBoolean()
This commit is contained in:
gavin 2021-03-19 07:44:11 +01:00 committed by Gavin King
parent 0ecd66fd46
commit e60e3736a7
10 changed files with 76 additions and 88 deletions

View File

@ -29,7 +29,6 @@ import org.hibernate.exception.LockTimeoutException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate; import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor; import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor;
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor; import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.util.JdbcExceptionHelper; import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.StringHelper;
import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityMappingType;
@ -57,18 +56,11 @@ import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorOracleDatabaseImpl; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorOracleDatabaseImpl;
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.descriptor.JdbcTypeNameMapper;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.descriptor.jdbc.BasicBinder;
import org.hibernate.type.descriptor.jdbc.BlobTypeDescriptor; import org.hibernate.type.descriptor.jdbc.BlobTypeDescriptor;
import org.hibernate.type.descriptor.jdbc.BooleanTypeDescriptor;
import org.hibernate.type.descriptor.jdbc.JdbcTypeDescriptor; import org.hibernate.type.descriptor.jdbc.JdbcTypeDescriptor;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeDescriptorRegistry; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeDescriptorRegistry;
import java.sql.CallableStatement; import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Types; import java.sql.Types;
@ -78,8 +70,6 @@ import java.util.regex.Pattern;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
import org.jboss.logging.Logger;
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate; import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
import static org.hibernate.query.TemporalUnit.*; import static org.hibernate.query.TemporalUnit.*;
@ -656,79 +646,6 @@ public class OracleDialect extends Dialect {
typeContributions.contributeJdbcTypeDescriptor( descriptor ); typeContributions.contributeJdbcTypeDescriptor( descriptor );
} }
typeContributions.contributeJdbcTypeDescriptor( new OracleBooleanTypeDescriptor() );
}
private static final class OracleBooleanTypeDescriptor extends BooleanTypeDescriptor {
private static final Logger log = CoreLogging.logger( BasicBinder.class );
@Override
public <X> ValueBinder<X> getBinder(JavaTypeDescriptor<X> javaTypeDescriptor) {
return new ValueBinder<X>() {
private static final String BIND_MSG_TEMPLATE = "binding parameter [%s] as [%s] - [%s]";
private static final String NULL_BIND_MSG_TEMPLATE = "binding parameter [%s] as [%s] - [null]";
@Override
public final void bind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
if ( value == null ) {
if ( log.isTraceEnabled() ) {
log.trace(
String.format(
NULL_BIND_MSG_TEMPLATE,
index,
JdbcTypeNameMapper.getTypeName( Types.BOOLEAN )
)
);
}
st.setNull( index, Types.BIT );
}
else {
if ( log.isTraceEnabled() ) {
log.trace(
String.format(
BIND_MSG_TEMPLATE,
index,
JdbcTypeNameMapper.getTypeName( Types.BOOLEAN ),
javaTypeDescriptor.extractLoggableRepresentation( value )
)
);
}
st.setBoolean( index, javaTypeDescriptor.unwrap( value, Boolean.class, options ) );
}
}
@Override
public final void bind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException {
if ( value == null ) {
if ( log.isTraceEnabled() ) {
log.trace(
String.format(
NULL_BIND_MSG_TEMPLATE,
name,
JdbcTypeNameMapper.getTypeName( Types.BOOLEAN )
)
);
}
st.setNull( name, Types.BIT );
}
else {
if ( log.isTraceEnabled() ) {
log.trace(
String.format(
BIND_MSG_TEMPLATE,
name,
JdbcTypeNameMapper.getTypeName( Types.BOOLEAN ),
javaTypeDescriptor.extractLoggableRepresentation( value )
)
);
}
st.setBoolean( name, javaTypeDescriptor.unwrap( value, Boolean.class, options ) );
}
}
};
}
} }
@Override @Override

View File

@ -28,6 +28,11 @@ public abstract class AbstractDelegatingWrapperOptions implements WrapperOptions
return delegate().useStreamForLobBinding(); return delegate().useStreamForLobBinding();
} }
@Override
public int getPreferredSqlTypeCodeForBoolean() {
return delegate().getPreferredSqlTypeCodeForBoolean();
}
@Override @Override
public LobCreator getLobCreator() { public LobCreator getLobCreator() {
return delegate().getLobCreator(); return delegate().getLobCreator();

View File

@ -1095,6 +1095,11 @@ public class SessionDelegatorBaseImpl implements SessionImplementor {
return delegate.useStreamForLobBinding(); return delegate.useStreamForLobBinding();
} }
@Override
public int getPreferredSqlTypeCodeForBoolean() {
return delegate.getPreferredSqlTypeCodeForBoolean();
}
@Override @Override
public LobCreator getLobCreator() { public LobCreator getLobCreator() {
return delegate.getLobCreator(); return delegate.getLobCreator();

View File

@ -557,6 +557,11 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
return fastSessionServices.useStreamForLobBinding; return fastSessionServices.useStreamForLobBinding;
} }
@Override
public int getPreferredSqlTypeCodeForBoolean() {
return fastSessionServices.preferredSqlTypeCodeForBoolean;
}
@Override @Override
public LobCreator getLobCreator() { public LobCreator getLobCreator() {
return Hibernate.getLobCreator( this ); return Hibernate.getLobCreator( this );

View File

@ -142,6 +142,7 @@ public final class FastSessionServices {
//Intentionally Package private: //Intentionally Package private:
final boolean disallowOutOfTransactionUpdateOperations; final boolean disallowOutOfTransactionUpdateOperations;
final boolean useStreamForLobBinding; final boolean useStreamForLobBinding;
final int preferredSqlTypeCodeForBoolean;
final boolean requiresMultiTenantConnectionProvider; final boolean requiresMultiTenantConnectionProvider;
final ConnectionProvider connectionProvider; final ConnectionProvider connectionProvider;
final MultiTenantConnectionProvider multiTenantConnectionProvider; final MultiTenantConnectionProvider multiTenantConnectionProvider;
@ -210,6 +211,7 @@ public final class FastSessionServices {
this.dialect = jdbcServices.getJdbcEnvironment().getDialect(); this.dialect = jdbcServices.getJdbcEnvironment().getDialect();
this.disallowOutOfTransactionUpdateOperations = !sessionFactoryOptions.isAllowOutOfTransactionUpdateOperations(); this.disallowOutOfTransactionUpdateOperations = !sessionFactoryOptions.isAllowOutOfTransactionUpdateOperations();
this.useStreamForLobBinding = Environment.useStreamsForBinary() || dialect.useInputStreamToInsertBlob(); this.useStreamForLobBinding = Environment.useStreamsForBinary() || dialect.useInputStreamToInsertBlob();
this.preferredSqlTypeCodeForBoolean = sessionFactoryOptions.getPreferredSqlTypeCodeForBoolean();
this.requiresMultiTenantConnectionProvider = sf.getSettings().getMultiTenancyStrategy().requiresMultiTenantConnectionProvider(); this.requiresMultiTenantConnectionProvider = sf.getSettings().getMultiTenancyStrategy().requiresMultiTenantConnectionProvider();
//Some "hot" services: //Some "hot" services:
@ -343,4 +345,8 @@ public final class FastSessionServices {
public void firePostLoadEvent(final PostLoadEvent postLoadEvent) { public void firePostLoadEvent(final PostLoadEvent postLoadEvent) {
eventListenerGroup_POST_LOAD.fireEventOnEachListener( postLoadEvent, PostLoadEventListener::onPostLoad ); eventListenerGroup_POST_LOAD.fireEventOnEachListener( postLoadEvent, PostLoadEventListener::onPostLoad );
} }
public int getPreferredSqlTypeCodeForBoolean() {
return preferredSqlTypeCodeForBoolean;
}
} }

View File

@ -49,7 +49,6 @@ import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.Loadable; import org.hibernate.persister.entity.Loadable;
import org.hibernate.query.ComparisonOperator; import org.hibernate.query.ComparisonOperator;
import org.hibernate.query.FetchClauseType;
import org.hibernate.query.Limit; import org.hibernate.query.Limit;
import org.hibernate.query.NullPrecedence; import org.hibernate.query.NullPrecedence;
import org.hibernate.query.SortOrder; import org.hibernate.query.SortOrder;
@ -69,7 +68,6 @@ import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.cte.CteColumn; import org.hibernate.sql.ast.tree.cte.CteColumn;
import org.hibernate.sql.ast.tree.cte.CteContainer; import org.hibernate.sql.ast.tree.cte.CteContainer;
import org.hibernate.sql.ast.tree.cte.CteSearchClauseKind;
import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.cte.SearchClauseSpecification; import org.hibernate.sql.ast.tree.cte.SearchClauseSpecification;
import org.hibernate.sql.ast.tree.delete.DeleteStatement; import org.hibernate.sql.ast.tree.delete.DeleteStatement;
@ -268,6 +266,11 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
return sessionFactory.getFastSessionServices().useStreamForLobBinding(); return sessionFactory.getFastSessionServices().useStreamForLobBinding();
} }
@Override
public int getPreferredSqlTypeCodeForBoolean() {
return sessionFactory.getFastSessionServices().getPreferredSqlTypeCodeForBoolean();
}
@Override @Override
public JdbcTypeDescriptor remapSqlTypeDescriptor(JdbcTypeDescriptor jdbcTypeDescriptor) { public JdbcTypeDescriptor remapSqlTypeDescriptor(JdbcTypeDescriptor jdbcTypeDescriptor) {
return sessionFactory.getFastSessionServices().remapSqlTypeDescriptor( jdbcTypeDescriptor ); return sessionFactory.getFastSessionServices().remapSqlTypeDescriptor( jdbcTypeDescriptor );

View File

@ -33,6 +33,11 @@ public interface WrapperOptions {
*/ */
boolean useStreamForLobBinding(); boolean useStreamForLobBinding();
/**
* Get the JDBC {@link java.sql.Types type code} used to bind a null boolean value
*/
int getPreferredSqlTypeCodeForBoolean();
/** /**
* Obtain access to the {@link LobCreator} * Obtain access to the {@link LobCreator}
* *

View File

@ -55,7 +55,7 @@ public abstract class BasicBinder<J> implements ValueBinder<J> {
) )
); );
} }
st.setNull( index, jdbcTypeDescriptor.getJdbcType() ); doBindNull( st, index, options );
} }
else { else {
if ( JdbcBindingLogging.TRACE_ENABLED ) { if ( JdbcBindingLogging.TRACE_ENABLED ) {
@ -84,7 +84,7 @@ public abstract class BasicBinder<J> implements ValueBinder<J> {
) )
); );
} }
st.setNull( name, jdbcTypeDescriptor.getJdbcType() ); doBindNull( st, name, options );
} }
else { else {
if ( JdbcBindingLogging.TRACE_ENABLED ) { if ( JdbcBindingLogging.TRACE_ENABLED ) {
@ -101,6 +101,32 @@ public abstract class BasicBinder<J> implements ValueBinder<J> {
} }
} }
/**
* Perform the null binding.
*
* @param st The prepared statement
* @param index The index at which to bind
* @param options The binding options
*
* @throws SQLException Indicates a problem binding to the prepared statement.
*/
protected void doBindNull(PreparedStatement st, int index, WrapperOptions options) throws SQLException {
st.setNull( index, jdbcTypeDescriptor.getJdbcType() );
}
/**
* Perform the null binding.
*
* @param st The CallableStatement
* @param name The name at which to bind
* @param options The binding options
*
* @throws SQLException Indicates a problem binding to the callable statement.
*/
protected void doBindNull(CallableStatement st, String name, WrapperOptions options) throws SQLException {
st.setNull( name, jdbcTypeDescriptor.getJdbcType() );
}
/** /**
* Perform the binding. Safe to assume that value is not null. * Perform the binding. Safe to assume that value is not null.
* *
@ -122,7 +148,7 @@ public abstract class BasicBinder<J> implements ValueBinder<J> {
* @param name The name at which to bind * @param name The name at which to bind
* @param options The binding options * @param options The binding options
* *
* @throws SQLException Indicates a problem binding to the prepared statement. * @throws SQLException Indicates a problem binding to the callable statement.
*/ */
protected abstract void doBind(CallableStatement st, J value, String name, WrapperOptions options) protected abstract void doBind(CallableStatement st, J value, String name, WrapperOptions options)
throws SQLException; throws SQLException;

View File

@ -63,6 +63,16 @@ public class BooleanTypeDescriptor implements JdbcTypeDescriptor {
public <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) { public <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicBinder<X>( javaTypeDescriptor, this ) { return new BasicBinder<X>( javaTypeDescriptor, this ) {
@Override
protected void doBindNull(PreparedStatement st, int index, WrapperOptions options) throws SQLException {
st.setNull( index, options.getPreferredSqlTypeCodeForBoolean() );
}
@Override
protected void doBindNull(CallableStatement st, String name, WrapperOptions options) throws SQLException {
st.setNull( name, options.getPreferredSqlTypeCodeForBoolean() );;
}
@Override @Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
st.setBoolean( index, javaTypeDescriptor.unwrap( value, Boolean.class, options ) ); st.setBoolean( index, javaTypeDescriptor.unwrap( value, Boolean.class, options ) );

View File

@ -12,6 +12,7 @@ import java.lang.reflect.Proxy;
import java.sql.CallableStatement; import java.sql.CallableStatement;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Types;
import java.util.TimeZone; import java.util.TimeZone;
import org.hibernate.engine.jdbc.LobCreator; import org.hibernate.engine.jdbc.LobCreator;
@ -98,6 +99,11 @@ public class MaterializedNClobBindTest {
return useStreamForLobBinding; return useStreamForLobBinding;
} }
@Override
public int getPreferredSqlTypeCodeForBoolean() {
return Types.BOOLEAN;
}
@Override @Override
public LobCreator getLobCreator() { public LobCreator getLobCreator() {
return NonContextualLobCreator.INSTANCE; return NonContextualLobCreator.INSTANCE;