HHH-9548 - Allow propagation of NULL for stored-procedure argument parameters to database

This commit is contained in:
Steve Ebersole 2016-01-19 15:47:36 -06:00
parent 90b7f9e07c
commit a5e65834a1
15 changed files with 444 additions and 158 deletions

View File

@ -81,6 +81,7 @@ import static org.hibernate.cfg.AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLV
import static org.hibernate.cfg.AvailableSettings.ORDER_INSERTS;
import static org.hibernate.cfg.AvailableSettings.ORDER_UPDATES;
import static org.hibernate.cfg.AvailableSettings.PREFER_USER_TRANSACTION;
import static org.hibernate.cfg.AvailableSettings.PROCEDURE_NULL_PARAM_PASSING;
import static org.hibernate.cfg.AvailableSettings.QUERY_CACHE_FACTORY;
import static org.hibernate.cfg.AvailableSettings.QUERY_STARTUP_CHECKING;
import static org.hibernate.cfg.AvailableSettings.QUERY_SUBSTITUTIONS;
@ -525,6 +526,7 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
private Map querySubstitutions;
private boolean strictJpaQueryLanguageCompliance;
private boolean namedQueryStartupCheckingEnabled;
private final boolean procedureParameterNullPassingEnabled;
// Caching
private boolean secondLevelCacheEnabled;
@ -640,6 +642,7 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
this.querySubstitutions = ConfigurationHelper.toMap( QUERY_SUBSTITUTIONS, " ,=;:\n\t\r\f", configurationSettings );
this.strictJpaQueryLanguageCompliance = cfgService.getSetting( JPAQL_STRICT_COMPLIANCE, BOOLEAN, false );
this.namedQueryStartupCheckingEnabled = cfgService.getSetting( QUERY_STARTUP_CHECKING, BOOLEAN, true );
this.procedureParameterNullPassingEnabled = cfgService.getSetting( PROCEDURE_NULL_PARAM_PASSING, BOOLEAN, false );
this.secondLevelCacheEnabled = cfgService.getSetting( USE_SECOND_LEVEL_CACHE, BOOLEAN, true );
this.queryCacheEnabled = cfgService.getSetting( USE_QUERY_CACHE, BOOLEAN, false );
@ -889,6 +892,11 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
return namedQueryStartupCheckingEnabled;
}
@Override
public boolean isProcedureParameterNullPassingEnabled() {
return procedureParameterNullPassingEnabled;
}
@Override
public boolean isSecondLevelCacheEnabled() {
return secondLevelCacheEnabled;
@ -1160,6 +1168,11 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
return options.isNamedQueryStartupCheckingEnabled();
}
@Override
public boolean isProcedureParameterNullPassingEnabled() {
return options.isProcedureParameterNullPassingEnabled();
}
@Override
public boolean isSecondLevelCacheEnabled() {
return options.isSecondLevelCacheEnabled();

View File

@ -89,6 +89,7 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions {
private final Map querySubstitutions;
private final boolean strictJpaQueryLanguageCompliance;
private final boolean namedQueryStartupCheckingEnabled;
private final boolean procedureParameterNullPassingEnabled;
// Caching
private final boolean secondLevelCacheEnabled;
@ -159,6 +160,7 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions {
this.querySubstitutions = state.getQuerySubstitutions();
this.strictJpaQueryLanguageCompliance = state.isStrictJpaQueryLanguageCompliance();
this.namedQueryStartupCheckingEnabled = state.isNamedQueryStartupCheckingEnabled();
this.procedureParameterNullPassingEnabled = state.isProcedureParameterNullPassingEnabled();
this.secondLevelCacheEnabled = state.isSecondLevelCacheEnabled();
this.queryCacheEnabled = state.isQueryCacheEnabled();
@ -336,6 +338,11 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions {
return namedQueryStartupCheckingEnabled;
}
@Override
public boolean isProcedureParameterNullPassingEnabled() {
return procedureParameterNullPassingEnabled;
}
@Override
public boolean isSecondLevelCacheEnabled() {
return secondLevelCacheEnabled;

View File

@ -37,97 +37,99 @@ import org.hibernate.tuple.entity.EntityTuplizerFactory;
* @author Steve Ebersole
*/
public interface SessionFactoryOptionsState {
public StandardServiceRegistry getServiceRegistry();
StandardServiceRegistry getServiceRegistry();
public Object getBeanManagerReference();
Object getBeanManagerReference();
public Object getValidatorFactoryReference();
Object getValidatorFactoryReference();
public String getSessionFactoryName();
String getSessionFactoryName();
public boolean isSessionFactoryNameAlsoJndiName();
boolean isSessionFactoryNameAlsoJndiName();
public boolean isFlushBeforeCompletionEnabled();
boolean isFlushBeforeCompletionEnabled();
public boolean isAutoCloseSessionEnabled();
boolean isAutoCloseSessionEnabled();
public boolean isStatisticsEnabled();
boolean isStatisticsEnabled();
public Interceptor getInterceptor();
Interceptor getInterceptor();
public StatementInspector getStatementInspector();
StatementInspector getStatementInspector();
public SessionFactoryObserver[] getSessionFactoryObservers();
SessionFactoryObserver[] getSessionFactoryObservers();
public BaselineSessionEventsListenerBuilder getBaselineSessionEventsListenerBuilder();
BaselineSessionEventsListenerBuilder getBaselineSessionEventsListenerBuilder();
public boolean isIdentifierRollbackEnabled();
boolean isIdentifierRollbackEnabled();
public EntityMode getDefaultEntityMode();
EntityMode getDefaultEntityMode();
public EntityTuplizerFactory getEntityTuplizerFactory();
EntityTuplizerFactory getEntityTuplizerFactory();
public boolean isCheckNullability();
boolean isCheckNullability();
public boolean isInitializeLazyStateOutsideTransactionsEnabled();
boolean isInitializeLazyStateOutsideTransactionsEnabled();
public MultiTableBulkIdStrategy getMultiTableBulkIdStrategy();
MultiTableBulkIdStrategy getMultiTableBulkIdStrategy();
public TempTableDdlTransactionHandling getTempTableDdlTransactionHandling();
TempTableDdlTransactionHandling getTempTableDdlTransactionHandling();
public BatchFetchStyle getBatchFetchStyle();
BatchFetchStyle getBatchFetchStyle();
public int getDefaultBatchFetchSize();
int getDefaultBatchFetchSize();
public Integer getMaximumFetchDepth();
Integer getMaximumFetchDepth();
public NullPrecedence getDefaultNullPrecedence();
NullPrecedence getDefaultNullPrecedence();
public boolean isOrderUpdatesEnabled();
boolean isOrderUpdatesEnabled();
public boolean isOrderInsertsEnabled();
boolean isOrderInsertsEnabled();
public MultiTenancyStrategy getMultiTenancyStrategy();
MultiTenancyStrategy getMultiTenancyStrategy();
public CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver();
CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver();
public boolean isJtaTrackByThread();
boolean isJtaTrackByThread();
public Map getQuerySubstitutions();
Map getQuerySubstitutions();
public boolean isStrictJpaQueryLanguageCompliance();
boolean isStrictJpaQueryLanguageCompliance();
public boolean isNamedQueryStartupCheckingEnabled();
boolean isNamedQueryStartupCheckingEnabled();
public boolean isSecondLevelCacheEnabled();
boolean isProcedureParameterNullPassingEnabled();
public boolean isQueryCacheEnabled();
boolean isSecondLevelCacheEnabled();
public QueryCacheFactory getQueryCacheFactory();
boolean isQueryCacheEnabled();
public String getCacheRegionPrefix();
QueryCacheFactory getQueryCacheFactory();
public boolean isMinimalPutsEnabled();
String getCacheRegionPrefix();
public boolean isStructuredCacheEntriesEnabled();
boolean isMinimalPutsEnabled();
public boolean isDirectReferenceCacheEntriesEnabled();
boolean isStructuredCacheEntriesEnabled();
public boolean isAutoEvictCollectionCache();
boolean isDirectReferenceCacheEntriesEnabled();
public SchemaAutoTooling getSchemaAutoTooling();
boolean isAutoEvictCollectionCache();
public int getJdbcBatchSize();
SchemaAutoTooling getSchemaAutoTooling();
public boolean isJdbcBatchVersionedData();
int getJdbcBatchSize();
public boolean isScrollableResultSetsEnabled();
boolean isJdbcBatchVersionedData();
public boolean isWrapResultSetsEnabled();
boolean isScrollableResultSetsEnabled();
public boolean isGetGeneratedKeysEnabled();
boolean isWrapResultSetsEnabled();
public Integer getJdbcFetchSize();
boolean isGetGeneratedKeysEnabled();
Integer getJdbcFetchSize();
PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode();
@ -137,15 +139,16 @@ public interface SessionFactoryOptionsState {
@Deprecated
ConnectionReleaseMode getConnectionReleaseMode();
public boolean isCommentsEnabled();
boolean isCommentsEnabled();
public CustomEntityDirtinessStrategy getCustomEntityDirtinessStrategy();
CustomEntityDirtinessStrategy getCustomEntityDirtinessStrategy();
public EntityNameResolver[] getEntityNameResolvers();
EntityNameResolver[] getEntityNameResolvers();
public EntityNotFoundDelegate getEntityNotFoundDelegate();
EntityNotFoundDelegate getEntityNotFoundDelegate();
public Map<String, SQLFunction> getCustomSqlFunctionMap();
Map<String, SQLFunction> getCustomSqlFunctionMap();
boolean isPreferUserTransaction();
public boolean isPreferUserTransaction();
}

View File

@ -198,6 +198,11 @@ public abstract class AbstractDelegatingSessionFactoryOptions implements Session
return delegate.isNamedQueryStartupCheckingEnabled();
}
@Override
public boolean isProcedureParameterNullPassingEnabled() {
return delegate.isProcedureParameterNullPassingEnabled();
}
@Override
public boolean isSecondLevelCacheEnabled() {
return delegate.isSecondLevelCacheEnabled();

View File

@ -42,11 +42,11 @@ public interface SessionFactoryOptions {
*
* @return The service registry to use.
*/
public StandardServiceRegistry getServiceRegistry();
StandardServiceRegistry getServiceRegistry();
public Object getBeanManagerReference();
Object getBeanManagerReference();
public Object getValidatorFactoryReference();
Object getValidatorFactoryReference();
/**
* The name to be used for the SessionFactory. This is use both in:<ul>
@ -56,7 +56,7 @@ public interface SessionFactoryOptions {
*
* @return The SessionFactory name
*/
public String getSessionFactoryName();
String getSessionFactoryName();
/**
* Is the {@link #getSessionFactoryName SesssionFactory name} also a JNDI name, indicating we
@ -64,94 +64,94 @@ public interface SessionFactoryOptions {
*
* @return {@code true} if the SessionFactory name is also a JNDI name; {@code false} otherwise.
*/
public boolean isSessionFactoryNameAlsoJndiName();
boolean isSessionFactoryNameAlsoJndiName();
public boolean isFlushBeforeCompletionEnabled();
boolean isFlushBeforeCompletionEnabled();
public boolean isAutoCloseSessionEnabled();
boolean isAutoCloseSessionEnabled();
public boolean isStatisticsEnabled();
boolean isStatisticsEnabled();
/**
* Get the interceptor to use by default for all sessions opened from this factory.
*
* @return The interceptor to use factory wide. May be {@code null}
*/
public Interceptor getInterceptor();
Interceptor getInterceptor();
public StatementInspector getStatementInspector();
StatementInspector getStatementInspector();
public SessionFactoryObserver[] getSessionFactoryObservers();
SessionFactoryObserver[] getSessionFactoryObservers();
public BaselineSessionEventsListenerBuilder getBaselineSessionEventsListenerBuilder();
BaselineSessionEventsListenerBuilder getBaselineSessionEventsListenerBuilder();
public boolean isIdentifierRollbackEnabled();
boolean isIdentifierRollbackEnabled();
public EntityMode getDefaultEntityMode();
EntityMode getDefaultEntityMode();
public EntityTuplizerFactory getEntityTuplizerFactory();
EntityTuplizerFactory getEntityTuplizerFactory();
public boolean isCheckNullability();
boolean isCheckNullability();
public boolean isInitializeLazyStateOutsideTransactionsEnabled();
boolean isInitializeLazyStateOutsideTransactionsEnabled();
public MultiTableBulkIdStrategy getMultiTableBulkIdStrategy();
MultiTableBulkIdStrategy getMultiTableBulkIdStrategy();
public TempTableDdlTransactionHandling getTempTableDdlTransactionHandling();
TempTableDdlTransactionHandling getTempTableDdlTransactionHandling();
public BatchFetchStyle getBatchFetchStyle();
BatchFetchStyle getBatchFetchStyle();
public int getDefaultBatchFetchSize();
int getDefaultBatchFetchSize();
public Integer getMaximumFetchDepth();
Integer getMaximumFetchDepth();
public NullPrecedence getDefaultNullPrecedence();
NullPrecedence getDefaultNullPrecedence();
public boolean isOrderUpdatesEnabled();
boolean isOrderUpdatesEnabled();
public boolean isOrderInsertsEnabled();
boolean isOrderInsertsEnabled();
public MultiTenancyStrategy getMultiTenancyStrategy();
MultiTenancyStrategy getMultiTenancyStrategy();
public CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver();
CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver();
public boolean isJtaTrackByThread();
boolean isJtaTrackByThread();
public Map getQuerySubstitutions();
Map getQuerySubstitutions();
public boolean isStrictJpaQueryLanguageCompliance();
boolean isStrictJpaQueryLanguageCompliance();
public boolean isNamedQueryStartupCheckingEnabled();
boolean isNamedQueryStartupCheckingEnabled();
public boolean isSecondLevelCacheEnabled();
boolean isSecondLevelCacheEnabled();
public boolean isQueryCacheEnabled();
boolean isQueryCacheEnabled();
public QueryCacheFactory getQueryCacheFactory();
QueryCacheFactory getQueryCacheFactory();
public String getCacheRegionPrefix();
String getCacheRegionPrefix();
public boolean isMinimalPutsEnabled();
boolean isMinimalPutsEnabled();
public boolean isStructuredCacheEntriesEnabled();
boolean isStructuredCacheEntriesEnabled();
public boolean isDirectReferenceCacheEntriesEnabled();
boolean isDirectReferenceCacheEntriesEnabled();
public boolean isAutoEvictCollectionCache();
boolean isAutoEvictCollectionCache();
public SchemaAutoTooling getSchemaAutoTooling();
SchemaAutoTooling getSchemaAutoTooling();
public int getJdbcBatchSize();
int getJdbcBatchSize();
public boolean isJdbcBatchVersionedData();
boolean isJdbcBatchVersionedData();
public boolean isScrollableResultSetsEnabled();
boolean isScrollableResultSetsEnabled();
public boolean isWrapResultSetsEnabled();
boolean isWrapResultSetsEnabled();
public boolean isGetGeneratedKeysEnabled();
boolean isGetGeneratedKeysEnabled();
public Integer getJdbcFetchSize();
Integer getJdbcFetchSize();
PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode();
@ -161,22 +161,24 @@ public interface SessionFactoryOptions {
@Deprecated
ConnectionReleaseMode getConnectionReleaseMode();
public boolean isCommentsEnabled();
boolean isCommentsEnabled();
public CustomEntityDirtinessStrategy getCustomEntityDirtinessStrategy();
public EntityNameResolver[] getEntityNameResolvers();
CustomEntityDirtinessStrategy getCustomEntityDirtinessStrategy();
EntityNameResolver[] getEntityNameResolvers();
/**
* Get the delegate for handling entity-not-found exception conditions.
*
* @return The specific EntityNotFoundDelegate to use, May be {@code null}
*/
public EntityNotFoundDelegate getEntityNotFoundDelegate();
EntityNotFoundDelegate getEntityNotFoundDelegate();
public Map<String, SQLFunction> getCustomSqlFunctionMap();
Map<String, SQLFunction> getCustomSqlFunctionMap();
void setCheckNullability(boolean enabled);
public boolean isPreferUserTransaction();
boolean isPreferUserTransaction();
boolean isProcedureParameterNullPassingEnabled();
}

View File

@ -997,4 +997,16 @@ public interface AvailableSettings {
*/
String AUTO_SESSION_EVENTS_LISTENER = "hibernate.session.events.auto";
/**
* Global setting for whether NULL parameter bindings should be passed to database
* procedure/function calls as part of {@link org.hibernate.procedure.ProcedureCall}
* handling. Implicitly Hibernate will not pass the NULL, the intention being to allow
* any default argumnet values to be applied.
* <p/>
* This defines a global setting, which can them be controlled per parameter via
* {@link org.hibernate.procedure.ParameterRegistration#enablePassingNulls(boolean)}
* <p/>
* Values are {@code true} (pass the NULLs) or {@code false} (do not pass the NULLs).
*/
String PROCEDURE_NULL_PARAM_PASSING = "hibernate.proc.param_null_passing";
}

View File

@ -17,6 +17,7 @@ import javax.persistence.ParameterMode;
import javax.persistence.StoredProcedureParameter;
import org.hibernate.MappingException;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.ResultSetMappingDefinition;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn;
import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -48,10 +49,11 @@ public class NamedProcedureCallDefinition {
NamedProcedureCallDefinition(NamedStoredProcedureQuery annotation) {
this.registeredName = annotation.name();
this.procedureName = annotation.procedureName();
this.hints = new QueryHintDefinition( annotation.hints() ).getHintsMap();
this.resultClasses = annotation.resultClasses();
this.resultSetMappings = annotation.resultSetMappings();
this.parameterDefinitions = new ParameterDefinitions( annotation.parameters() );
this.hints = new QueryHintDefinition( annotation.hints() ).getHintsMap();
this.parameterDefinitions = new ParameterDefinitions( annotation.parameters(), hints );
final boolean specifiesResultClasses = resultClasses != null && resultClasses.length > 0;
final boolean specifiesResultSetMappings = resultSetMappings != null && resultSetMappings.length > 0;
@ -145,7 +147,7 @@ public class NamedProcedureCallDefinition {
private final ParameterStrategy parameterStrategy;
private final ParameterDefinition[] parameterDefinitions;
ParameterDefinitions(StoredProcedureParameter[] parameters) {
ParameterDefinitions(StoredProcedureParameter[] parameters, Map<String, Object> queryHintMap) {
if ( parameters == null || parameters.length == 0 ) {
parameterStrategy = ParameterStrategy.POSITIONAL;
parameterDefinitions = new ParameterDefinition[0];
@ -155,9 +157,15 @@ public class NamedProcedureCallDefinition {
? ParameterStrategy.NAMED
: ParameterStrategy.POSITIONAL;
parameterDefinitions = new ParameterDefinition[ parameters.length ];
for ( int i = 0; i < parameters.length; i++ ) {
// i+1 for the position because the apis say the numbers are 1-based, not zero
parameterDefinitions[i] = new ParameterDefinition( i+1, parameters[i] );
parameterDefinitions[i] = ParameterDefinition.from(
parameterStrategy,
parameters[i],
// i+1 for the position because the apis say the numbers are 1-based, not zero
i+1,
queryHintMap
);
}
}
}
@ -180,21 +188,62 @@ public class NamedProcedureCallDefinition {
private final String name;
private final ParameterMode parameterMode;
private final Class type;
private final Boolean explicitPassNullSetting;
ParameterDefinition(int position, StoredProcedureParameter annotation) {
static ParameterDefinition from(
ParameterStrategy parameterStrategy,
StoredProcedureParameter parameterAnnotation,
int adjustedPosition,
Map<String, Object> queryHintMap) {
// see if there was an explicit hint for this parameter in regards to NULL passing
final Object explicitNullPassingHint;
if ( parameterStrategy == ParameterStrategy.NAMED ) {
explicitNullPassingHint = queryHintMap.get( AvailableSettings.PROCEDURE_NULL_PARAM_PASSING + '.' + parameterAnnotation.name() );
}
else {
explicitNullPassingHint = queryHintMap.get( AvailableSettings.PROCEDURE_NULL_PARAM_PASSING + '.' + adjustedPosition );
}
return new ParameterDefinition(
adjustedPosition,
parameterAnnotation,
interpretBoolean( explicitNullPassingHint )
);
}
private static Boolean interpretBoolean(Object value) {
if ( value == null ) {
return null;
}
if ( value instanceof Boolean ) {
return (Boolean) value;
}
return Boolean.valueOf( value.toString() );
}
ParameterDefinition(int position, StoredProcedureParameter annotation, Boolean explicitPassNullSetting) {
this.position = position;
this.name = normalize( annotation.name() );
this.parameterMode = annotation.mode();
this.type = annotation.type();
this.explicitPassNullSetting = explicitPassNullSetting;
}
@SuppressWarnings("UnnecessaryUnboxing")
public ParameterMemento toMemento(SessionFactoryImpl sessionFactory) {
final boolean initialPassNullSetting = explicitPassNullSetting != null
? explicitPassNullSetting.booleanValue()
: sessionFactory.getSessionFactoryOptions().isProcedureParameterNullPassingEnabled();
return new ParameterMemento(
position,
name,
parameterMode,
type,
sessionFactory.getTypeResolver().heuristicType( type.getName() )
sessionFactory.getTypeResolver().heuristicType( type.getName() ),
initialPassNullSetting
);
}
}

View File

@ -12,6 +12,8 @@ import javax.persistence.TemporalType;
import org.hibernate.type.Type;
/**
* Describes a registered procedure/function parameter.
*
* @author Steve Ebersole
*/
public interface ParameterRegistration<T> {
@ -21,7 +23,7 @@ public interface ParameterRegistration<T> {
*
* @return The name;
*/
public String getName();
String getName();
/**
* The position at which this parameter was registered. Can be {@code null} which should indicate that
@ -29,7 +31,7 @@ public interface ParameterRegistration<T> {
*
* @return The name;
*/
public Integer getPosition();
Integer getPosition();
/**
* Obtain the Java type of parameter. This is used to guess the Hibernate type (unless {@link #setHibernateType}
@ -37,7 +39,7 @@ public interface ParameterRegistration<T> {
*
* @return The parameter Java type.
*/
public Class<T> getType();
Class<T> getType();
/**
* Retrieves the parameter "mode" which describes how the parameter is defined in the actual database procedure
@ -45,14 +47,32 @@ public interface ParameterRegistration<T> {
*
* @return The parameter mode.
*/
public ParameterMode getMode();
ParameterMode getMode();
/**
* Controls how unbound values for this IN/INOUT parameter registration will be handled prior to
* execution. There are 2 possible options to handle it:<ul>
* <li>bind the NULL to the parameter</li>
* <li>do not bind the NULL to the parameter</li>
* </ul>
* <p/>
* The reason for the distinction comes from default values defined on the corresponding
* database procedure/function argument. Any time a value (including NULL) is bound to the
* argument, its default value will not be used. So effectively this setting controls
* whether the NULL should be interpreted as "pass the NULL" or as "apply the argument default".
* <p/>
* The (global) default this setting is defined by {@link org.hibernate.cfg.AvailableSettings#PROCEDURE_NULL_PARAM_PASSING}
*
* @param enabled {@code true} indicates that the NULL should be passed; {@code false} indicates it should not.
*/
void enablePassingNulls(boolean enabled);
/**
* Set the Hibernate mapping type for this parameter.
*
* @param type The Hibernate mapping type.
*/
public void setHibernateType(Type type);
void setHibernateType(Type type);
/**
* Retrieve the binding associated with this parameter. The binding is only relevant for INPUT parameters. Can
@ -61,7 +81,7 @@ public interface ParameterRegistration<T> {
*
* @return The parameter binding
*/
public ParameterBind<T> getBind();
ParameterBind<T> getBind();
/**
* Bind a value to the parameter. How this value is bound to the underlying JDBC CallableStatement is
@ -69,7 +89,7 @@ public interface ParameterRegistration<T> {
*
* @param value The value to bind.
*/
public void bindValue(T value);
void bindValue(T value);
/**
* Bind a value to the parameter, using just a specified portion of the DATE/TIME value. It is illegal to call
@ -79,5 +99,5 @@ public interface ParameterRegistration<T> {
* @param value The value to bind
* @param explicitTemporalType An explicitly supplied TemporalType.
*/
public void bindValue(T value, TemporalType explicitTemporalType);
void bindValue(T value, TemporalType explicitTemporalType);
}

View File

@ -44,6 +44,7 @@ public abstract class AbstractParameterRegistrationImpl<T> implements ParameterR
private final Class<T> type;
private ParameterBindImpl bind;
private boolean passNulls;
private int startIndex;
private Type hibernateType;
@ -56,8 +57,9 @@ public abstract class AbstractParameterRegistrationImpl<T> implements ParameterR
ProcedureCallImpl procedureCall,
Integer position,
ParameterMode mode,
Class<T> type) {
this( procedureCall, position, null, mode, type );
Class<T> type,
boolean initialPassNullsSetting) {
this( procedureCall, position, null, mode, type, initialPassNullsSetting );
}
protected AbstractParameterRegistrationImpl(
@ -65,8 +67,9 @@ public abstract class AbstractParameterRegistrationImpl<T> implements ParameterR
Integer position,
ParameterMode mode,
Class<T> type,
Type hibernateType) {
this( procedureCall, position, null, mode, type, hibernateType );
Type hibernateType,
boolean initialPassNullsSetting) {
this( procedureCall, position, null, mode, type, hibernateType, initialPassNullsSetting );
}
@ -76,8 +79,9 @@ public abstract class AbstractParameterRegistrationImpl<T> implements ParameterR
ProcedureCallImpl procedureCall,
String name,
ParameterMode mode,
Class<T> type) {
this( procedureCall, null, name, mode, type );
Class<T> type,
boolean initialPassNullsSetting) {
this( procedureCall, null, name, mode, type, initialPassNullsSetting );
}
protected AbstractParameterRegistrationImpl(
@ -85,8 +89,9 @@ public abstract class AbstractParameterRegistrationImpl<T> implements ParameterR
String name,
ParameterMode mode,
Class<T> type,
Type hibernateType) {
this( procedureCall, null, name, mode, type, hibernateType );
Type hibernateType,
boolean initialPassNullsSetting) {
this( procedureCall, null, name, mode, type, hibernateType, initialPassNullsSetting );
}
@ -98,7 +103,8 @@ public abstract class AbstractParameterRegistrationImpl<T> implements ParameterR
String name,
ParameterMode mode,
Class<T> type,
Type hibernateType) {
Type hibernateType,
boolean initialPassNullsSetting) {
this.procedureCall = procedureCall;
this.position = position;
@ -111,6 +117,7 @@ public abstract class AbstractParameterRegistrationImpl<T> implements ParameterR
return;
}
this.passNulls = initialPassNullsSetting;
setHibernateType( hibernateType );
}
@ -119,14 +126,16 @@ public abstract class AbstractParameterRegistrationImpl<T> implements ParameterR
Integer position,
String name,
ParameterMode mode,
Class<T> type) {
Class<T> type,
boolean initialPassNullsSetting) {
this(
procedureCall,
position,
name,
mode,
type,
procedureCall.getSession().getFactory().getTypeResolver().heuristicType( type.getName() )
procedureCall.getSession().getFactory().getTypeResolver().heuristicType( type.getName() ),
initialPassNullsSetting
);
}
@ -154,6 +163,16 @@ public abstract class AbstractParameterRegistrationImpl<T> implements ParameterR
return mode;
}
@Override
public boolean isPassNullsEnabled() {
return passNulls;
}
@Override
public void enablePassingNulls(boolean enabled) {
this.passNulls = enabled;
}
@Override
public Type getHibernateType() {
return hibernateType;
@ -258,17 +277,28 @@ public abstract class AbstractParameterRegistrationImpl<T> implements ParameterR
if ( mode == ParameterMode.INOUT || mode == ParameterMode.IN ) {
if ( bind == null || bind.getValue() == null ) {
// the user did not bind a value to the parameter being processed. That might be ok *if* the
// procedure as defined in the database defines a default value for that parameter.
// the user did not bind a value to the parameter being processed. This is the condition
// defined by `passNulls` and that value controls what happens here. If `passNulls` is
// {@code true} we will bind the NULL value into the statement; if `passNulls` is
// {@code false} we will not.
//
// Unfortunately there is not a way to reliably know through JDBC metadata whether a procedure
// parameter defines a default value. So we simply allow the procedure execution to happen
// assuming that the database will complain appropriately if not setting the given parameter
// bind value is an error.
log.debugf(
"Stored procedure [%s] IN/INOUT parameter [%s] not bound; assuming procedure defines default value",
procedureCall.getProcedureName(),
this
);
// parameter defines a default value. Deferring to that information would be the best option
if ( passNulls ) {
log.debugf(
"Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to true; binding NULL",
procedureCall.getProcedureName(),
this
);
typeToUse.nullSafeSet( statement, null, startIndex, session() );
}
else {
log.debugf(
"Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to false; assuming procedure defines default value",
procedureCall.getProcedureName(),
this
);
}
}
else {
typeToUse.nullSafeSet( statement, bind.getValue(), startIndex, session() );

View File

@ -20,8 +20,9 @@ public class NamedParameterRegistration<T> extends AbstractParameterRegistration
ProcedureCallImpl procedureCall,
String name,
ParameterMode mode,
Class<T> type) {
super( procedureCall, name, mode, type );
Class<T> type,
boolean initialPassNullsSetting) {
super( procedureCall, name, mode, type, initialPassNullsSetting );
}
NamedParameterRegistration(
@ -29,7 +30,8 @@ public class NamedParameterRegistration<T> extends AbstractParameterRegistration
String name,
ParameterMode mode,
Class<T> type,
Type hibernateType) {
super( procedureCall, name, mode, type, hibernateType );
Type hibernateType,
boolean initialPassNullsSetting) {
super( procedureCall, name, mode, type, hibernateType, initialPassNullsSetting );
}
}

View File

@ -20,8 +20,9 @@ public class PositionalParameterRegistration<T> extends AbstractParameterRegistr
ProcedureCallImpl procedureCall,
Integer position,
ParameterMode mode,
Class<T> type) {
super( procedureCall, position, mode, type );
Class<T> type,
boolean initialPassNullsSetting) {
super( procedureCall, position, mode, type, initialPassNullsSetting );
}
PositionalParameterRegistration(
@ -29,7 +30,8 @@ public class PositionalParameterRegistration<T> extends AbstractParameterRegistr
Integer position,
ParameterMode mode,
Class<T> type,
Type hibernateType) {
super( procedureCall, position, mode, type, hibernateType );
Type hibernateType,
boolean initialPassNullsSetting) {
super( procedureCall, position, mode, type, hibernateType, initialPassNullsSetting );
}
}

View File

@ -61,6 +61,8 @@ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements
private final String procedureName;
private final NativeSQLQueryReturn[] queryReturns;
private final boolean globalParameterPassNullsSetting;
private ParameterStrategy parameterStrategy = ParameterStrategy.UNKNOWN;
private List<ParameterRegistrationImplementor<?>> registeredParameters = new ArrayList<ParameterRegistrationImplementor<?>>();
@ -77,6 +79,8 @@ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements
public ProcedureCallImpl(SessionImplementor session, String procedureName) {
super( session );
this.procedureName = procedureName;
this.globalParameterPassNullsSetting = session.getFactory().getSessionFactoryOptions().isProcedureParameterNullPassingEnabled();
this.queryReturns = NO_RETURNS;
}
@ -90,6 +94,7 @@ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements
public ProcedureCallImpl(final SessionImplementor session, String procedureName, Class... resultClasses) {
super( session );
this.procedureName = procedureName;
this.globalParameterPassNullsSetting = session.getFactory().getSessionFactoryOptions().isProcedureParameterNullPassingEnabled();
final List<NativeSQLQueryReturn> collectedQueryReturns = new ArrayList<NativeSQLQueryReturn>();
final Set<String> collectedQuerySpaces = new HashSet<String>();
@ -128,6 +133,7 @@ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements
public ProcedureCallImpl(final SessionImplementor session, String procedureName, String... resultSetMappings) {
super( session );
this.procedureName = procedureName;
this.globalParameterPassNullsSetting = session.getFactory().getSessionFactoryOptions().isProcedureParameterNullPassingEnabled();
final List<NativeSQLQueryReturn> collectedQueryReturns = new ArrayList<NativeSQLQueryReturn>();
final Set<String> collectedQuerySpaces = new HashSet<String>();
@ -171,6 +177,7 @@ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements
ProcedureCallImpl(SessionImplementor session, ProcedureCallMementoImpl memento) {
super( session );
this.procedureName = memento.getProcedureName();
this.globalParameterPassNullsSetting = session.getFactory().getSessionFactoryOptions().isProcedureParameterNullPassingEnabled();
this.queryReturns = memento.getQueryReturns();
this.synchronizedQuerySpaces = Util.copy( memento.getSynchronizedQuerySpaces() );
@ -207,7 +214,8 @@ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements
storedRegistration.getName(),
storedRegistration.getMode(),
storedRegistration.getType(),
storedRegistration.getHibernateType()
storedRegistration.getHibernateType(),
storedRegistration.isPassNullsEnabled()
);
}
else {
@ -221,7 +229,8 @@ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements
storedRegistration.getPosition(),
storedRegistration.getMode(),
storedRegistration.getType(),
storedRegistration.getHibernateType()
storedRegistration.getHibernateType(),
storedRegistration.isPassNullsEnabled()
);
}
parameterRegistrations.add( registration );
@ -257,7 +266,7 @@ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements
@SuppressWarnings("unchecked")
public <T> ParameterRegistration<T> registerParameter(int position, Class<T> type, ParameterMode mode) {
final PositionalParameterRegistration parameterRegistration =
new PositionalParameterRegistration( this, position, mode, type );
new PositionalParameterRegistration( this, position, mode, type, globalParameterPassNullsSetting );
registerParameter( parameterRegistration );
return parameterRegistration;
}
@ -326,7 +335,7 @@ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements
@Override
@SuppressWarnings("unchecked")
public <T> ParameterRegistration<T> registerParameter(String name, Class<T> type, ParameterMode mode) {
final NamedParameterRegistration parameterRegistration = new NamedParameterRegistration( this, name, mode, type );
final NamedParameterRegistration parameterRegistration = new NamedParameterRegistration( this, name, mode, type, globalParameterPassNullsSetting );
registerParameter( parameterRegistration );
return parameterRegistration;
}

View File

@ -105,6 +105,7 @@ public class ProcedureCallMementoImpl implements ProcedureCallMemento {
private final ParameterMode mode;
private final Class type;
private final Type hibernateType;
private final boolean passNulls;
/**
* Create the memento
@ -114,13 +115,21 @@ public class ProcedureCallMementoImpl implements ProcedureCallMemento {
* @param mode The parameter mode
* @param type The Java type of the parameter
* @param hibernateType The Hibernate Type.
* @param passNulls Should NULL values to passed to the database?
*/
public ParameterMemento(int position, String name, ParameterMode mode, Class type, Type hibernateType) {
public ParameterMemento(
int position,
String name,
ParameterMode mode,
Class type,
Type hibernateType,
boolean passNulls) {
this.position = position;
this.name = name;
this.mode = mode;
this.type = type;
this.hibernateType = hibernateType;
this.passNulls = passNulls;
}
public Integer getPosition() {
@ -143,6 +152,10 @@ public class ProcedureCallMementoImpl implements ProcedureCallMemento {
return hibernateType;
}
public boolean isPassNullsEnabled() {
return passNulls;
}
/**
* Build a ParameterMemento from the given parameter registration
*
@ -156,7 +169,8 @@ public class ProcedureCallMementoImpl implements ProcedureCallMemento {
registration.getName(),
registration.getMode(),
registration.getType(),
registration.getHibernateType()
registration.getHibernateType(),
registration.isPassNullsEnabled()
);
}

View File

@ -26,21 +26,23 @@ public interface ParameterRegistrationImplementor<T> extends ParameterRegistrati
*
* @throws SQLException Indicates a problem accessing the statement object
*/
public void prepare(CallableStatement statement, int i) throws SQLException;
void prepare(CallableStatement statement, int i) throws SQLException;
/**
* Access to the Hibernate type for this parameter registration
*
* @return The Hibernate Type
*/
public Type getHibernateType();
Type getHibernateType();
boolean isPassNullsEnabled();
/**
* Access to the SQL type(s) for this parameter
*
* @return The SQL types (JDBC type codes)
*/
public int[] getSqlTypes();
int[] getSqlTypes();
/**
* Extract value from the statement after execution (used for OUT/INOUT parameters).
@ -49,6 +51,6 @@ public interface ParameterRegistrationImplementor<T> extends ParameterRegistrati
*
* @return The extracted value
*/
public T extract(CallableStatement statement);
T extract(CallableStatement statement);
}

View File

@ -7,7 +7,13 @@
package org.hibernate.test.sql.storedproc;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedStoredProcedureQueries;
import javax.persistence.NamedStoredProcedureQuery;
import javax.persistence.ParameterMode;
import javax.persistence.QueryHint;
import javax.persistence.StoredProcedureParameter;
import org.hibernate.JDBCException;
import org.hibernate.Session;
@ -34,6 +40,11 @@ import static org.junit.Assert.fail;
*/
@RequiresDialect( H2Dialect.class )
public class StoredProcedureTest extends BaseCoreFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] { MyEntity.class };
}
@Override
protected void configure(Configuration configuration) {
super.configure( configuration );
@ -328,4 +339,109 @@ public class StoredProcedureTest extends BaseCoreFunctionalTestCase {
session.getTransaction().commit();
session.close();
}
@Test
public void testInParametersNotSetPass() {
Session session = openSession();
session.beginTransaction();
// unlike #testInParametersNotSet here we are asking that the NULL be passed
// so these executions should succeed
ProcedureCall query = session.createStoredProcedureCall( "findUserRange" );
query.registerParameter( 1, Integer.class, ParameterMode.IN ).enablePassingNulls( true );
query.registerParameter( 2, Integer.class, ParameterMode.IN ).bindValue( 2 );
query.getOutputs();
// H2 does not support named parameters
// {
// ProcedureCall query = session.createStoredProcedureCall( "findUserRange" );
// query.registerParameter( "start", Integer.class, ParameterMode.IN );
// query.registerParameter( "end", Integer.class, ParameterMode.IN ).bindValue( 2 );
// try {
// query.getOutputs();
// fail( "Expecting failure due to missing parameter bind" );
// }
// catch (JDBCException expected) {
// }
// }
session.getTransaction().commit();
session.close();
}
@Test
@SuppressWarnings("unchecked")
public void testInParametersNullnessPassingInNamedQueriesViaHints() {
Session session = openSession();
session.beginTransaction();
// similar to #testInParametersNotSet and #testInParametersNotSetPass in terms of testing
// support for specifying whether to pass NULL argument values or not. This version tests
// named procedure support via hints.
// first a fixture - this execution should fail
{
ProcedureCall query = session.getNamedProcedureCall( "findUserRangeNoNullPassing" );
query.getParameterRegistration( 2 ).bindValue( 2 );
try {
query.getOutputs();
fail( "Expecting failure due to missing parameter bind" );
}
catch (JDBCException ignore) {
}
}
// here we enable NULL passing via hint through a named parameter
{
ProcedureCall query = session.getNamedProcedureCall( "findUserRangeNamedNullPassing" );
query.getParameterRegistration( "secondArg" ).bindValue( 2 );
query.getOutputs();
}
// here we enable NULL passing via hint through a named parameter
{
ProcedureCall query = session.getNamedProcedureCall( "findUserRangeOrdinalNullPassing" );
query.getParameterRegistration( 2 ).bindValue( 2 );
query.getOutputs();
}
session.getTransaction().commit();
session.close();
}
@Entity
@NamedStoredProcedureQueries( {
@NamedStoredProcedureQuery(
name = "findUserRangeNoNullPassing",
procedureName = "findUserRange",
parameters = {
@StoredProcedureParameter( type = Integer.class ),
@StoredProcedureParameter( type = Integer.class ),
}
),
@NamedStoredProcedureQuery(
name = "findUserRangeNamedNullPassing",
procedureName = "findUserRange",
hints = @QueryHint( name = "hibernate.proc.param_null_passing.firstArg", value = "true" ),
parameters = {
@StoredProcedureParameter( name = "firstArg", type = Integer.class ),
@StoredProcedureParameter( name = "secondArg", type = Integer.class ),
}
),
@NamedStoredProcedureQuery(
name = "findUserRangeOrdinalNullPassing",
procedureName = "findUserRange",
hints = @QueryHint( name = "hibernate.proc.param_null_passing.1", value = "true" ),
parameters = {
@StoredProcedureParameter( type = Integer.class ),
@StoredProcedureParameter( type = Integer.class ),
}
)
} )
public static class MyEntity {
@Id
public Integer id;
}
}