HHH-7914 - Improve new stored procedure call support

This commit is contained in:
Steve Ebersole 2013-01-11 17:47:29 -06:00
parent 0713cea180
commit 5de1677ce7
29 changed files with 1746 additions and 946 deletions

View File

@ -25,6 +25,8 @@ package org.hibernate;
import java.io.Serializable; import java.io.Serializable;
import org.hibernate.procedure.Call;
/** /**
* Contract methods shared between {@link Session} and {@link StatelessSession} * Contract methods shared between {@link Session} and {@link StatelessSession}
* *
@ -91,17 +93,18 @@ public interface SharedSessionContract extends Serializable {
* *
* @return The representation of the procedure call. * @return The representation of the procedure call.
*/ */
public StoredProcedureCall createStoredProcedureCall(String procedureName); public Call createStoredProcedureCall(String procedureName);
/** /**
* Creates a call to a stored procedure with specific result set entity mappings * Creates a call to a stored procedure with specific result set entity mappings. Each class named
* is considered a "root return".
* *
* @param procedureName The name of the procedure. * @param procedureName The name of the procedure.
* @param resultClasses The entity(s) to map the result on to. * @param resultClasses The entity(s) to map the result on to.
* *
* @return The representation of the procedure call. * @return The representation of the procedure call.
*/ */
public StoredProcedureCall createStoredProcedureCall(String procedureName, Class... resultClasses); public Call createStoredProcedureCall(String procedureName, Class... resultClasses);
/** /**
* Creates a call to a stored procedure with specific result set entity mappings * Creates a call to a stored procedure with specific result set entity mappings
@ -111,7 +114,7 @@ public interface SharedSessionContract extends Serializable {
* *
* @return The representation of the procedure call. * @return The representation of the procedure call.
*/ */
public StoredProcedureCall createStoredProcedureCall(String procedureName, String... resultSetMappings); public Call createStoredProcedureCall(String procedureName, String... resultSetMappings);
/** /**
* Create {@link Criteria} instance for the given class (entity or subclasses/implementors) * Create {@link Criteria} instance for the given class (entity or subclasses/implementors)

View File

@ -1,198 +0,0 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate;
import javax.persistence.ParameterMode;
import javax.persistence.TemporalType;
import java.util.List;
import org.hibernate.internal.StoredProcedureCallImpl;
import org.hibernate.type.Type;
/**
* @author Steve Ebersole
*/
public interface StoredProcedureCall extends BasicQueryContract, SynchronizeableQuery {
@Override
StoredProcedureCall addSynchronizedQuerySpace(String querySpace);
@Override
StoredProcedureCall addSynchronizedEntityName(String entityName) throws MappingException;
@Override
StoredProcedureCall addSynchronizedEntityClass(Class entityClass) throws MappingException;
/**
* Get the name of the stored procedure to be called.
*
* @return The procedure name.
*/
public String getProcedureName();
/**
* Register a positional parameter.
* All positional parameters must be registered.
*
* @param position parameter position
* @param type type of the parameter
* @param mode parameter mode
*
* @return the same query instance
*/
StoredProcedureCall registerStoredProcedureParameter(
int position,
Class type,
ParameterMode mode);
/**
* Register a named parameter.
* When using parameter names, all parameters must be registered
* in the order in which they occur in the parameter list of the
* stored procedure.
*
* @param parameterName name of the parameter as registered or
* <p/>
* specified in metadata
* @param type type of the parameter
* @param mode parameter mode
*
* @return the same query instance
*/
StoredProcedureCall registerStoredProcedureParameter(
String parameterName,
Class type,
ParameterMode mode);
/**
* Retrieve all registered parameters.
*
* @return The (immutable) list of all registered parameters.
*/
public List<StoredProcedureParameter> getRegisteredParameters();
/**
* Retrieve parameter registered by name.
*
* @param name The name under which the parameter of interest was registered.
*
* @return The registered parameter.
*/
public StoredProcedureParameter getRegisteredParameter(String name);
public StoredProcedureParameter getRegisteredParameter(int position);
public StoredProcedureOutputs getOutputs();
/**
* Describes a parameter registered with the stored procedure. Parameters can be either named or positional
* as the registration mechanism. Named and positional should not be mixed.
*/
public static interface StoredProcedureParameter<T> {
/**
* The name under which this parameter was registered. Can be {@code null} which should indicate that
* positional registration was used (and therefore {@link #getPosition()} should return non-null.
*
* @return The name;
*/
public String getName();
/**
* The position at which this parameter was registered. Can be {@code null} which should indicate that
* named registration was used (and therefore {@link #getName()} should return non-null.
*
* @return The name;
*/
public Integer getPosition();
/**
* Obtain the Java type of parameter. This is used to guess the Hibernate type (unless {@link #setHibernateType}
* is called explicitly).
*
* @return The parameter Java type.
*/
public Class<T> getType();
/**
* Retrieves the parameter "mode" which describes how the parameter is defined in the actual database procedure
* definition (is it an INPUT parameter? An OUTPUT parameter? etc).
*
* @return The parameter mode.
*/
public ParameterMode getMode();
/**
* Set the Hibernate mapping type for this parameter.
*
* @param type The Hibernate mapping type.
*/
public void setHibernateType(Type type);
/**
* Retrieve the binding associated with this parameter. The binding is only relevant for INPUT parameters. Can
* return {@code null} if nothing has been bound yet. To bind a value to the parameter use one of the
* {@link #bindValue} methods.
*
* @return The parameter binding
*/
public StoredProcedureParameterBind getParameterBind();
/**
* Bind a value to the parameter. How this value is bound to the underlying JDBC CallableStatement is
* totally dependent on the Hibernate type.
*
* @param value The value to bind.
*/
public 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
* this form if the parameter is not DATE/TIME type. The Hibernate type is circumvented in this case and
* an appropriate "precision" Type is used instead.
*
* @param value The value to bind
* @param explicitTemporalType An explicitly supplied TemporalType.
*/
public void bindValue(T value, TemporalType explicitTemporalType);
}
/**
* Describes an input value binding for any IN/INOUT parameters.
*/
public static interface StoredProcedureParameterBind<T> {
/**
* Retrieves the bound value.
*
* @return The bound value.
*/
public T getValue();
/**
* If {@code <T>} represents a DATE/TIME type value, JPA usually allows specifying the particular parts of
* the DATE/TIME value to be bound. This value represents the particular part the user requested to be bound.
*
* @return The explicitly supplied TemporalType.
*/
public TemporalType getExplicitTemporalType();
}
}

View File

