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

View File

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

View File

@ -37,97 +37,99 @@ import org.hibernate.tuple.entity.EntityTuplizerFactory;
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public interface SessionFactoryOptionsState { 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(); PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode();
@ -137,15 +139,16 @@ public interface SessionFactoryOptionsState {
@Deprecated @Deprecated
ConnectionReleaseMode getConnectionReleaseMode(); 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(); return delegate.isNamedQueryStartupCheckingEnabled();
} }
@Override
public boolean isProcedureParameterNullPassingEnabled() {
return delegate.isProcedureParameterNullPassingEnabled();
}
@Override @Override
public boolean isSecondLevelCacheEnabled() { public boolean isSecondLevelCacheEnabled() {
return delegate.isSecondLevelCacheEnabled(); return delegate.isSecondLevelCacheEnabled();

View File

@ -42,11 +42,11 @@ public interface SessionFactoryOptions {
* *
* @return The service registry to use. * @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> * 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 * @return The SessionFactory name
*/ */
public String getSessionFactoryName(); String getSessionFactoryName();
/** /**
* Is the {@link #getSessionFactoryName SesssionFactory name} also a JNDI name, indicating we * 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. * @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. * 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} * @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(); PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode();
@ -161,22 +161,24 @@ public interface SessionFactoryOptions {
@Deprecated @Deprecated
ConnectionReleaseMode getConnectionReleaseMode(); ConnectionReleaseMode getConnectionReleaseMode();
public boolean isCommentsEnabled(); boolean isCommentsEnabled();
public CustomEntityDirtinessStrategy getCustomEntityDirtinessStrategy(); CustomEntityDirtinessStrategy getCustomEntityDirtinessStrategy();
public EntityNameResolver[] getEntityNameResolvers(); EntityNameResolver[] getEntityNameResolvers();
/** /**
* Get the delegate for handling entity-not-found exception conditions. * Get the delegate for handling entity-not-found exception conditions.
* *
* @return The specific EntityNotFoundDelegate to use, May be {@code null} * @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); 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"; 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 javax.persistence.StoredProcedureParameter;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.ResultSetMappingDefinition; import org.hibernate.engine.ResultSetMappingDefinition;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn; import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -48,10 +49,11 @@ public class NamedProcedureCallDefinition {
NamedProcedureCallDefinition(NamedStoredProcedureQuery annotation) { NamedProcedureCallDefinition(NamedStoredProcedureQuery annotation) {
this.registeredName = annotation.name(); this.registeredName = annotation.name();
this.procedureName = annotation.procedureName(); this.procedureName = annotation.procedureName();
this.hints = new QueryHintDefinition( annotation.hints() ).getHintsMap();
this.resultClasses = annotation.resultClasses(); this.resultClasses = annotation.resultClasses();
this.resultSetMappings = annotation.resultSetMappings(); 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 specifiesResultClasses = resultClasses != null && resultClasses.length > 0;
final boolean specifiesResultSetMappings = resultSetMappings != null && resultSetMappings.length > 0; final boolean specifiesResultSetMappings = resultSetMappings != null && resultSetMappings.length > 0;
@ -145,7 +147,7 @@ public class NamedProcedureCallDefinition {
private final ParameterStrategy parameterStrategy; private final ParameterStrategy parameterStrategy;
private final ParameterDefinition[] parameterDefinitions; private final ParameterDefinition[] parameterDefinitions;
ParameterDefinitions(StoredProcedureParameter[] parameters) { ParameterDefinitions(StoredProcedureParameter[] parameters, Map<String, Object> queryHintMap) {
if ( parameters == null || parameters.length == 0 ) { if ( parameters == null || parameters.length == 0 ) {
parameterStrategy = ParameterStrategy.POSITIONAL; parameterStrategy = ParameterStrategy.POSITIONAL;
parameterDefinitions = new ParameterDefinition[0]; parameterDefinitions = new ParameterDefinition[0];
@ -155,9 +157,15 @@ public class NamedProcedureCallDefinition {
? ParameterStrategy.NAMED ? ParameterStrategy.NAMED
: ParameterStrategy.POSITIONAL; : ParameterStrategy.POSITIONAL;
parameterDefinitions = new ParameterDefinition[ parameters.length ]; parameterDefinitions = new ParameterDefinition[ parameters.length ];
for ( int i = 0; i < parameters.length; i++ ) { 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] = ParameterDefinition.from(
parameterDefinitions[i] = new ParameterDefinition( i+1, parameters[i] ); 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 String name;
private final ParameterMode parameterMode; private final ParameterMode parameterMode;
private final Class type; 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.position = position;
this.name = normalize( annotation.name() ); this.name = normalize( annotation.name() );
this.parameterMode = annotation.mode(); this.parameterMode = annotation.mode();
this.type = annotation.type(); this.type = annotation.type();
this.explicitPassNullSetting = explicitPassNullSetting;
} }
@SuppressWarnings("UnnecessaryUnboxing")
public ParameterMemento toMemento(SessionFactoryImpl sessionFactory) { public ParameterMemento toMemento(SessionFactoryImpl sessionFactory) {
final boolean initialPassNullSetting = explicitPassNullSetting != null
? explicitPassNullSetting.booleanValue()
: sessionFactory.getSessionFactoryOptions().isProcedureParameterNullPassingEnabled();
return new ParameterMemento( return new ParameterMemento(
position, position,
name, name,
parameterMode, parameterMode,
type, 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; import org.hibernate.type.Type;
/** /**
* Describes a registered procedure/function parameter.
*
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public interface ParameterRegistration<T> { public interface ParameterRegistration<T> {
@ -21,7 +23,7 @@ public interface ParameterRegistration<T> {
* *
* @return The name; * @return The name;
*/ */
public String getName(); String getName();
/** /**
* The position at which this parameter was registered. Can be {@code null} which should indicate that * 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; * @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} * 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. * @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 * 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. * @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. * Set the Hibernate mapping type for this parameter.
* *
* @param type The Hibernate mapping type. * @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 * 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 * @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 * 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. * @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 * 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 value The value to bind
* @param explicitTemporalType An explicitly supplied TemporalType. * @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 final Class<T> type;
private ParameterBindImpl bind; private ParameterBindImpl bind;
private boolean passNulls;
private int startIndex; private int startIndex;
private Type hibernateType; private Type hibernateType;
@ -56,8 +57,9 @@ public abstract class AbstractParameterRegistrationImpl<T> implements ParameterR
ProcedureCallImpl procedureCall, ProcedureCallImpl procedureCall,
Integer position, Integer position,
ParameterMode mode, ParameterMode mode,
Class<T> type) { Class<T> type,
this( procedureCall, position, null, mode, type ); boolean initialPassNullsSetting) {
this( procedureCall, position, null, mode, type, initialPassNullsSetting );
} }
protected AbstractParameterRegistrationImpl( protected AbstractParameterRegistrationImpl(
@ -65,8 +67,9 @@ public abstract class AbstractParameterRegistrationImpl<T> implements ParameterR
Integer position, Integer position,
ParameterMode mode, ParameterMode mode,
Class<T> type, Class<T> type,
Type hibernateType) { Type hibernateType,
this( procedureCall, position, null, mode, 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, ProcedureCallImpl procedureCall,
String name, String name,
ParameterMode mode, ParameterMode mode,
Class<T> type) { Class<T> type,
this( procedureCall, null, name, mode, type ); boolean initialPassNullsSetting) {
this( procedureCall, null, name, mode, type, initialPassNullsSetting );
} }
protected AbstractParameterRegistrationImpl( protected AbstractParameterRegistrationImpl(
@ -85,8 +89,9 @@ public abstract class AbstractParameterRegistrationImpl<T> implements ParameterR
String name, String name,
ParameterMode mode, ParameterMode mode,
Class<T> type, Class<T> type,
Type hibernateType) { Type hibernateType,
this( procedureCall, null, name, mode, 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, String name,
ParameterMode mode, ParameterMode mode,
Class<T> type, Class<T> type,
Type hibernateType) { Type hibernateType,
boolean initialPassNullsSetting) {
this.procedureCall = procedureCall; this.procedureCall = procedureCall;
this.position = position; this.position = position;
@ -111,6 +117,7 @@ public abstract class AbstractParameterRegistrationImpl<T> implements ParameterR
return; return;
} }
this.passNulls = initialPassNullsSetting;
setHibernateType( hibernateType ); setHibernateType( hibernateType );
} }
@ -119,14 +126,16 @@ public abstract class AbstractParameterRegistrationImpl<T> implements ParameterR
Integer position, Integer position,
String name, String name,
ParameterMode mode, ParameterMode mode,
Class<T> type) { Class<T> type,
boolean initialPassNullsSetting) {
this( this(
procedureCall, procedureCall,
position, position,
name, name,
mode, mode,
type, 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; return mode;
} }
@Override
public boolean isPassNullsEnabled() {
return passNulls;
}
@Override
public void enablePassingNulls(boolean enabled) {
this.passNulls = enabled;
}
@Override @Override
public Type getHibernateType() { public Type getHibernateType() {
return hibernateType; return hibernateType;
@ -258,17 +277,28 @@ public abstract class AbstractParameterRegistrationImpl<T> implements ParameterR
if ( mode == ParameterMode.INOUT || mode == ParameterMode.IN ) { if ( mode == ParameterMode.INOUT || mode == ParameterMode.IN ) {
if ( bind == null || bind.getValue() == null ) { if ( bind == null || bind.getValue() == null ) {
// the user did not bind a value to the parameter being processed. That might be ok *if* the // the user did not bind a value to the parameter being processed. This is the condition
// procedure as defined in the database defines a default value for that parameter. // 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 // 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 // parameter defines a default value. Deferring to that information would be the best option
// assuming that the database will complain appropriately if not setting the given parameter if ( passNulls ) {
// bind value is an error. log.debugf(
log.debugf( "Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to true; binding NULL",
"Stored procedure [%s] IN/INOUT parameter [%s] not bound; assuming procedure defines default value", procedureCall.getProcedureName(),
procedureCall.getProcedureName(), this
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 { else {
typeToUse.nullSafeSet( statement, bind.getValue(), startIndex, session() ); typeToUse.nullSafeSet( statement, bind.getValue(), startIndex, session() );

View File

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

View File

@ -105,6 +105,7 @@ public class ProcedureCallMementoImpl implements ProcedureCallMemento {
private final ParameterMode mode; private final ParameterMode mode;
private final Class type; private final Class type;
private final Type hibernateType; private final Type hibernateType;
private final boolean passNulls;
/** /**
* Create the memento * Create the memento
@ -114,13 +115,21 @@ public class ProcedureCallMementoImpl implements ProcedureCallMemento {
* @param mode The parameter mode * @param mode The parameter mode
* @param type The Java type of the parameter * @param type The Java type of the parameter
* @param hibernateType The Hibernate Type. * @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.position = position;
this.name = name; this.name = name;
this.mode = mode; this.mode = mode;
this.type = type; this.type = type;
this.hibernateType = hibernateType; this.hibernateType = hibernateType;
this.passNulls = passNulls;
} }
public Integer getPosition() { public Integer getPosition() {
@ -143,6 +152,10 @@ public class ProcedureCallMementoImpl implements ProcedureCallMemento {
return hibernateType; return hibernateType;
} }
public boolean isPassNullsEnabled() {
return passNulls;
}
/** /**
* Build a ParameterMemento from the given parameter registration * Build a ParameterMemento from the given parameter registration
* *
@ -156,7 +169,8 @@ public class ProcedureCallMementoImpl implements ProcedureCallMemento {
registration.getName(), registration.getName(),
registration.getMode(), registration.getMode(),
registration.getType(), 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 * @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 * Access to the Hibernate type for this parameter registration
* *
* @return The Hibernate Type * @return The Hibernate Type
*/ */
public Type getHibernateType(); Type getHibernateType();
boolean isPassNullsEnabled();
/** /**
* Access to the SQL type(s) for this parameter * Access to the SQL type(s) for this parameter
* *
* @return The SQL types (JDBC type codes) * @return The SQL types (JDBC type codes)
*/ */
public int[] getSqlTypes(); int[] getSqlTypes();
/** /**
* Extract value from the statement after execution (used for OUT/INOUT parameters). * 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 * @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; package org.hibernate.test.sql.storedproc;
import java.util.List; 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.ParameterMode;
import javax.persistence.QueryHint;
import javax.persistence.StoredProcedureParameter;
import org.hibernate.JDBCException; import org.hibernate.JDBCException;
import org.hibernate.Session; import org.hibernate.Session;
@ -34,6 +40,11 @@ import static org.junit.Assert.fail;
*/ */
@RequiresDialect( H2Dialect.class ) @RequiresDialect( H2Dialect.class )
public class StoredProcedureTest extends BaseCoreFunctionalTestCase { public class StoredProcedureTest extends BaseCoreFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] { MyEntity.class };
}
@Override @Override
protected void configure(Configuration configuration) { protected void configure(Configuration configuration) {
super.configure( configuration ); super.configure( configuration );
@ -328,4 +339,109 @@ public class StoredProcedureTest extends BaseCoreFunctionalTestCase {
session.getTransaction().commit(); session.getTransaction().commit();
session.close(); 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;
}
} }