@ -37,7 +37,7 @@ import org.hibernate.SQLQuery;
import org.hibernate.ScrollableResults; import org.hibernate.ScrollableResults;
import org.hibernate.SessionException; import org.hibernate.SessionException;
import org.hibernate.SharedSessionContract; import org.hibernate.SharedSessionContract;
import org.hibernate.StoredProcedureCall; import org.hibernate.procedure.Call;
import org.hibernate.cache.spi.CacheKey; import org.hibernate.cache.spi.CacheKey;
import org.hibernate.engine.jdbc.LobCreationContext; import org.hibernate.engine.jdbc.LobCreationContext;
import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess; import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess;
@ -59,6 +59,7 @@ import org.hibernate.jdbc.WorkExecutorVisitable;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.hibernate.procedure.internal.CallImpl;
import org.hibernate.type.Type; import org.hibernate.type.Type;
/** /**
@ -240,27 +241,27 @@ public abstract class AbstractSessionImpl implements Serializable, SharedSession
@Override @Override
@SuppressWarnings("UnnecessaryLocalVariable") @SuppressWarnings("UnnecessaryLocalVariable")
public StoredProcedureCall createStoredProcedureCall(String procedureName) { public Call createStoredProcedureCall(String procedureName) {
errorIfClosed(); errorIfClosed();
final StoredProcedureCall call = new StoredProcedureCallImpl( this, procedureName ); final Call call = new CallImpl( this, procedureName );
// call.setComment( "Dynamic stored procedure call" ); // call.setComment( "Dynamic stored procedure call" );
return call; return call;
} }
@Override @Override
@SuppressWarnings("UnnecessaryLocalVariable") @SuppressWarnings("UnnecessaryLocalVariable")
public StoredProcedureCall createStoredProcedureCall(String procedureName, Class... resultClasses) { public Call createStoredProcedureCall(String procedureName, Class... resultClasses) {
errorIfClosed(); errorIfClosed();
final StoredProcedureCall call = new StoredProcedureCallImpl( this, procedureName, resultClasses ); final Call call = new CallImpl( this, procedureName, resultClasses );
// call.setComment( "Dynamic stored procedure call" ); // call.setComment( "Dynamic stored procedure call" );
return call; return call;
} }
@Override @Override
@SuppressWarnings("UnnecessaryLocalVariable") @SuppressWarnings("UnnecessaryLocalVariable")
public StoredProcedureCall createStoredProcedureCall(String procedureName, String... resultSetMappings) { public Call createStoredProcedureCall(String procedureName, String... resultSetMappings) {
errorIfClosed(); errorIfClosed();
final StoredProcedureCall call = new StoredProcedureCallImpl( this, procedureName, resultSetMappings ); final Call call = new CallImpl( this, procedureName, resultSetMappings );
// call.setComment( "Dynamic stored procedure call" ); // call.setComment( "Dynamic stored procedure call" );
return call; return call;
} }

View File

@ -76,7 +76,7 @@ import org.hibernate.ScrollableResults;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.SessionBuilder; import org.hibernate.SessionBuilder;
import org.hibernate.SessionException; import org.hibernate.SessionException;
import org.hibernate.StoredProcedureCall; import org.hibernate.procedure.Call;
import org.hibernate.engine.spi.SessionOwner; import org.hibernate.engine.spi.SessionOwner;
import org.hibernate.SharedSessionBuilder; import org.hibernate.SharedSessionBuilder;
import org.hibernate.SimpleNaturalIdLoadAccess; import org.hibernate.SimpleNaturalIdLoadAccess;
@ -141,7 +141,6 @@ import org.hibernate.event.spi.ResolveNaturalIdEventListener;
import org.hibernate.event.spi.SaveOrUpdateEvent; import org.hibernate.event.spi.SaveOrUpdateEvent;
import org.hibernate.event.spi.SaveOrUpdateEventListener; import org.hibernate.event.spi.SaveOrUpdateEventListener;
import org.hibernate.internal.CriteriaImpl.CriterionEntry; import org.hibernate.internal.CriteriaImpl.CriterionEntry;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.jdbc.ReturningWork; import org.hibernate.jdbc.ReturningWork;
import org.hibernate.jdbc.Work; import org.hibernate.jdbc.Work;
import org.hibernate.jdbc.WorkExecutor; import org.hibernate.jdbc.WorkExecutor;
@ -1743,21 +1742,21 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
} }
@Override @Override
public StoredProcedureCall createStoredProcedureCall(String procedureName) { public Call createStoredProcedureCall(String procedureName) {
errorIfClosed(); errorIfClosed();
checkTransactionSynchStatus(); checkTransactionSynchStatus();
return super.createStoredProcedureCall( procedureName ); return super.createStoredProcedureCall( procedureName );
} }
@Override @Override
public StoredProcedureCall createStoredProcedureCall(String procedureName, String... resultSetMappings) { public Call createStoredProcedureCall(String procedureName, String... resultSetMappings) {
errorIfClosed(); errorIfClosed();
checkTransactionSynchStatus(); checkTransactionSynchStatus();
return super.createStoredProcedureCall( procedureName, resultSetMappings ); return super.createStoredProcedureCall( procedureName, resultSetMappings );
} }
@Override @Override
public StoredProcedureCall createStoredProcedureCall(String procedureName, Class... resultClasses) { public Call createStoredProcedureCall(String procedureName, Class... resultClasses) {
errorIfClosed(); errorIfClosed();
checkTransactionSynchStatus(); checkTransactionSynchStatus();
return super.createStoredProcedureCall( procedureName, resultClasses ); return super.createStoredProcedureCall( procedureName, resultClasses );

View File

@ -1,608 +0,0 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.internal;
import javax.persistence.ParameterMode;
import javax.persistence.TemporalType;
import java.sql.CallableStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.jboss.logging.Logger;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.MappingException;
import org.hibernate.QueryException;
import org.hibernate.StoredProcedureCall;
import org.hibernate.StoredProcedureOutputs;
import org.hibernate.cfg.NotYetImplementedException;
import org.hibernate.engine.ResultSetMappingDefinition;
import org.hibernate.engine.jdbc.spi.ExtractedDatabaseMetaData;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryRootReturn;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport;
import org.hibernate.type.DateType;
import org.hibernate.type.ProcedureParameterExtractionAware;
import org.hibernate.type.Type;
/**
* @author Steve Ebersole
*/
public class StoredProcedureCallImpl extends AbstractBasicQueryContractImpl implements StoredProcedureCall {
private static final Logger log = Logger.getLogger( StoredProcedureCallImpl.class );
private final String procedureName;
private final NativeSQLQueryReturn[] queryReturns;
private TypeOfParameter typeOfParameters = TypeOfParameter.UNKNOWN;
private List<StoredProcedureParameterImplementor> registeredParameters = new ArrayList<StoredProcedureParameterImplementor>();
private Set<String> synchronizedQuerySpaces;
@SuppressWarnings("unchecked")
public StoredProcedureCallImpl(SessionImplementor session, String procedureName) {
this( session, procedureName, (List) null );
}
public StoredProcedureCallImpl(SessionImplementor session, String procedureName, List<NativeSQLQueryReturn> queryReturns) {
super( session );
this.procedureName = procedureName;
if ( queryReturns == null || queryReturns.isEmpty() ) {
this.queryReturns = new NativeSQLQueryReturn[0];
}
else {
this.queryReturns = queryReturns.toArray( new NativeSQLQueryReturn[ queryReturns.size() ] );
}
}
public StoredProcedureCallImpl(SessionImplementor session, String procedureName, Class... resultClasses) {
this( session, procedureName, collectQueryReturns( resultClasses ) );
}
private static List<NativeSQLQueryReturn> collectQueryReturns(Class[] resultClasses) {
if ( resultClasses == null || resultClasses.length == 0 ) {
return null;
}
List<NativeSQLQueryReturn> queryReturns = new ArrayList<NativeSQLQueryReturn>( resultClasses.length );
int i = 1;
for ( Class resultClass : resultClasses ) {
queryReturns.add( new NativeSQLQueryRootReturn( "alias" + i, resultClass.getName(), LockMode.READ ) );
i++;
}
return queryReturns;
}
public StoredProcedureCallImpl(SessionImplementor session, String procedureName, String... resultSetMappings) {
this( session, procedureName, collectQueryReturns( session, resultSetMappings ) );
}
private static List<NativeSQLQueryReturn> collectQueryReturns(SessionImplementor session, String[] resultSetMappings) {
if ( resultSetMappings == null || resultSetMappings.length == 0 ) {
return null;
}
List<NativeSQLQueryReturn> queryReturns = new ArrayList<NativeSQLQueryReturn>( resultSetMappings.length );
for ( String resultSetMapping : resultSetMappings ) {
ResultSetMappingDefinition mapping = session.getFactory().getResultSetMapping( resultSetMapping );
if ( mapping == null ) {
throw new MappingException( "Unknown SqlResultSetMapping [" + resultSetMapping + "]" );
}
queryReturns.addAll( Arrays.asList( mapping.getQueryReturns() ) );
}
return queryReturns;
}
// public StoredProcedureCallImpl(
// SessionImplementor session,
// String procedureName,
// List<StoredProcedureParameter> parameters) {
// // this form is intended for named stored procedure calls.
// // todo : introduce a NamedProcedureCallDefinition object to hold all needed info and pass that in here; will help with EM.addNamedQuery as well..
// this( session, procedureName );
// for ( StoredProcedureParameter parameter : parameters ) {
// registerParameter( (StoredProcedureParameterImplementor) parameter );
// }
// }
@Override
public String getProcedureName() {
return procedureName;
}
NativeSQLQueryReturn[] getQueryReturns() {
return queryReturns;
}
@Override
@SuppressWarnings("unchecked")
public StoredProcedureCall registerStoredProcedureParameter(int position, Class type, ParameterMode mode) {
registerParameter( new PositionalStoredProcedureParameter( this, position, mode, type ) );
return this;
}
private void registerParameter(StoredProcedureParameterImplementor parameter) {
if ( StringHelper.isNotEmpty( parameter.getName() ) ) {
prepareForNamedParameters();
}
else if ( parameter.getPosition() != null ) {
prepareForPositionalParameters();
}
else {
throw new IllegalArgumentException( "Given parameter did not define name nor position [" + parameter + "]" );
}
registeredParameters.add( parameter );
}
private void prepareForPositionalParameters() {
if ( typeOfParameters == TypeOfParameter.NAMED ) {
throw new QueryException( "Cannot mix named and positional parameters" );
}
typeOfParameters = TypeOfParameter.POSITIONAL;
}
private void prepareForNamedParameters() {
if ( typeOfParameters == TypeOfParameter.POSITIONAL ) {
throw new QueryException( "Cannot mix named and positional parameters" );
}
if ( typeOfParameters == null ) {
// protect to only do this check once
final ExtractedDatabaseMetaData databaseMetaData = session().getTransactionCoordinator()
.getJdbcCoordinator()
.getLogicalConnection()
.getJdbcServices()
.getExtractedMetaDataSupport();
if ( ! databaseMetaData.supportsNamedParameters() ) {
throw new QueryException(
"Named stored procedure parameters used, but JDBC driver does not support named parameters"
);
}
typeOfParameters = TypeOfParameter.NAMED;
}
}
@Override
@SuppressWarnings("unchecked")
public StoredProcedureCall registerStoredProcedureParameter(String name, Class type, ParameterMode mode) {
registerParameter( new NamedStoredProcedureParameter( this, name, mode, type ) );
return this;
}
@Override
@SuppressWarnings("unchecked")
public List<StoredProcedureParameter> getRegisteredParameters() {
return new ArrayList<StoredProcedureParameter>( registeredParameters );
}
@Override
public StoredProcedureParameterImplementor getRegisteredParameter(String name) {
if ( typeOfParameters != TypeOfParameter.NAMED ) {
throw new IllegalArgumentException( "Names were not used to register parameters with this stored procedure call" );
}
for ( StoredProcedureParameterImplementor parameter : registeredParameters ) {
if ( name.equals( parameter.getName() ) ) {
return parameter;
}
}
throw new IllegalArgumentException( "Could not locate parameter registered under that name [" + name + "]" );
}
@Override
public StoredProcedureParameterImplementor getRegisteredParameter(int position) {
try {
return registeredParameters.get( position );
}
catch ( Exception e ) {
throw new QueryException( "Could not locate parameter registered using that position [" + position + "]" );
}
}
@Override
public StoredProcedureOutputs getOutputs() {
// todo : going to need a very specialized Loader for this.
// or, might be a good time to look at splitting Loader up into:
// 1) building statement objects
// 2) executing statement objects
// 3) processing result sets
// for now assume there are no resultClasses nor mappings defined..
// TOTAL PROOF-OF-CONCEPT!!!!!!
final StringBuilder buffer = new StringBuilder().append( "{call " )
.append( procedureName )
.append( "(" );
String sep = "";
for ( StoredProcedureParameterImplementor parameter : registeredParameters ) {
for ( int i = 0; i < parameter.getSqlTypes().length; i++ ) {
buffer.append( sep ).append( "?" );
sep = ",";
}
}
buffer.append( ")}" );
try {
final CallableStatement statement = session().getTransactionCoordinator()
.getJdbcCoordinator()
.getLogicalConnection()
.getShareableConnectionProxy()
.prepareCall( buffer.toString() );
// prepare parameters
int i = 1;
for ( StoredProcedureParameterImplementor parameter : registeredParameters ) {
if ( parameter == null ) {
throw new QueryException( "Registered stored procedure parameters had gaps" );
}
parameter.prepare( statement, i );
i += parameter.getSqlTypes().length;
}
return new StoredProcedureOutputsImpl( this, statement );
}
catch (SQLException e) {
throw session().getFactory().getSQLExceptionHelper().convert(
e,
"Error preparing CallableStatement",
getProcedureName()
);
}
}
@Override
public Type[] getReturnTypes() throws HibernateException {
throw new NotYetImplementedException();
}
protected Set<String> synchronizedQuerySpaces() {
if ( synchronizedQuerySpaces == null ) {
synchronizedQuerySpaces = new HashSet<String>();
}
return synchronizedQuerySpaces;
}
@Override
@SuppressWarnings("unchecked")
public Collection<String> getSynchronizedQuerySpaces() {
if ( synchronizedQuerySpaces == null ) {
return Collections.emptySet();
}
else {
return Collections.unmodifiableSet( synchronizedQuerySpaces );
}
}
public Set<String> getSynchronizedQuerySpacesSet() {
return (Set<String>) getSynchronizedQuerySpaces();
}
@Override
public StoredProcedureCallImpl addSynchronizedQuerySpace(String querySpace) {
synchronizedQuerySpaces().add( querySpace );
return this;
}
@Override
public StoredProcedureCallImpl addSynchronizedEntityName(String entityName) {
addSynchronizedQuerySpaces( session().getFactory().getEntityPersister( entityName ) );
return this;
}
protected void addSynchronizedQuerySpaces(EntityPersister persister) {
synchronizedQuerySpaces().addAll( Arrays.asList( (String[]) persister.getQuerySpaces() ) );
}
@Override
public StoredProcedureCallImpl addSynchronizedEntityClass(Class entityClass) {
addSynchronizedQuerySpaces( session().getFactory().getEntityPersister( entityClass.getName() ) );
return this;
}
public QueryParameters buildQueryParametersObject() {
QueryParameters qp = super.buildQueryParametersObject();
// both of these are for documentation purposes, they are actually handled directly...
qp.setAutoDiscoverScalarTypes( true );
qp.setCallable( true );
return qp;
}
public StoredProcedureParameterImplementor[] collectRefCursorParameters() {
List<StoredProcedureParameterImplementor> refCursorParams = new ArrayList<StoredProcedureParameterImplementor>();
for ( StoredProcedureParameterImplementor param : registeredParameters ) {
if ( param.getMode() == ParameterMode.REF_CURSOR ) {
refCursorParams.add( param );
}
}
return refCursorParams.toArray( new StoredProcedureParameterImplementor[refCursorParams.size()] );
}
/**
* Ternary logic enum
*/
private static enum TypeOfParameter {
NAMED,
POSITIONAL,
UNKNOWN
}
protected static interface StoredProcedureParameterImplementor<T> extends StoredProcedureParameter<T> {
public void prepare(CallableStatement statement, int i) throws SQLException;
public int[] getSqlTypes();
public T extract(CallableStatement statement);
}
public static abstract class AbstractStoredProcedureParameterImpl<T> implements StoredProcedureParameterImplementor<T> {
private final StoredProcedureCallImpl procedureCall;
private final ParameterMode mode;
private final Class<T> type;
private int startIndex;
private Type hibernateType;
private int[] sqlTypes;
private StoredProcedureParameterBindImpl bind;
protected AbstractStoredProcedureParameterImpl(
StoredProcedureCallImpl procedureCall,
ParameterMode mode,
Class<T> type) {
this.procedureCall = procedureCall;
this.mode = mode;
this.type = type;
setHibernateType( session().getFactory().getTypeResolver().heuristicType( type.getName() ) );
}
@Override
public String getName() {
return null;
}
@Override
public Integer getPosition() {
return null;
}
@Override
public Class<T> getType() {
return type;
}
@Override
public ParameterMode getMode() {
return mode;
}
@Override
public void setHibernateType(Type type) {
if ( type == null ) {
throw new IllegalArgumentException( "Type cannot be null" );
}
this.hibernateType = type;
this.sqlTypes = hibernateType.sqlTypes( session().getFactory() );
}
protected SessionImplementor session() {
return procedureCall.session();
}
@Override
public void prepare(CallableStatement statement, int startIndex) throws SQLException {
if ( mode == ParameterMode.REF_CURSOR ) {
throw new NotYetImplementedException( "Support for REF_CURSOR parameters not yet supported" );
}
this.startIndex = startIndex;
if ( mode == ParameterMode.IN || mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) {
if ( mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) {
if ( sqlTypes.length > 1 ) {
if ( ProcedureParameterExtractionAware.class.isInstance( hibernateType )
&& ( (ProcedureParameterExtractionAware) hibernateType ).canDoExtraction() ) {
// the type can handle multi-param extraction...
}
else {
// it cannot...
throw new UnsupportedOperationException(
"Type [" + hibernateType + "] does support multi-parameter value extraction"
);
}
}
for ( int i = 0; i < sqlTypes.length; i++ ) {
statement.registerOutParameter( startIndex + i, sqlTypes[i] );
}
}
if ( mode == ParameterMode.INOUT || mode == ParameterMode.IN ) {
if ( bind == null || bind.getValue() == null ) {
log.debugf(
"Stored procedure [%s] IN/INOUT parameter [%s] not bound; assuming procedure defines default value",
procedureCall.getProcedureName(),
this
);
}
else {
final Type typeToUse;
if ( bind.getExplicitTemporalType() != null && bind.getExplicitTemporalType() == TemporalType.TIMESTAMP ) {
typeToUse = hibernateType;
}
else if ( bind.getExplicitTemporalType() != null && bind.getExplicitTemporalType() == TemporalType.DATE ) {
typeToUse = DateType.INSTANCE;
}
else {
typeToUse = hibernateType;
}
typeToUse.nullSafeSet( statement, bind.getValue(), startIndex, session() );
}
}
}
else {
// we have a REF_CURSOR type param
if ( procedureCall.typeOfParameters == TypeOfParameter.NAMED ) {
session().getFactory().getServiceRegistry()
.getService( RefCursorSupport.class )
.registerRefCursorParameter( statement, getName() );
}
else {
session().getFactory().getServiceRegistry()
.getService( RefCursorSupport.class )
.registerRefCursorParameter( statement, getPosition() );
}
}
}
public int[] getSqlTypes() {
return sqlTypes;
}
@Override
public StoredProcedureParameterBind getParameterBind() {
return bind;
}
@Override
public void bindValue(T value) {
this.bind = new StoredProcedureParameterBindImpl<T>( value );
}
@Override
public void bindValue(T value, TemporalType explicitTemporalType) {
if ( explicitTemporalType != null ) {
if ( ! isDateTimeType() ) {
throw new IllegalArgumentException( "TemporalType should not be specified for non date/time type" );
}
}
this.bind = new StoredProcedureParameterBindImpl<T>( value, explicitTemporalType );
}
private boolean isDateTimeType() {
return Date.class.isAssignableFrom( type )
|| Calendar.class.isAssignableFrom( type );
}
@Override
@SuppressWarnings("unchecked")
public T extract(CallableStatement statement) {
if ( mode == ParameterMode.IN ) {
throw new QueryException( "IN parameter not valid for output extraction" );
}
else if ( mode == ParameterMode.REF_CURSOR ) {
throw new QueryException( "REF_CURSOR parameters should be accessed via results" );
}
try {
if ( ProcedureParameterExtractionAware.class.isInstance( hibernateType ) ) {
return (T) ( (ProcedureParameterExtractionAware) hibernateType ).extract( statement, startIndex, session() );
}
else {
return (T) statement.getObject( startIndex );
}
}
catch (SQLException e) {
throw procedureCall.session().getFactory().getSQLExceptionHelper().convert(
e,
"Unable to extract OUT/INOUT parameter value"
);
}
}
}
public static class StoredProcedureParameterBindImpl<T> implements StoredProcedureParameterBind<T> {
private final T value;
private final TemporalType explicitTemporalType;
public StoredProcedureParameterBindImpl(T value) {
this( value, null );
}
public StoredProcedureParameterBindImpl(T value, TemporalType explicitTemporalType) {
this.value = value;
this.explicitTemporalType = explicitTemporalType;
}
@Override
public T getValue() {
return value;
}
@Override
public TemporalType getExplicitTemporalType() {
return explicitTemporalType;
}
}
public static class NamedStoredProcedureParameter<T> extends AbstractStoredProcedureParameterImpl<T> {
private final String name;
public NamedStoredProcedureParameter(
StoredProcedureCallImpl procedureCall,
String name,
ParameterMode mode,
Class<T> type) {
super( procedureCall, mode, type );
this.name = name;
}
@Override
public String getName() {
return name;
}
}
public static class PositionalStoredProcedureParameter<T> extends AbstractStoredProcedureParameterImpl<T> {
private final Integer position;
public PositionalStoredProcedureParameter(
StoredProcedureCallImpl procedureCall,
Integer position,
ParameterMode mode,
Class<T> type) {
super( procedureCall, mode, type );
this.position = position;
}
@Override
public Integer getPosition() {
return position;
}
}
}

View File

@ -0,0 +1,136 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.procedure;
import javax.persistence.ParameterMode;
import java.util.List;
import org.hibernate.BasicQueryContract;
import org.hibernate.MappingException;
import org.hibernate.SynchronizeableQuery;
/**
* Defines support for executing database stored procedures and functions
*
* @author Steve Ebersole
*/
public interface Call extends BasicQueryContract, SynchronizeableQuery {
@Override
public Call addSynchronizedQuerySpace(String querySpace);
@Override
public Call addSynchronizedEntityName(String entityName) throws MappingException;
@Override
public Call addSynchronizedEntityClass(Class entityClass) throws MappingException;
/**
* Get the name of the stored procedure to be called.
*
* @return The procedure name.
*/
public String getProcedureName();
/**
* Basic form for registering a positional parameter.
*
* @param position The position
* @param type The Java type of the parameter
* @param mode The parameter mode (in, out, inout)
*
* @return The parameter registration memento
*/
public <T> ParameterRegistration<T> registerParameter(int position, Class<T> type, ParameterMode mode);
/**
* Chained form of {@link #registerParameter(int, Class, javax.persistence.ParameterMode)}
*
* @param position The position
* @param type The Java type of the parameter
* @param mode The parameter mode (in, out, inout)
*
* @return {@code this}, for method chaining
*/
public Call registerParameter0(int position, Class type, ParameterMode mode);
/**
* Retrieve a previously registered parameter memento by the position under which it was registered.
*
* @param position The parameter position
*
* @return The parameter registration memento
*/
public ParameterRegistration getParameterRegistration(int position);
/**
* Basic form for registering a named parameter.
*
* @param parameterName The parameter name
* @param type The Java type of the parameter
* @param mode The parameter mode (in, out, inout)
*
* @return The parameter registration memento
*/
public <T> ParameterRegistration<T> registerParameter(String parameterName, Class<T> type, ParameterMode mode)
throws NamedParametersNotSupportedException;
/**
* Chained form of {@link #registerParameter(String, Class, javax.persistence.ParameterMode)}
*
* @param parameterName The parameter name
* @param type The Java type of the parameter
* @param mode The parameter mode (in, out, inout)
*
* @return The parameter registration memento
*/
public Call registerParameter0(String parameterName, Class type, ParameterMode mode)
throws NamedParametersNotSupportedException;
/**
* Retrieve a previously registered parameter memento by the name under which it was registered.
*
* @param name The parameter name
*
* @return The parameter registration memento
*/
public ParameterRegistration getParameterRegistration(String name);
/**
* Retrieve all registered parameters.
*
* @return The (immutable) list of all registered parameters.
*/
public List<ParameterRegistration> getRegisteredParameters();
/**
* Retrieves access to outputs of this procedure call. Can be called multiple times, returning the same
* Output instance each time.
* <p/>
* Note that the procedure will not actually be executed until the outputs are actually accessed.
*
* @return The outputs representation
*/
public Outputs getOutputs();
}

View File

@ -0,0 +1,30 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.procedure;
/**
* @author Steve Ebersole
*/
public interface InOutParameterRegistration<T> extends InParameterRegistration<T>, OutParameterRegistration<T> {
}

View File

@ -1,7 +1,7 @@
/* /*
* Hibernate, Relational Persistence for Idiomatic Java * Hibernate, Relational Persistence for Idiomatic Java
* *
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as * Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution * indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are * statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc. * distributed under license by Red Hat Inc.
@ -21,11 +21,10 @@
* 51 Franklin Street, Fifth Floor * 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA * Boston, MA 02110-1301 USA
*/ */
package org.hibernate; package org.hibernate.procedure;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public interface StoredProcedureReturn { public interface InParameterRegistration<T> extends ParameterRegistration<T> {
public boolean isResultSet();
} }

View File

@ -0,0 +1,38 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.procedure;
import org.hibernate.HibernateException;
/**
* Thrown to indicate that an attempt was made to register a stored procedure named parameter, but the underlying
* database reports to not support named parameters.
*
* @author Steve Ebersole
*/
public class NamedParametersNotSupportedException extends HibernateException {
public NamedParametersNotSupportedException(String message) {
super( message );
}
}

View File

@ -1,7 +1,7 @@
/* /*
* Hibernate, Relational Persistence for Idiomatic Java * Hibernate, Relational Persistence for Idiomatic Java
* *
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as * Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution * indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are * statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc. * distributed under license by Red Hat Inc.
@ -21,11 +21,10 @@
* 51 Franklin Street, Fifth Floor * 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA * Boston, MA 02110-1301 USA
*/ */
package org.hibernate; package org.hibernate.procedure;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public interface StoredProcedureUpdateCountReturn extends StoredProcedureReturn { public interface OutParameterRegistration<T> extends ParameterRegistration<T> {
public int getUpdateCount();
} }

View File

@ -21,7 +21,7 @@
* 51 Franklin Street, Fifth Floor * 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA * Boston, MA 02110-1301 USA
*/ */
package org.hibernate; package org.hibernate.procedure;
/** /**
* Represents all the outputs of a call to a database stored procedure (or function) through the JDBC * Represents all the outputs of a call to a database stored procedure (or function) through the JDBC
@ -29,7 +29,22 @@ package org.hibernate;
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public interface StoredProcedureOutputs { public interface Outputs {
/**
* Retrieve the value of an OUTPUT parameter by the parameter's registration memento.
* <p/>
* Should NOT be called for parameters registered as REF_CURSOR. REF_CURSOR parameters should be
* accessed via the returns (see {@link #getNextReturn}
*
* @param parameterRegistration The parameter's registration memento.
*
* @return The output value.
*
* @see Call#registerParameter(String, Class, javax.persistence.ParameterMode)
*/
public <T> T getOutputParameterValue(ParameterRegistration<T> parameterRegistration);
/** /**
* Retrieve the value of an OUTPUT parameter by the name under which the parameter was registered. * Retrieve the value of an OUTPUT parameter by the name under which the parameter was registered.
* *
@ -37,7 +52,7 @@ public interface StoredProcedureOutputs {
* *
* @return The output value. * @return The output value.
* *
* @see StoredProcedureCall#registerStoredProcedureParameter(String, Class, javax.persistence.ParameterMode) * @see Call#registerParameter(String, Class, javax.persistence.ParameterMode)
*/ */
public Object getOutputParameterValue(String name); public Object getOutputParameterValue(String name);
@ -48,7 +63,7 @@ public interface StoredProcedureOutputs {
* *
* @return The output value. * @return The output value.
* *
* @see StoredProcedureCall#registerStoredProcedureParameter(int, Class, javax.persistence.ParameterMode) * @see Call#registerParameter(int, Class, javax.persistence.ParameterMode)
*/ */
public Object getOutputParameterValue(int position); public Object getOutputParameterValue(int position);
@ -65,5 +80,5 @@ public interface StoredProcedureOutputs {
* *
* @return The next return. * @return The next return.
*/ */
public StoredProcedureReturn getNextReturn(); public Return getNextReturn();
} }

View File

@ -0,0 +1,46 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.procedure;
import javax.persistence.TemporalType;
/**
* Describes an input value binding for any IN/INOUT parameters.
*/
public interface ParameterBind<T> {
/**
* Retrieves the bound value.
*
* @return The bound value.
*/
public T getValue();
/**
* If {@code <T>} represents a DATE/TIME type value, JPA usually allows specifying the particular parts of
* the DATE/TIME value to be bound. This value represents the particular part the user requested to be bound.
*
* @return The explicitly supplied TemporalType.
*/
public TemporalType getExplicitTemporalType();
}

View File

@ -0,0 +1,37 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.procedure;
import org.hibernate.HibernateException;
/**
* Thrown to indicate a misuse of a {@link ParameterRegistration}
*
* @author Steve Ebersole
*/
public class ParameterMisuseException extends HibernateException {
public ParameterMisuseException(String message) {
super( message );
}
}

View File

@ -0,0 +1,100 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.procedure;
import javax.persistence.ParameterMode;
import javax.persistence.TemporalType;
import org.hibernate.type.Type;
/**
* @author Steve Ebersole
*/
public interface ParameterRegistration<T> {
/**
* The name under which this parameter was registered. Can be {@code null} which should indicate that
* positional registration was used (and therefore {@link #getPosition()} should return non-null.
*
* @return The name;
*/
public String getName();
/**
* The position at which this parameter was registered. Can be {@code null} which should indicate that
* named registration was used (and therefore {@link #getName()} should return non-null.
*
* @return The name;
*/
public Integer getPosition();
/**
* Obtain the Java type of parameter. This is used to guess the Hibernate type (unless {@link #setHibernateType}
* is called explicitly).
*
* @return The parameter Java type.
*/
public Class<T> getType();
/**
* Retrieves the parameter "mode" which describes how the parameter is defined in the actual database procedure
* definition (is it an INPUT parameter? An OUTPUT parameter? etc).
*
* @return The parameter mode.
*/
public ParameterMode getMode();
/**
* Set the Hibernate mapping type for this parameter.
*
* @param type The Hibernate mapping type.
*/
public void setHibernateType(Type type);
/**
* Retrieve the binding associated with this parameter. The binding is only relevant for INPUT parameters. Can
* return {@code null} if nothing has been bound yet. To bind a value to the parameter use one of the
* {@link #bindValue} methods.
*
* @return The parameter binding
*/
public ParameterBind getParameterBind();
/**
* Bind a value to the parameter. How this value is bound to the underlying JDBC CallableStatement is
* totally dependent on the Hibernate type.
*
* @param value The value to bind.
*/
public 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
* this form if the parameter is not DATE/TIME type. The Hibernate type is circumvented in this case and
* an appropriate "precision" Type is used instead.
*
* @param value The value to bind
* @param explicitTemporalType An explicitly supplied TemporalType.
*/
public void bindValue(T value, TemporalType explicitTemporalType);
}

View File

@ -21,7 +21,7 @@
* 51 Franklin Street, Fifth Floor * 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA * Boston, MA 02110-1301 USA
*/ */
package org.hibernate; package org.hibernate.procedure;
import java.util.List; import java.util.List;
@ -30,7 +30,7 @@ import java.util.List;
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public interface StoredProcedureResultSetReturn extends StoredProcedureReturn { public interface ResultSetReturn extends Return {
/** /**
* Consume the underlying {@link java.sql.ResultSet} and return the resulting List. * Consume the underlying {@link java.sql.ResultSet} and return the resulting List.
* *

View File

@ -0,0 +1,41 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.procedure;
/**
* Common contract for procedure call results which can be either results ({@link ResultSetReturn}) or update
* counts ({@link UpdateCountReturn}).
*
* @author Steve Ebersole
*/
public interface Return {
/**
* Determine if this return is a result (castable to {@link ResultSetReturn}). The alternative is that it is
* an update count (castable to {@link UpdateCountReturn}).
*
* @return {@code true} indicates that {@code this} can be safely cast to {@link ResultSetReturn}), other wise
* it can be cast to {@link UpdateCountReturn}.
*/
public boolean isResultSet();
}

View File

@ -0,0 +1,38 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.procedure;
/**
* Models a stored procedure result that is a result set.
*
* @author Steve Ebersole
*/
public interface UpdateCountReturn extends Return {
/**
* Retrieve the associated update count
*
* @return The update count
*/
public int getUpdateCount();
}

View File

@ -0,0 +1,267 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.procedure.internal;
import javax.persistence.ParameterMode;
import javax.persistence.TemporalType;
import java.sql.CallableStatement;
import java.sql.SQLException;
import java.util.Calendar;
import java.util.Date;
import org.jboss.logging.Logger;
import org.hibernate.cfg.NotYetImplementedException;
import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.procedure.ParameterBind;
import org.hibernate.procedure.ParameterMisuseException;
import org.hibernate.type.DateType;
import org.hibernate.type.ProcedureParameterExtractionAware;
import org.hibernate.type.Type;
/**
* @author Steve Ebersole
*/
public abstract class AbstractParameterRegistrationImpl<T> implements ParameterRegistrationImplementor<T> {
private static final Logger log = Logger.getLogger( AbstractParameterRegistrationImpl.class );
private final CallImpl procedureCall;
private final Integer position;
private final String name;
private final ParameterMode mode;
private final Class<T> type;
private ParameterBindImpl bind;
private int startIndex;
private Type hibernateType;
private int[] sqlTypes;
protected AbstractParameterRegistrationImpl(
CallImpl procedureCall,
Integer position,
Class<T> type,
ParameterMode mode) {
this( procedureCall, position, null, type, mode );
}
protected AbstractParameterRegistrationImpl(
CallImpl procedureCall,
String name,
Class<T> type,
ParameterMode mode) {
this( procedureCall, null, name, type, mode );
}
private AbstractParameterRegistrationImpl(
CallImpl procedureCall,
Integer position,
String name,
Class<T> type,
ParameterMode mode) {
this.procedureCall = procedureCall;
this.position = position;
this.name = name;
this.mode = mode;
this.type = type;
setHibernateType( session().getFactory().getTypeResolver().heuristicType( type.getName() ) );
}
protected SessionImplementor session() {
return procedureCall.session();
}
@Override
public String getName() {
return name;
}
@Override
public Integer getPosition() {
return position;
}
@Override
public Class<T> getType() {
return type;
}
@Override
public ParameterMode getMode() {
return mode;
}
@Override
public void setHibernateType(Type type) {
if ( type == null ) {
throw new IllegalArgumentException( "Type cannot be null" );
}
this.hibernateType = type;
this.sqlTypes = hibernateType.sqlTypes( session().getFactory() );
}
@Override
public ParameterBind getParameterBind() {
return bind;
}
@Override
public void bindValue(T value) {
validateBindability();
this.bind = new ParameterBindImpl<T>( value );
}
private void validateBindability() {
if ( ! canBind() ) {
throw new ParameterMisuseException( "Cannot bind value to non-input parameter : " + this );
}
}
private boolean canBind() {
return mode == ParameterMode.IN || mode == ParameterMode.INOUT;
}
@Override
public void bindValue(T value, TemporalType explicitTemporalType) {
validateBindability();
if ( explicitTemporalType != null ) {
if ( ! isDateTimeType() ) {
throw new IllegalArgumentException( "TemporalType should not be specified for non date/time type" );
}
}
this.bind = new ParameterBindImpl<T>( value, explicitTemporalType );
}
private boolean isDateTimeType() {
return Date.class.isAssignableFrom( type )
|| Calendar.class.isAssignableFrom( type );
}
@Override
public void prepare(CallableStatement statement, int startIndex) throws SQLException {
if ( mode == ParameterMode.REF_CURSOR ) {
throw new NotYetImplementedException( "Support for REF_CURSOR parameters not yet supported" );
}
this.startIndex = startIndex;
if ( mode == ParameterMode.IN || mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) {
if ( mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) {
if ( sqlTypes.length > 1 ) {
if ( ProcedureParameterExtractionAware.class.isInstance( hibernateType )
&& ( (ProcedureParameterExtractionAware) hibernateType ).canDoExtraction() ) {
// the type can handle multi-param extraction...
}
else {
// it cannot...
throw new UnsupportedOperationException(
"Type [" + hibernateType + "] does support multi-parameter value extraction"
);
}
}
for ( int i = 0; i < sqlTypes.length; i++ ) {
statement.registerOutParameter( startIndex + i, sqlTypes[i] );
}
}
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.
// 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
);
}
else {
final Type typeToUse;
if ( bind.getExplicitTemporalType() != null && bind.getExplicitTemporalType() == TemporalType.TIMESTAMP ) {
typeToUse = hibernateType;
}
else if ( bind.getExplicitTemporalType() != null && bind.getExplicitTemporalType() == TemporalType.DATE ) {
typeToUse = DateType.INSTANCE;
}
else {
typeToUse = hibernateType;
}
typeToUse.nullSafeSet( statement, bind.getValue(), startIndex, session() );
}
}
}
else {
// we have a REF_CURSOR type param
if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED ) {
session().getFactory().getServiceRegistry()
.getService( RefCursorSupport.class )
.registerRefCursorParameter( statement, getName() );
}
else {
session().getFactory().getServiceRegistry()
.getService( RefCursorSupport.class )
.registerRefCursorParameter( statement, getPosition() );
}
}
}
public int[] getSqlTypes() {
return sqlTypes;
}
@Override
@SuppressWarnings("unchecked")
public T extract(CallableStatement statement) {
if ( mode == ParameterMode.IN ) {
throw new ParameterMisuseException( "IN parameter not valid for output extraction" );
}
else if ( mode == ParameterMode.REF_CURSOR ) {
throw new ParameterMisuseException( "REF_CURSOR parameters should be accessed via results" );
}
try {
if ( ProcedureParameterExtractionAware.class.isInstance( hibernateType ) ) {
return (T) ( (ProcedureParameterExtractionAware) hibernateType ).extract( statement, startIndex, session() );
}
else {
return (T) statement.getObject( startIndex );
}
}
catch (SQLException e) {
throw procedureCall.session().getFactory().getSQLExceptionHelper().convert(
e,
"Unable to extract OUT/INOUT parameter value"
);
}
}
}

View File

@ -0,0 +1,389 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.procedure.internal;
import javax.persistence.ParameterMode;
import java.sql.CallableStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.MappingException;
import org.hibernate.QueryException;
import org.hibernate.cfg.NotYetImplementedException;
import org.hibernate.engine.ResultSetMappingDefinition;
import org.hibernate.engine.jdbc.spi.ExtractedDatabaseMetaData;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryRootReturn;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.AbstractBasicQueryContractImpl;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.procedure.Call;
import org.hibernate.procedure.NamedParametersNotSupportedException;
import org.hibernate.procedure.ParameterRegistration;
import org.hibernate.procedure.Outputs;
import org.hibernate.type.Type;
/**
* Standard implementation of {@link Call}
*
* @author Steve Ebersole
*/
public class CallImpl extends AbstractBasicQueryContractImpl implements Call {
private final String procedureName;
private final NativeSQLQueryReturn[] queryReturns;
private ParameterStrategy parameterStrategy = ParameterStrategy.UNKNOWN;
private List<ParameterRegistrationImplementor<?>> registeredParameters = new ArrayList<ParameterRegistrationImplementor<?>>();
private Set<String> synchronizedQuerySpaces;
private OutputsImpl outputs;
@SuppressWarnings("unchecked")
public CallImpl(SessionImplementor session, String procedureName) {
this( session, procedureName, (List) null );
}
public CallImpl(SessionImplementor session, String procedureName, List<NativeSQLQueryReturn> queryReturns) {
super( session );
this.procedureName = procedureName;
if ( queryReturns == null || queryReturns.isEmpty() ) {
this.queryReturns = new NativeSQLQueryReturn[0];
}
else {
this.queryReturns = queryReturns.toArray( new NativeSQLQueryReturn[ queryReturns.size() ] );
}
}
public CallImpl(SessionImplementor session, String procedureName, Class... resultClasses) {
this( session, procedureName, collectQueryReturns( resultClasses ) );
}
private static List<NativeSQLQueryReturn> collectQueryReturns(Class[] resultClasses) {
if ( resultClasses == null || resultClasses.length == 0 ) {
return null;
}
List<NativeSQLQueryReturn> queryReturns = new ArrayList<NativeSQLQueryReturn>( resultClasses.length );
int i = 1;
for ( Class resultClass : resultClasses ) {
queryReturns.add( new NativeSQLQueryRootReturn( "alias" + i, resultClass.getName(), LockMode.READ ) );
i++;
}
return queryReturns;
}
public CallImpl(SessionImplementor session, String procedureName, String... resultSetMappings) {
this( session, procedureName, collectQueryReturns( session, resultSetMappings ) );
}
private static List<NativeSQLQueryReturn> collectQueryReturns(SessionImplementor session, String[] resultSetMappings) {
if ( resultSetMappings == null || resultSetMappings.length == 0 ) {
return null;
}
List<NativeSQLQueryReturn> queryReturns = new ArrayList<NativeSQLQueryReturn>( resultSetMappings.length );
for ( String resultSetMapping : resultSetMappings ) {
ResultSetMappingDefinition mapping = session.getFactory().getResultSetMapping( resultSetMapping );
if ( mapping == null ) {
throw new MappingException( "Unknown SqlResultSetMapping [" + resultSetMapping + "]" );
}
queryReturns.addAll( Arrays.asList( mapping.getQueryReturns() ) );
}
return queryReturns;
}
// public CallImpl(
// SessionImplementor session,
// String procedureName,
// List<StoredProcedureParameter> parameters) {
// // this form is intended for named stored procedure calls.
// // todo : introduce a NamedProcedureCallDefinition object to hold all needed info and pass that in here; will help with EM.addNamedQuery as well..
// this( session, procedureName );
// for ( StoredProcedureParameter parameter : parameters ) {
// registerParameter( (StoredProcedureParameterImplementor) parameter );
// }
// }
@Override
public SessionImplementor session() {
// provide access to delegates
return super.session();
}
public ParameterStrategy getParameterStrategy() {
return parameterStrategy;
}
@Override
public String getProcedureName() {
return procedureName;
}
NativeSQLQueryReturn[] getQueryReturns() {
return queryReturns;
}
@Override
@SuppressWarnings("unchecked")
public <T> ParameterRegistration<T> registerParameter(int position, Class<T> type, ParameterMode mode) {
final PositionalParameterRegistration parameterRegistration = new PositionalParameterRegistration( this, position, type, mode );
registerParameter( parameterRegistration );
return parameterRegistration;
}
@Override
@SuppressWarnings("unchecked")
public Call registerParameter0(int position, Class type, ParameterMode mode) {
registerParameter( position, type, mode );
return this;
}
private void registerParameter(ParameterRegistrationImplementor parameter) {
if ( StringHelper.isNotEmpty( parameter.getName() ) ) {
prepareForNamedParameters();
}
else if ( parameter.getPosition() != null ) {
prepareForPositionalParameters();
}
else {
throw new IllegalArgumentException( "Given parameter did not define name or position [" + parameter + "]" );
}
registeredParameters.add( parameter );
}
private void prepareForPositionalParameters() {
if ( parameterStrategy == ParameterStrategy.NAMED ) {
throw new QueryException( "Cannot mix named and positional parameters" );
}
parameterStrategy = ParameterStrategy.POSITIONAL;
}
private void prepareForNamedParameters() {
if ( parameterStrategy == ParameterStrategy.POSITIONAL ) {
throw new QueryException( "Cannot mix named and positional parameters" );
}
if ( parameterStrategy == null ) {
// protect to only do this check once
final ExtractedDatabaseMetaData databaseMetaData = session().getTransactionCoordinator()
.getJdbcCoordinator()
.getLogicalConnection()
.getJdbcServices()
.getExtractedMetaDataSupport();
if ( ! databaseMetaData.supportsNamedParameters() ) {
throw new NamedParametersNotSupportedException(
"Named stored procedure parameters used, but JDBC driver does not support named parameters"
);
}
parameterStrategy = ParameterStrategy.NAMED;
}
}
@Override
public ParameterRegistrationImplementor getParameterRegistration(int position) {
if ( parameterStrategy != ParameterStrategy.POSITIONAL ) {
throw new IllegalArgumentException( "Positions were not used to register parameters with this stored procedure call" );
}
try {
return registeredParameters.get( position );
}
catch ( Exception e ) {
throw new QueryException( "Could not locate parameter registered using that position [" + position + "]" );
}
}
@Override
@SuppressWarnings("unchecked")
public <T> ParameterRegistration<T> registerParameter(String name, Class<T> type, ParameterMode mode) {
final NamedParameterRegistration parameterRegistration = new NamedParameterRegistration( this, name, type, mode );
registerParameter( parameterRegistration );
return parameterRegistration;
}
@Override
@SuppressWarnings("unchecked")
public Call registerParameter0(String name, Class type, ParameterMode mode) {
registerParameter( name, type, mode );
return this;
}
@Override
public ParameterRegistrationImplementor getParameterRegistration(String name) {
if ( parameterStrategy != ParameterStrategy.NAMED ) {
throw new IllegalArgumentException( "Names were not used to register parameters with this stored procedure call" );
}
for ( ParameterRegistrationImplementor parameter : registeredParameters ) {
if ( name.equals( parameter.getName() ) ) {
return parameter;
}
}
throw new IllegalArgumentException( "Could not locate parameter registered under that name [" + name + "]" );
}
@Override
@SuppressWarnings("unchecked")
public List<ParameterRegistration> getRegisteredParameters() {
return new ArrayList<ParameterRegistration>( registeredParameters );
}
@Override
public Outputs getOutputs() {
if ( outputs == null ) {
outputs = buildOutputs();
}
return outputs;
}
private OutputsImpl buildOutputs() {
// todo : going to need a very specialized Loader for this.
// or, might be a good time to look at splitting Loader up into:
// 1) building statement objects
// 2) executing statement objects
// 3) processing result sets
// for now assume there are no resultClasses nor mappings defined..
// TOTAL PROOF-OF-CONCEPT!!!!!!
final StringBuilder buffer = new StringBuilder().append( "{call " )
.append( procedureName )
.append( "(" );
String sep = "";
for ( ParameterRegistrationImplementor parameter : registeredParameters ) {
for ( int i = 0; i < parameter.getSqlTypes().length; i++ ) {
buffer.append( sep ).append( "?" );
sep = ",";
}
}
buffer.append( ")}" );
try {
final CallableStatement statement = session().getTransactionCoordinator()
.getJdbcCoordinator()
.getLogicalConnection()
.getShareableConnectionProxy()
.prepareCall( buffer.toString() );
// prepare parameters
int i = 1;
for ( ParameterRegistrationImplementor parameter : registeredParameters ) {
if ( parameter == null ) {
throw new QueryException( "Registered stored procedure parameters had gaps" );
}
parameter.prepare( statement, i );
i += parameter.getSqlTypes().length;
}
return new OutputsImpl( this, statement );
}
catch (SQLException e) {
throw session().getFactory().getSQLExceptionHelper().convert(
e,
"Error preparing CallableStatement",
getProcedureName()
);
}
}
@Override
public Type[] getReturnTypes() throws HibernateException {
throw new NotYetImplementedException();
}
protected Set<String> synchronizedQuerySpaces() {
if ( synchronizedQuerySpaces == null ) {
synchronizedQuerySpaces = new HashSet<String>();
}
return synchronizedQuerySpaces;
}
@Override
@SuppressWarnings("unchecked")
public Collection<String> getSynchronizedQuerySpaces() {
if ( synchronizedQuerySpaces == null ) {
return Collections.emptySet();
}
else {
return Collections.unmodifiableSet( synchronizedQuerySpaces );
}
}
public Set<String> getSynchronizedQuerySpacesSet() {
return (Set<String>) getSynchronizedQuerySpaces();
}
@Override
public CallImpl addSynchronizedQuerySpace(String querySpace) {
synchronizedQuerySpaces().add( querySpace );
return this;
}
@Override
public CallImpl addSynchronizedEntityName(String entityName) {
addSynchronizedQuerySpaces( session().getFactory().getEntityPersister( entityName ) );
return this;
}
protected void addSynchronizedQuerySpaces(EntityPersister persister) {
synchronizedQuerySpaces().addAll( Arrays.asList( (String[]) persister.getQuerySpaces() ) );
}
@Override
public CallImpl addSynchronizedEntityClass(Class entityClass) {
addSynchronizedQuerySpaces( session().getFactory().getEntityPersister( entityClass.getName() ) );
return this;
}
public QueryParameters buildQueryParametersObject() {
QueryParameters qp = super.buildQueryParametersObject();
// both of these are for documentation purposes, they are actually handled directly...
qp.setAutoDiscoverScalarTypes( true );
qp.setCallable( true );
return qp;
}
public ParameterRegistrationImplementor[] collectRefCursorParameters() {
List<ParameterRegistrationImplementor> refCursorParams = new ArrayList<ParameterRegistrationImplementor>();
for ( ParameterRegistrationImplementor param : registeredParameters ) {
if ( param.getMode() == ParameterMode.REF_CURSOR ) {
refCursorParams.add( param );
}
}
return refCursorParams.toArray( new ParameterRegistrationImplementor[refCursorParams.size()] );
}
}

View File

@ -0,0 +1,39 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.procedure.internal;
import javax.persistence.ParameterMode;
/**
* @author Steve Ebersole
*/
public class NamedParameterRegistration<T> extends AbstractParameterRegistrationImpl<T> {
public NamedParameterRegistration(
CallImpl procedureCall,
String name,
Class<T> type,
ParameterMode mode) {
super( procedureCall, name, type, mode );
}
}

View File

@ -21,7 +21,7 @@
* 51 Franklin Street, Fifth Floor * 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA * Boston, MA 02110-1301 USA
*/ */
package org.hibernate.internal; package org.hibernate.procedure.internal;
import java.sql.CallableStatement; import java.sql.CallableStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
@ -32,27 +32,24 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import org.hibernate.JDBCException; import org.hibernate.JDBCException;
import org.hibernate.StoredProcedureOutputs; import org.hibernate.procedure.Outputs;
import org.hibernate.StoredProcedureResultSetReturn; import org.hibernate.procedure.ParameterRegistration;
import org.hibernate.StoredProcedureReturn; import org.hibernate.procedure.Return;
import org.hibernate.StoredProcedureUpdateCountReturn;
import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.StoredProcedureCallImpl.StoredProcedureParameterImplementor;
import org.hibernate.loader.custom.CustomLoader; import org.hibernate.loader.custom.CustomLoader;
import org.hibernate.loader.custom.CustomQuery; import org.hibernate.loader.custom.CustomQuery;
import org.hibernate.loader.custom.Return;
import org.hibernate.loader.custom.sql.SQLQueryReturnProcessor; import org.hibernate.loader.custom.sql.SQLQueryReturnProcessor;
import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport; import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class StoredProcedureOutputsImpl implements StoredProcedureOutputs { public class OutputsImpl implements Outputs {
private final StoredProcedureCallImpl procedureCall; private final CallImpl procedureCall;
private final CallableStatement callableStatement; private final CallableStatement callableStatement;
private final StoredProcedureParameterImplementor[] refCursorParameters; private final ParameterRegistrationImplementor[] refCursorParameters;
private final CustomLoaderExtension loader; private final CustomLoaderExtension loader;
private CurrentReturnDescriptor currentReturnDescriptor; private CurrentReturnDescriptor currentReturnDescriptor;
@ -60,7 +57,7 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs {
private boolean executed = false; private boolean executed = false;
private int refCursorParamIndex = 0; private int refCursorParamIndex = 0;
StoredProcedureOutputsImpl(StoredProcedureCallImpl procedureCall, CallableStatement callableStatement) { OutputsImpl(CallImpl procedureCall, CallableStatement callableStatement) {
this.procedureCall = procedureCall; this.procedureCall = procedureCall;
this.callableStatement = callableStatement; this.callableStatement = callableStatement;
@ -69,14 +66,19 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs {
this.loader = buildSpecializedCustomLoader( procedureCall ); this.loader = buildSpecializedCustomLoader( procedureCall );
} }
@Override
public <T> T getOutputParameterValue(ParameterRegistration<T> parameterRegistration) {
return ( (ParameterRegistrationImplementor<T>) parameterRegistration ).extract( callableStatement );
}
@Override @Override
public Object getOutputParameterValue(String name) { public Object getOutputParameterValue(String name) {
return procedureCall.getRegisteredParameter( name ).extract( callableStatement ); return procedureCall.getParameterRegistration( name ).extract( callableStatement );
} }
@Override @Override
public Object getOutputParameterValue(int position) { public Object getOutputParameterValue(int position) {
return procedureCall.getRegisteredParameter( position ).extract( callableStatement ); return procedureCall.getParameterRegistration( position ).extract( callableStatement );
} }
@Override @Override
@ -125,7 +127,7 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs {
} }
@Override @Override
public StoredProcedureReturn getNextReturn() { public Return getNextReturn() {
if ( currentReturnDescriptor == null ) { if ( currentReturnDescriptor == null ) {
if ( executed ) { if ( executed ) {
throw new IllegalStateException( "Unexpected condition" ); throw new IllegalStateException( "Unexpected condition" );
@ -157,7 +159,7 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs {
this.refCursorParamIndex++; this.refCursorParamIndex++;
ResultSet resultSet; ResultSet resultSet;
int refCursorParamIndex = copyReturnDescriptor.refCursorParamIndex; int refCursorParamIndex = copyReturnDescriptor.refCursorParamIndex;
StoredProcedureParameterImplementor refCursorParam = refCursorParameters[refCursorParamIndex]; ParameterRegistrationImplementor refCursorParam = refCursorParameters[refCursorParamIndex];
if ( refCursorParam.getName() != null ) { if ( refCursorParam.getName() != null ) {
resultSet = procedureCall.session().getFactory().getServiceRegistry() resultSet = procedureCall.session().getFactory().getServiceRegistry()
.getService( RefCursorSupport.class ) .getService( RefCursorSupport.class )
@ -192,11 +194,11 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs {
} }
} }
private static class ResultSetReturn implements StoredProcedureResultSetReturn { private static class ResultSetReturn implements org.hibernate.procedure.ResultSetReturn {
private final StoredProcedureOutputsImpl storedProcedureOutputs; private final OutputsImpl storedProcedureOutputs;
private final ResultSet resultSet; private final ResultSet resultSet;
public ResultSetReturn(StoredProcedureOutputsImpl storedProcedureOutputs, ResultSet resultSet) { public ResultSetReturn(OutputsImpl storedProcedureOutputs, ResultSet resultSet) {
this.storedProcedureOutputs = storedProcedureOutputs; this.storedProcedureOutputs = storedProcedureOutputs;
this.resultSet = resultSet; this.resultSet = resultSet;
} }
@ -229,12 +231,12 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs {
} }
} }
private class UpdateCountReturn implements StoredProcedureUpdateCountReturn { private class UpdateCountReturn implements org.hibernate.procedure.UpdateCountReturn {
private final StoredProcedureOutputsImpl storedProcedureOutputs; private final OutputsImpl procedureOutputs;
private final int updateCount; private final int updateCount;
public UpdateCountReturn(StoredProcedureOutputsImpl storedProcedureOutputs, int updateCount) { public UpdateCountReturn(OutputsImpl procedureOutputs, int updateCount) {
this.storedProcedureOutputs = storedProcedureOutputs; this.procedureOutputs = procedureOutputs;
this.updateCount = updateCount; this.updateCount = updateCount;
} }
@ -249,13 +251,13 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs {
} }
} }
private static CustomLoaderExtension buildSpecializedCustomLoader(final StoredProcedureCallImpl procedureCall) { private static CustomLoaderExtension buildSpecializedCustomLoader(final CallImpl procedureCall) {
final SQLQueryReturnProcessor processor = new SQLQueryReturnProcessor( final SQLQueryReturnProcessor processor = new SQLQueryReturnProcessor(
procedureCall.getQueryReturns(), procedureCall.getQueryReturns(),
procedureCall.session().getFactory() procedureCall.session().getFactory()
); );
processor.process(); processor.process();
final List<Return> customReturns = processor.generateCustomReturns( false ); final List<org.hibernate.loader.custom.Return> customReturns = processor.generateCustomReturns( false );
CustomQuery customQuery = new CustomQuery() { CustomQuery customQuery = new CustomQuery() {
@Override @Override
@ -275,7 +277,7 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs {
} }
@Override @Override
public List<Return> getCustomQueryReturns() { public List<org.hibernate.loader.custom.Return> getCustomQueryReturns() {
return customReturns; return customReturns;
} }
}; };

View File

@ -0,0 +1,57 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.procedure.internal;
import javax.persistence.TemporalType;
import org.hibernate.procedure.ParameterBind;
/**
* Implementation of the {@link ParameterBind} contract.
*
* @author Steve Ebersole
*/
public class ParameterBindImpl<T> implements ParameterBind<T> {
private final T value;
private final TemporalType explicitTemporalType;
public ParameterBindImpl(T value) {
this( value, null );
}
public ParameterBindImpl(T value, TemporalType explicitTemporalType) {
this.value = value;
this.explicitTemporalType = explicitTemporalType;
}
@Override
public T getValue() {
return value;
}
@Override
public TemporalType getExplicitTemporalType() {
return explicitTemporalType;
}
}

View File

@ -0,0 +1,41 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.procedure.internal;
import java.sql.CallableStatement;
import java.sql.SQLException;
import org.hibernate.procedure.ParameterRegistration;
/**
* @author Steve Ebersole
*/
public interface ParameterRegistrationImplementor<T> extends ParameterRegistration<T> {
public void prepare(CallableStatement statement, int i) throws SQLException;
public int[] getSqlTypes();
public T extract(CallableStatement statement);
}

View File

@ -0,0 +1,33 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.procedure.internal;
/**
* The style/strategy of parameter registration used in a particular procedure call definition.
*/
public enum ParameterStrategy {
NAMED,
POSITIONAL,
UNKNOWN
}

View File

@ -0,0 +1,39 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.procedure.internal;
import javax.persistence.ParameterMode;
/**
* @author Steve Ebersole
*/
public class PositionalParameterRegistration<T> extends AbstractParameterRegistrationImpl<T> {
public PositionalParameterRegistration(
CallImpl procedureCall,
Integer position,
Class<T> type,
ParameterMode mode) {
super( procedureCall, position, type, mode );
}
}

View File

@ -0,0 +1,37 @@
package org.hibernate.procedure;
/**
* Defines support for executing database stored procedures and functions and accessing its outputs.
* <p/>
* First a reference to {@link Call} is obtained through one of the overloaded
* {@link org.hibernate.Session#createStoredProcedureCall} methods. The Call reference is then used to "configure"
* the procedure call (set timeouts, etc) and to perform parameter registration. All procedure parameters that the
* application wants to use must be registered. For all IN and INOUT parameters, values can then be bound.
* <p/>
* At this point we are ready to execute the procedure call and start accessing the outputs. This is done by first
* calling the {@link Call#getOutputs()} method. The underlying JDBC call is executed as needed. The pattern to
* access the returns is iterating through the outputs while {@link Outputs#hasMoreReturns()} returns {@code true} and
* calling {@link Outputs#getNextReturn()} during iteration:
* <code>
* Call call = session.createStoredProcedureCall( "some_procedure" );
* ...
* Outputs = call.getOutputs();
* while ( call.hasMoreReturns() ) {
* final Return rtn = call.getNextReturn();
* if ( rtn.isResultSet() ) {
* handleResultSetReturn( (ResultSetReturn) rtn );
* }
* else {
* handleUpdateCountReturn( (UpdateCountReturn) rtn );
* }
* }
* </code>
* <p/>
* Finally output parameters can be accessed using the overloaded {@link Outputs#getOutputParameterValue} methods.
* For portability amongst databases, it is advised to access the output parameters after all returns have been
* processed.
*
* @see org.hibernate.Session#createStoredProcedureCall(String)
* @see org.hibernate.Session#createStoredProcedureCall(String, Class[])
* @see org.hibernate.Session#createStoredProcedureCall(String, String...)
*/

View File

@ -23,24 +23,21 @@
*/ */
package org.hibernate.test.sql.storedproc; package org.hibernate.test.sql.storedproc;
import javax.persistence.Entity; import javax.persistence.ParameterMode;
import javax.persistence.Id;
import java.util.List; import java.util.List;
import org.hibernate.HibernateException; import org.hibernate.JDBCException;
import org.hibernate.SQLQuery;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.StoredProcedureCall;
import org.hibernate.StoredProcedureOutputs;
import org.hibernate.StoredProcedureResultSetReturn;
import org.hibernate.StoredProcedureReturn;
import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Configuration;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.Mapping;
import org.hibernate.mapping.AuxiliaryDatabaseObject; import org.hibernate.mapping.AuxiliaryDatabaseObject;
import org.hibernate.procedure.Call;
import org.hibernate.procedure.Outputs;
import org.hibernate.procedure.ResultSetReturn;
import org.hibernate.procedure.Return;
import org.hibernate.dialect.H2Dialect;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.RequiresDialect;
@ -50,67 +47,289 @@ import org.hibernate.testing.junit4.ExtraAssertions;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@RequiresDialect( H2Dialect.class ) @RequiresDialect( H2Dialect.class )
public class StoredProcedureTest extends BaseCoreFunctionalTestCase { public class StoredProcedureTest extends BaseCoreFunctionalTestCase {
// this is not working in H2 @Override
// @Override protected void configure(Configuration configuration) {
// protected void configure(Configuration configuration) { super.configure( configuration );
// super.configure( configuration ); configuration.addAuxiliaryDatabaseObject(
// configuration.addAuxiliaryDatabaseObject( new AuxiliaryDatabaseObject() {
// new AuxiliaryDatabaseObject() { @Override
// @Override public void addDialectScope(String dialectName) {
// public void addDialectScope(String dialectName) { }
// }
// @Override
// @Override public boolean appliesToDialect(Dialect dialect) {
// public boolean appliesToDialect(Dialect dialect) { return H2Dialect.class.isInstance( dialect );
// return H2Dialect.class.isInstance( dialect ); }
// }
// @Override
// @Override public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) {
// public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) { return "CREATE ALIAS findOneUser AS $$\n" +
// return "CREATE ALIAS findUser AS $$\n" + "import org.h2.tools.SimpleResultSet;\n" +
// "import org.h2.tools.SimpleResultSet;\n" + "import java.sql.*;\n" +
// "import java.sql.*;\n" + "@CODE\n" +
// "@CODE\n" + "ResultSet findOneUser() {\n" +
// "ResultSet findUser() {\n" + " SimpleResultSet rs = new SimpleResultSet();\n" +
// " SimpleResultSet rs = new SimpleResultSet();\n" + " rs.addColumn(\"ID\", Types.INTEGER, 10, 0);\n" +
// " rs.addColumn(\"ID\", Types.INTEGER, 10, 0);\n" + " rs.addColumn(\"NAME\", Types.VARCHAR, 255, 0);\n" +
// " rs.addColumn(\"NAME\", Types.VARCHAR, 255, 0);\n" + " rs.addRow(1, \"Steve\");\n" +
// " rs.addRow(1, \"Steve\");\n" + " return rs;\n" +
// " return rs;\n" + "}\n" +
// "}\n" + "$$";
// "$$"; }
// }
// @Override
// @Override public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) {
// public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) { return "DROP ALIAS findUser IF EXISTS";
// return "DROP ALIAS findUser IF EXISTS"; }
// } }
// } );
// );
// } configuration.addAuxiliaryDatabaseObject(
new AuxiliaryDatabaseObject() {
@Override
public void addDialectScope(String dialectName) {
}
@Override
public boolean appliesToDialect(Dialect dialect) {
return H2Dialect.class.isInstance( dialect );
}
@Override
public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) {
return "CREATE ALIAS findUsers AS $$\n" +
"import org.h2.tools.SimpleResultSet;\n" +
"import java.sql.*;\n" +
"@CODE\n" +
"ResultSet findUsers() {\n" +
" SimpleResultSet rs = new SimpleResultSet();\n" +
" rs.addColumn(\"ID\", Types.INTEGER, 10, 0);\n" +
" rs.addColumn(\"NAME\", Types.VARCHAR, 255, 0);\n" +
" rs.addRow(1, \"Steve\");\n" +
" rs.addRow(2, \"John\");\n" +
" rs.addRow(3, \"Jane\");\n" +
" return rs;\n" +
"}\n" +
"$$";
}
@Override
public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) {
return "DROP ALIAS findUser IF EXISTS";
}
}
);
configuration.addAuxiliaryDatabaseObject(
new AuxiliaryDatabaseObject() {
@Override
public void addDialectScope(String dialectName) {
}
@Override
public boolean appliesToDialect(Dialect dialect) {
return H2Dialect.class.isInstance( dialect );
}
@Override
public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) {
return "CREATE ALIAS findUserRange AS $$\n" +
"import org.h2.tools.SimpleResultSet;\n" +
"import java.sql.*;\n" +
"@CODE\n" +
"ResultSet findUserRange(int start, int end) {\n" +
" SimpleResultSet rs = new SimpleResultSet();\n" +
" rs.addColumn(\"ID\", Types.INTEGER, 10, 0);\n" +
" rs.addColumn(\"NAME\", Types.VARCHAR, 255, 0);\n" +
" for ( int i = start; i < end; i++ ) {\n" +
" rs.addRow(1, \"User \" + i );\n" +
" }\n" +
" return rs;\n" +
"}\n" +
"$$";
}
@Override
public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) {
return "DROP ALIAS findUser IF EXISTS";
}
}
);
}
@Test @Test
public void baseTest() { public void baseTest() {
Session session = openSession(); Session session = openSession();
session.beginTransaction(); session.beginTransaction();
StoredProcedureCall query = session.createStoredProcedureCall( "user"); Call query = session.createStoredProcedureCall( "user");
StoredProcedureOutputs outputs = query.getOutputs(); Outputs outputs = query.getOutputs();
assertTrue( "Checking StoredProcedureOutputs has more returns", outputs.hasMoreReturns() ); assertTrue( "Checking Outputs has more returns", outputs.hasMoreReturns() );
StoredProcedureReturn nextReturn = outputs.getNextReturn(); Return nextReturn = outputs.getNextReturn();
assertNotNull( nextReturn ); assertNotNull( nextReturn );
ExtraAssertions.assertClassAssignability( StoredProcedureResultSetReturn.class, nextReturn.getClass() ); ExtraAssertions.assertClassAssignability( ResultSetReturn.class, nextReturn.getClass() );
StoredProcedureResultSetReturn resultSetReturn = (StoredProcedureResultSetReturn) nextReturn; ResultSetReturn resultSetReturn = (ResultSetReturn) nextReturn;
String name = (String) resultSetReturn.getSingleResult(); String name = (String) resultSetReturn.getSingleResult();
assertEquals( "SA", name ); assertEquals( "SA", name );
session.getTransaction().commit(); session.getTransaction().commit();
session.close(); session.close();
} }
@Test
public void testGetSingleResultTuple() {
Session session = openSession();
session.beginTransaction();
Call query = session.createStoredProcedureCall( "findOneUser" );
Outputs outputs = query.getOutputs();
assertTrue( "Checking Outputs has more returns", outputs.hasMoreReturns() );
Return nextReturn = outputs.getNextReturn();
assertNotNull( nextReturn );
ExtraAssertions.assertClassAssignability( ResultSetReturn.class, nextReturn.getClass() );
ResultSetReturn resultSetReturn = (ResultSetReturn) nextReturn;
Object result = resultSetReturn.getSingleResult();
ExtraAssertions.assertTyping( Object[].class, result );
String name = (String) ( (Object[]) result )[1];
assertEquals( "Steve", name );
session.getTransaction().commit();
session.close();
}
@Test
public void testGetResultListTuple() {
Session session = openSession();
session.beginTransaction();
Call query = session.createStoredProcedureCall( "findUsers" );
Outputs outputs = query.getOutputs();
assertTrue( "Checking Outputs has more returns", outputs.hasMoreReturns() );
Return nextReturn = outputs.getNextReturn();
assertNotNull( nextReturn );
ExtraAssertions.assertClassAssignability( ResultSetReturn.class, nextReturn.getClass() );
ResultSetReturn resultSetReturn = (ResultSetReturn) nextReturn;
List results = resultSetReturn.getResultList();
assertEquals( 3, results.size() );
for ( Object result : results ) {
ExtraAssertions.assertTyping( Object[].class, result );
Integer id = (Integer) ( (Object[]) result )[0];
String name = (String) ( (Object[]) result )[1];
if ( id.equals( 1 ) ) {
assertEquals( "Steve", name );
}
else if ( id.equals( 2 ) ) {
assertEquals( "John", name );
}
else if ( id.equals( 3 ) ) {
assertEquals( "Jane", name );
}
else {
fail( "Unexpected id value found [" + id + "]" );
}
}
session.getTransaction().commit();
session.close();
}
@Test
public void testInParametersByName() {
Session session = openSession();
session.beginTransaction();
Call query = session.createStoredProcedureCall( "findUserRange" );
query.registerParameter( "start", Integer.class, ParameterMode.IN ).bindValue( 1 );
query.registerParameter( "end", Integer.class, ParameterMode.IN ).bindValue( 2 );
Outputs outputs = query.getOutputs();
assertTrue( "Checking Outputs has more returns", outputs.hasMoreReturns() );
Return nextReturn = outputs.getNextReturn();
assertNotNull( nextReturn );
ExtraAssertions.assertClassAssignability( ResultSetReturn.class, nextReturn.getClass() );
ResultSetReturn resultSetReturn = (ResultSetReturn) nextReturn;
List results = resultSetReturn.getResultList();
assertEquals( 1, results.size() );
Object result = results.get( 0 );
ExtraAssertions.assertTyping( Object[].class, result );
Integer id = (Integer) ( (Object[]) result )[0];
String name = (String) ( (Object[]) result )[1];
assertEquals( 1, (int) id );
assertEquals( "User 1", name );
session.getTransaction().commit();
session.close();
}
@Test
public void testInParametersByPosition() {
Session session = openSession();
session.beginTransaction();
Call query = session.createStoredProcedureCall( "findUserRange" );
query.registerParameter( 1, Integer.class, ParameterMode.IN ).bindValue( 1 );
query.registerParameter( 2, Integer.class, ParameterMode.IN ).bindValue( 2 );
Outputs outputs = query.getOutputs();
assertTrue( "Checking Outputs has more returns", outputs.hasMoreReturns() );
Return nextReturn = outputs.getNextReturn();
assertNotNull( nextReturn );
ExtraAssertions.assertClassAssignability( ResultSetReturn.class, nextReturn.getClass() );
ResultSetReturn resultSetReturn = (ResultSetReturn) nextReturn;
List results = resultSetReturn.getResultList();
assertEquals( 1, results.size() );
Object result = results.get( 0 );
ExtraAssertions.assertTyping( Object[].class, result );
Integer id = (Integer) ( (Object[]) result )[0];
String name = (String) ( (Object[]) result )[1];
assertEquals( 1, (int) id );
assertEquals( "User 1", name );
session.getTransaction().commit();
session.close();
}
@Test
public void testInParametersNotSet() {
Session session = openSession();
session.beginTransaction();
// since the procedure does not define defaults for parameters this should result in SQLExceptions on
// execution
{
Call query = session.createStoredProcedureCall( "findUserRange" );
query.registerParameter( 1, Integer.class, ParameterMode.IN );
query.registerParameter( 2, Integer.class, ParameterMode.IN ).bindValue( 2 );
Outputs outputs = query.getOutputs();
try {
outputs.hasMoreReturns();
fail( "Expecting failure due to missing parameter bind" );
}
catch (JDBCException expected) {
}
}
{
Call query = session.createStoredProcedureCall( "findUserRange" );
query.registerParameter( "start", Integer.class, ParameterMode.IN );
query.registerParameter( "end", Integer.class, ParameterMode.IN ).bindValue( 2 );
Outputs outputs = query.getOutputs();
try {
outputs.hasMoreReturns();
fail( "Expecting failure due to missing parameter bind" );
}
catch (JDBCException expected) {
}
}
session.getTransaction().commit();
session.close();
}
} }

View File

@ -37,11 +37,11 @@ import java.util.List;
import org.hibernate.CacheMode; import org.hibernate.CacheMode;
import org.hibernate.FlushMode; import org.hibernate.FlushMode;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.StoredProcedureCall; import org.hibernate.procedure.Call;
import org.hibernate.StoredProcedureOutputs; import org.hibernate.procedure.Outputs;
import org.hibernate.StoredProcedureResultSetReturn; import org.hibernate.procedure.ResultSetReturn;
import org.hibernate.StoredProcedureReturn; import org.hibernate.procedure.Return;
import org.hibernate.StoredProcedureUpdateCountReturn; import org.hibernate.procedure.UpdateCountReturn;
import org.hibernate.jpa.spi.BaseQueryImpl; import org.hibernate.jpa.spi.BaseQueryImpl;
import org.hibernate.jpa.spi.HibernateEntityManagerImplementor; import org.hibernate.jpa.spi.HibernateEntityManagerImplementor;
@ -49,60 +49,61 @@ import org.hibernate.jpa.spi.HibernateEntityManagerImplementor;
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class StoredProcedureQueryImpl extends BaseQueryImpl implements StoredProcedureQuery { public class StoredProcedureQueryImpl extends BaseQueryImpl implements StoredProcedureQuery {
private final StoredProcedureCall storedProcedureCall; private final Call procedureCall;
private StoredProcedureOutputs storedProcedureOutputs; private Outputs procedureOutputs;
public StoredProcedureQueryImpl(StoredProcedureCall storedProcedureCall, HibernateEntityManagerImplementor entityManager) { public StoredProcedureQueryImpl(Call procedureCall, HibernateEntityManagerImplementor entityManager) {
super( entityManager ); super( entityManager );
this.storedProcedureCall = storedProcedureCall; this.procedureCall = procedureCall;
} }
@Override @Override
protected boolean applyTimeoutHint(int timeout) { protected boolean applyTimeoutHint(int timeout) {
storedProcedureCall.setTimeout( timeout ); procedureCall.setTimeout( timeout );
return true; return true;
} }
@Override @Override
protected boolean applyCacheableHint(boolean isCacheable) { protected boolean applyCacheableHint(boolean isCacheable) {
storedProcedureCall.setCacheable( isCacheable ); procedureCall.setCacheable( isCacheable );
return true; return true;
} }
@Override @Override
protected boolean applyCacheRegionHint(String regionName) { protected boolean applyCacheRegionHint(String regionName) {
storedProcedureCall.setCacheRegion( regionName ); procedureCall.setCacheRegion( regionName );
return true; return true;
} }
@Override @Override
protected boolean applyReadOnlyHint(boolean isReadOnly) { protected boolean applyReadOnlyHint(boolean isReadOnly) {
storedProcedureCall.setReadOnly( isReadOnly ); procedureCall.setReadOnly( isReadOnly );
return true; return true;
} }
@Override @Override
protected boolean applyCacheModeHint(CacheMode cacheMode) { protected boolean applyCacheModeHint(CacheMode cacheMode) {
storedProcedureCall.setCacheMode( cacheMode ); procedureCall.setCacheMode( cacheMode );
return true; return true;
} }
@Override @Override
protected boolean applyFlushModeHint(FlushMode flushMode) { protected boolean applyFlushModeHint(FlushMode flushMode) {
storedProcedureCall.setFlushMode( flushMode ); procedureCall.setFlushMode( flushMode );
return true; return true;
} }
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public StoredProcedureQuery registerStoredProcedureParameter(int position, Class type, ParameterMode mode) { public StoredProcedureQuery registerStoredProcedureParameter(int position, Class type, ParameterMode mode) {
storedProcedureCall.registerStoredProcedureParameter( position, type, mode ); procedureCall.registerParameter( position, type, mode );
return this; return this;
} }
@Override @Override
@SuppressWarnings("unchecked")
public StoredProcedureQuery registerStoredProcedureParameter(String parameterName, Class type, ParameterMode mode) { public StoredProcedureQuery registerStoredProcedureParameter(String parameterName, Class type, ParameterMode mode) {
storedProcedureCall.registerStoredProcedureParameter( parameterName, type, mode ); procedureCall.registerParameter( parameterName, type, mode );
return this; return this;
} }
@ -171,11 +172,11 @@ public class StoredProcedureQueryImpl extends BaseQueryImpl implements StoredPro
// outputs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // outputs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private StoredProcedureOutputs outputs() { private Outputs outputs() {
if ( storedProcedureOutputs == null ) { if ( procedureOutputs == null ) {
storedProcedureOutputs = storedProcedureCall.getOutputs(); procedureOutputs = procedureCall.getOutputs();
} }
return storedProcedureOutputs; return procedureOutputs;
} }
@Override @Override
@ -205,29 +206,29 @@ public class StoredProcedureQueryImpl extends BaseQueryImpl implements StoredPro
@Override @Override
public int getUpdateCount() { public int getUpdateCount() {
final StoredProcedureReturn nextReturn = outputs().getNextReturn(); final Return nextReturn = outputs().getNextReturn();
if ( nextReturn.isResultSet() ) { if ( nextReturn.isResultSet() ) {
return -1; return -1;
} }
return ( (StoredProcedureUpdateCountReturn) nextReturn ).getUpdateCount(); return ( (UpdateCountReturn) nextReturn ).getUpdateCount();
} }
@Override @Override
public List getResultList() { public List getResultList() {
final StoredProcedureReturn nextReturn = outputs().getNextReturn(); final Return nextReturn = outputs().getNextReturn();
if ( ! nextReturn.isResultSet() ) { if ( ! nextReturn.isResultSet() ) {
return null; // todo : what should be thrown/returned here? return null; // todo : what should be thrown/returned here?
} }
return ( (StoredProcedureResultSetReturn) nextReturn ).getResultList(); return ( (ResultSetReturn) nextReturn ).getResultList();
} }
@Override @Override
public Object getSingleResult() { public Object getSingleResult() {
final StoredProcedureReturn nextReturn = outputs().getNextReturn(); final Return nextReturn = outputs().getNextReturn();
if ( ! nextReturn.isResultSet() ) { if ( ! nextReturn.isResultSet() ) {
return null; // todo : what should be thrown/returned here? return null; // todo : what should be thrown/returned here?
} }
return ( (StoredProcedureResultSetReturn) nextReturn ).getSingleResult(); return ( (ResultSetReturn) nextReturn ).getSingleResult();
} }
@Override @Override

View File

@ -83,7 +83,7 @@ import org.hibernate.SQLQuery;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.StaleObjectStateException; import org.hibernate.StaleObjectStateException;
import org.hibernate.StaleStateException; import org.hibernate.StaleStateException;
import org.hibernate.StoredProcedureCall; import org.hibernate.procedure.Call;
import org.hibernate.TransientObjectException; import org.hibernate.TransientObjectException;
import org.hibernate.TypeMismatchException; import org.hibernate.TypeMismatchException;
import org.hibernate.UnresolvableObjectException; import org.hibernate.UnresolvableObjectException;
@ -813,7 +813,7 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage
@Override @Override
public StoredProcedureQuery createStoredProcedureQuery(String procedureName) { public StoredProcedureQuery createStoredProcedureQuery(String procedureName) {
try { try {
StoredProcedureCall call = getSession().createStoredProcedureCall( procedureName ); Call call = getSession().createStoredProcedureCall( procedureName );
return new StoredProcedureQueryImpl( call, this ); return new StoredProcedureQueryImpl( call, this );
} }
catch ( HibernateException he ) { catch ( HibernateException he ) {
@ -824,7 +824,7 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage
@Override @Override
public StoredProcedureQuery createStoredProcedureQuery(String procedureName, Class... resultClasses) { public StoredProcedureQuery createStoredProcedureQuery(String procedureName, Class... resultClasses) {
try { try {
StoredProcedureCall call = getSession().createStoredProcedureCall( procedureName, resultClasses ); Call call = getSession().createStoredProcedureCall( procedureName, resultClasses );
return new StoredProcedureQueryImpl( call, this ); return new StoredProcedureQueryImpl( call, this );
} }
catch ( HibernateException he ) { catch ( HibernateException he ) {