Implementing Procedure Call

This commit is contained in:
Andrea Boriero 2021-09-28 08:15:07 +02:00 committed by Christian Beikov
parent 6931635c4f
commit 95486ce5b1
41 changed files with 2527 additions and 1687 deletions

View File

@ -0,0 +1,63 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.procedure.internal;
import java.sql.CallableStatement;
import java.sql.SQLException;
import java.util.List;
import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.procedure.spi.CallableStatementSupport;
import org.hibernate.procedure.spi.ParameterStrategy;
import org.hibernate.procedure.spi.ProcedureCallImplementor;
import org.hibernate.procedure.spi.ProcedureParameterImplementor;
import org.hibernate.query.spi.ProcedureParameterMetadataImplementor;
import jakarta.persistence.ParameterMode;
public abstract class AbstractStandardCallableStatementSupport implements CallableStatementSupport {
@Override
public void registerParameters(
String procedureName,
ProcedureCallImplementor procedureCall,
CallableStatement statement,
ParameterStrategy parameterStrategy,
ProcedureParameterMetadataImplementor parameterMetadata,
SharedSessionContractImplementor session) {
final List<? extends ProcedureParameterImplementor<?>> registrations = parameterMetadata.getRegistrationsAsList();
try {
for ( int i = 0; i < registrations.size(); i++ ) {
final ProcedureParameterImplementor<?> paramater = registrations.get( i );
if ( paramater.getMode() == ParameterMode.REF_CURSOR ) {
if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED ) {
procedureCall.getSession().getFactory().getServiceRegistry()
.getService( RefCursorSupport.class )
.registerRefCursorParameter( statement, paramater.getName() );
}
else {
procedureCall.getSession().getFactory().getServiceRegistry()
.getService( RefCursorSupport.class )
.registerRefCursorParameter( statement, paramater.getPosition() );
}
}
else {
paramater.prepare( statement, i + 1, procedureCall );
}
}
}
catch (SQLException e) {
throw session.getJdbcServices().getSqlExceptionHelper().convert(
e,
"Error registering CallableStatement parameters",
procedureName
);
}
}
}

View File

@ -7,14 +7,23 @@
package org.hibernate.procedure.internal;
import java.sql.Types;
import jakarta.persistence.ParameterMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.model.domain.AllowableOutputParameterType;
import org.hibernate.metamodel.model.domain.AllowableParameterType;
import org.hibernate.procedure.spi.FunctionReturnImplementor;
import org.hibernate.procedure.spi.NamedCallableQueryMemento;
import org.hibernate.procedure.spi.ProcedureCallImplementor;
import org.hibernate.sql.exec.internal.JdbcCallFunctionReturnImpl;
import org.hibernate.sql.exec.internal.JdbcCallParameterExtractorImpl;
import org.hibernate.sql.exec.internal.JdbcCallRefCursorExtractorImpl;
import org.hibernate.sql.exec.spi.JdbcCallFunctionReturn;
import org.hibernate.type.descriptor.java.BasicJavaDescriptor;
import org.hibernate.type.descriptor.jdbc.JdbcTypeDescriptor;
import org.hibernate.type.spi.TypeConfiguration;
/**
* @author Steve Ebersole
@ -38,30 +47,30 @@ public class FunctionReturnImpl implements FunctionReturnImplementor {
}
// public JdbcCallFunctionReturn toJdbcFunctionReturn(SharedSessionContractImplementor persistenceContext) {
// final AllowableParameterType ormType;
// final JdbcCallRefCursorExtractorImpl refCursorExtractor;
// final JdbcCallParameterExtractorImpl parameterExtractor;
//
// if ( getJdbcTypeCode() == Types.REF_CURSOR ) {
// refCursorExtractor = new JdbcCallRefCursorExtractorImpl( null, 0 );
// ormType = null;
// parameterExtractor = null;
// }
// else {
//
// final TypeConfiguration typeConfiguration = persistenceContext.getFactory().getMetamodel().getTypeConfiguration();
// final SqlTypeDescriptor sqlTypeDescriptor = typeConfiguration.getSqlTypeDescriptorRegistry()
// .getDescriptor( getJdbcTypeCode() );
// final JavaTypeDescriptor javaTypeMapping = sqlTypeDescriptor
// .getJdbcRecommendedJavaTypeMapping( typeConfiguration );
// ormType = typeConfiguration.standardBasicTypeForJavaType( javaTypeMapping.getJavaType() );
// parameterExtractor = new JdbcCallParameterExtractorImpl( procedureCall.getProcedureName(), null, 0, ormType );
// refCursorExtractor = null;
// }
//
// return new JdbcCallFunctionReturnImpl( getJdbcTypeCode(), ormType, parameterExtractor, refCursorExtractor );
// }
public JdbcCallFunctionReturn toJdbcFunctionReturn(SharedSessionContractImplementor persistenceContext) {
final AllowableParameterType ormType;
final JdbcCallRefCursorExtractorImpl refCursorExtractor;
final JdbcCallParameterExtractorImpl parameterExtractor;
if ( getJdbcTypeCode() == Types.REF_CURSOR ) {
refCursorExtractor = new JdbcCallRefCursorExtractorImpl( null, 0 );
ormType = null;
parameterExtractor = null;
}
else {
final TypeConfiguration typeConfiguration = persistenceContext.getFactory().getMetamodel().getTypeConfiguration();
final JdbcTypeDescriptor sqlTypeDescriptor = typeConfiguration.getJdbcTypeDescriptorRegistry()
.getDescriptor( getJdbcTypeCode() );
final BasicJavaDescriptor javaTypeMapping = sqlTypeDescriptor
.getJdbcRecommendedJavaTypeMapping( null, null, typeConfiguration );
ormType = typeConfiguration.standardBasicTypeForJavaType( javaTypeMapping.getJavaType().getClass() );
parameterExtractor = new JdbcCallParameterExtractorImpl( procedureCall.getProcedureName(), null, 0, ormType );
refCursorExtractor = null;
}
return new JdbcCallFunctionReturnImpl( getJdbcTypeCode(), ormType, parameterExtractor, refCursorExtractor );
}
@Override
public int getJdbcTypeCode() {

View File

@ -6,19 +6,22 @@
*/
package org.hibernate.procedure.internal;
import java.sql.CallableStatement;
import java.util.List;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.procedure.spi.CallableStatementSupport;
import org.hibernate.procedure.spi.ParameterStrategy;
import org.hibernate.query.spi.ParameterMetadataImplementor;
import org.hibernate.procedure.spi.ProcedureParameterImplementor;
import org.hibernate.query.spi.ProcedureParameterMetadataImplementor;
import org.hibernate.sql.exec.internal.JdbcCallImpl;
import org.hibernate.sql.exec.spi.JdbcCall;
import jakarta.persistence.ParameterMode;
/**
* @author Steve Ebersole
*/
public class PostgresCallableStatementSupport implements CallableStatementSupport {
public class PostgresCallableStatementSupport extends AbstractStandardCallableStatementSupport {
/**
* Singleton access
*/
@ -28,83 +31,60 @@ public class PostgresCallableStatementSupport implements CallableStatementSuppor
public JdbcCall interpretCall(
String procedureName,
FunctionReturnImpl functionReturn,
ParameterMetadataImplementor parameterMetadata,
ProcedureParameterMetadataImplementor parameterMetadata,
ProcedureParamBindings paramBindings,
SharedSessionContractImplementor session) {
// if there are any parameters, see if the first is REF_CURSOR
final boolean firstParamIsRefCursor = parameterMetadata.getParameterCount() != 0
&& paramBindings..getMode() == ParameterMode.REF_CURSOR;
&& isFirstParameterModeRefCursor( parameterMetadata );
if ( firstParamIsRefCursor ) {
// validate that the parameter strategy is positional (cannot mix, and REF_CURSOR is inherently positional)
if ( parameterStrategy == ParameterStrategy.NAMED ) {
if ( parameterMetadata.hasNamedParameters() ) {
throw new HibernateException( "Cannot mix named parameters and REF_CURSOR parameter on PostgreSQL" );
}
}
final List<? extends ProcedureParameterImplementor<?>> registrations = parameterMetadata.getRegistrationsAsList();
final StringBuilder buffer;
if ( firstParamIsRefCursor ) {
buffer = new StringBuilder().append( "{? = call " );
buffer = new StringBuilder(11 + procedureName.length() + registrations.size() * 2).append( "{?=call " );
}
else {
buffer = new StringBuilder().append( "{call " );
buffer = new StringBuilder(9 + procedureName.length() + registrations.size() * 2).append( "{call " );
}
buffer.append( procedureName ).append( "(" );
String sep = "";
// skip the first registration if it was a REF_CURSOR
final int startIndex = firstParamIsRefCursor ? 1 : 0;
for ( int i = startIndex; i < parameterRegistrations.size(); i++ ) {
final ParameterRegistrationImplementor parameter = parameterRegistrations.get( i );
// any additional REF_CURSOR parameter registrations are an error
if ( parameter.getMode() == ParameterMode.REF_CURSOR ) {
throw new HibernateException( "PostgreSQL supports only one REF_CURSOR parameter, but multiple were registered" );
}
for ( int ignored : parameter.getSqlTypes() ) {
buffer.append( sep ).append( "?" );
sep = ",";
final int startIndex;
if ( firstParamIsRefCursor ) {
startIndex = 1;
}
else {
startIndex = 0;
}
String sep = "";
for ( int i = startIndex; i < registrations.size(); i++ ) {
if ( registrations.get( i ).getMode() == ParameterMode.REF_CURSOR ) {
throw new HibernateException(
"PostgreSQL supports only one REF_CURSOR parameter, but multiple were registered" );
}
buffer.append( sep ).append( "?" );
sep = ",";
}
return buffer.append( ")}" ).toString();
buffer.append( ")}" );
return new JdbcCallImpl.Builder(
buffer.toString(),
parameterMetadata.hasNamedParameters() ?
ParameterStrategy.NAMED :
ParameterStrategy.POSITIONAL
).buildJdbcCall();
}
@Override
public void registerParameters(
String procedureName,
CallableStatement statement,
ParameterStrategy parameterStrategy,
ParameterMetadataImplementor parameterMetadata,
SharedSessionContractImplementor session) {
throw new NotYetImplementedFor6Exception( getClass() );
// // prepare parameters
// int i = 1;
//
// try {
// for ( ParameterRegistrationImplementor parameter : parameterRegistrations ) {
// if ( parameter.getMode() == ParameterMode.REF_CURSOR ) {
// statement.registerOutParameter( i, Types.OTHER );
// i++;
//
// }
// else {
// parameter.prepare( statement, i );
// i += parameter.getSqlTypes().length;
// }
// }
// }
// catch (SQLException e) {
// throw session.getJdbcServices().getSqlExceptionHelper().convert(
// e,
// "Error registering CallableStatement parameters",
// procedureName
// );
// }
private static boolean isFirstParameterModeRefCursor(ProcedureParameterMetadataImplementor parameterMetadata) {
return parameterMetadata.getRegistrationsAsList().get( 0 ).getMode() == ParameterMode.REF_CURSOR;
}
}

View File

@ -7,7 +7,6 @@
package org.hibernate.procedure.internal;
import java.sql.CallableStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@ -16,7 +15,6 @@ import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Stream;
import jakarta.persistence.FlushModeType;
import jakarta.persistence.LockModeType;
@ -44,6 +42,7 @@ import org.hibernate.procedure.NoSuchParameterException;
import org.hibernate.procedure.ParameterStrategyException;
import org.hibernate.procedure.ProcedureCall;
import org.hibernate.procedure.ProcedureOutputs;
import org.hibernate.procedure.spi.CallableStatementSupport;
import org.hibernate.procedure.spi.NamedCallableQueryMemento;
import org.hibernate.procedure.spi.ParameterStrategy;
import org.hibernate.procedure.spi.ProcedureCallImplementor;
@ -112,6 +111,7 @@ public class ProcedureCallImpl<R>
this.synchronizedQuerySpaces = null;
}
/**
* The result Class(es) return form
*
@ -337,7 +337,10 @@ public class ProcedureCallImpl<R>
@Override
@SuppressWarnings("unchecked")
public ProcedureCallImplementor<R> registerStoredProcedureParameter(String parameterName, Class type, ParameterMode mode) {
public ProcedureCallImplementor<R> registerStoredProcedureParameter(
String parameterName,
Class type,
ParameterMode mode) {
getSession().checkOpen( true );
try {
registerParameter( parameterName, type, mode );
@ -352,6 +355,7 @@ public class ProcedureCallImpl<R>
return this;
}
@Override
@SuppressWarnings("unchecked")
public <T> ProcedureParameter<T> registerParameter(int position, Class<T> javaType, ParameterMode mode) {
@ -411,7 +415,7 @@ public class ProcedureCallImpl<R>
@Override
@SuppressWarnings("unchecked")
public List getRegisteredParameters() {
return new ArrayList( getParameterMetadata().getRegistrations() );
return getParameterMetadata().getRegistrationsAsList() ;
}
@Override
@ -438,49 +442,53 @@ public class ProcedureCallImpl<R>
// both: (1) add the `? = ` part and also (2) register a REFCURSOR parameter for DBs (Oracle, PGSQL) that
// need it.
final JdbcCall call = getSession().getJdbcServices().getJdbcEnvironment().getDialect().getCallableStatementSupport().interpretCall(
final CallableStatementSupport callableStatementSupport = getSession().getJdbcServices()
.getJdbcEnvironment()
.getDialect()
.getCallableStatementSupport();
final ProcedureParameterMetadataImpl parameterMetadata = getParameterMetadata();
final JdbcCall call = callableStatementSupport.interpretCall(
procedureName,
functionReturn,
getParameterMetadata(),
parameterMetadata,
paramBindings,
getSession()
);
LOG.debugf( "Preparing procedure call : %s", call );
final CallableStatement statement = (CallableStatement) getSession()
.getJdbcCoordinator()
.getStatementPreparer()
.prepareStatement( call.getSql(), true );
// prepare parameters
getParameterMetadata().visitRegistrations(
new Consumer<QueryParameter<?>>() {
int i = 1;
@Override
public void accept(QueryParameter queryParameter) {
try {
final ProcedureParameterImplementor registration = (ProcedureParameterImplementor) queryParameter;
registration.prepare( statement, i, ProcedureCallImpl.this );
// if ( registration.getMode() == ParameterMode.REF_CURSOR ) {
i++;
// }
// else {
// i += registration.getSqlTypes().length;
// }
}
catch (SQLException e) {
throw getSession().getJdbcServices().getSqlExceptionHelper().convert(
e,
"Error preparing registered callable parameter",
getProcedureName()
);
}
}
}
);
callableStatementSupport.registerParameters( procedureName, this,statement, parameterMetadata.getParameterStrategy(), parameterMetadata, getSession() );
// getParameterMetadata().visitRegistrations(
// new Consumer<QueryParameter<?>>() {
// int i = 1;
//
// @Override
// public void accept(QueryParameter queryParameter) {
// try {
// final ProcedureParameterImplementor registration = (ProcedureParameterImplementor) queryParameter;
// registration.prepare( statement, i, ProcedureCallImpl.this );
//// if ( registration.getMode() == ParameterMode.REF_CURSOR ) {
// i++;
//// }
//// else {
//// i += registration.getHibernateType().getSqlTypes().length;
//// }
// }
// catch (SQLException e) {
// throw getSession().getJdbcServices().getSqlExceptionHelper().convert(
// e,
// "Error preparing registered callable parameter",
// getProcedureName()
// );
// }
// }
// }
// );
return new ProcedureOutputsImpl( this, statement );
}
@ -650,7 +658,7 @@ public class ProcedureCallImpl<R>
@Override
protected int doExecuteUpdate() {
if ( ! getSession().isTransactionInProgress() ) {
if ( !getSession().isTransactionInProgress() ) {
throw new TransactionRequiredException( "jakarta.persistence.Query.executeUpdate requires active transaction" );
}
@ -731,7 +739,7 @@ public class ProcedureCallImpl<R>
}
try {
final Output rtn = outputs().getCurrent();
if ( ! ResultSetOutput.class.isInstance( rtn ) ) {
if ( !ResultSetOutput.class.isInstance( rtn ) ) {
throw new IllegalStateException( "Current CallableStatement ou was not a ResultSet, but getResultList was called" );
}
@ -765,7 +773,7 @@ public class ProcedureCallImpl<R>
}
try {
final Output rtn = outputs().getCurrent();
if ( !(rtn instanceof ResultSetOutput) ) {
if ( !( rtn instanceof ResultSetOutput ) ) {
throw new IllegalStateException( "Current CallableStatement ou was not a ResultSet, but getResultList was called" );
}
@ -841,7 +849,7 @@ public class ProcedureCallImpl<R>
@Override
public ProcedureCallImplementor<R> setLockMode(LockModeType lockMode) {
throw new IllegalStateException( "jakarta.persistence.Query.setLockMode not valid on jakarta.persistence.StoredProcedureQuery" );
throw new IllegalStateException("jakarta.persistence.Query.setLockMode not valid on jakarta.persistence.StoredProcedureQuery" );
}
@Override
@ -886,7 +894,10 @@ public class ProcedureCallImpl<R>
}
@Override
public <P> ProcedureCallImplementor<R> setParameter(QueryParameter<P> parameter, P value, AllowableParameterType type) {
public <P> ProcedureCallImplementor<R> setParameter(
QueryParameter<P> parameter,
P value,
AllowableParameterType type) {
return (ProcedureCallImplementor<R>) super.setParameter( parameter, value, type );
}
@ -917,7 +928,10 @@ public class ProcedureCallImpl<R>
// }
@Override
public <P> ProcedureCallImplementor<R> setParameter(QueryParameter<P> parameter, P value, TemporalType temporalPrecision) {
public <P> ProcedureCallImplementor<R> setParameter(
QueryParameter<P> parameter,
P value,
TemporalType temporalPrecision) {
return (ProcedureCallImplementor<R>) super.setParameter( parameter, value, temporalPrecision );
}
@ -932,7 +946,10 @@ public class ProcedureCallImpl<R>
}
@Override
public ProcedureCallImplementor<R> setParameter(Parameter parameter, Calendar value, TemporalType temporalPrecision) {
public ProcedureCallImplementor<R> setParameter(
Parameter parameter,
Calendar value,
TemporalType temporalPrecision) {
//noinspection unchecked
return (ProcedureCallImplementor<R>) super.setParameter( parameter, value, temporalPrecision );
}

View File

@ -15,12 +15,15 @@ import org.hibernate.metamodel.model.domain.AllowableOutputParameterType;
import org.hibernate.metamodel.model.domain.AllowableParameterType;
import org.hibernate.procedure.ParameterMisuseException;
import org.hibernate.procedure.ProcedureOutputs;
import org.hibernate.procedure.spi.ParameterStrategy;
import org.hibernate.procedure.spi.ProcedureParameterImplementor;
import org.hibernate.query.procedure.ProcedureParameter;
import org.hibernate.result.Output;
import org.hibernate.result.internal.OutputsImpl;
import org.hibernate.sql.exec.ExecutionException;
import jakarta.persistence.ParameterMode;
/**
* Implementation of ProcedureResult. Defines centralized access to all of the results of a procedure call.
*
@ -44,21 +47,48 @@ public class ProcedureOutputsImpl extends OutputsImpl implements ProcedureOutput
@Override
public <T> T getOutputParameterValue(ProcedureParameter<T> parameter) {
final AllowableParameterType<T> hibernateType = parameter.getHibernateType();
if ( hibernateType instanceof AllowableOutputParameterType<?> ) {
try {
//noinspection unchecked
return (T) ( (AllowableOutputParameterType<?>) hibernateType ).extract(
callableStatement,
parameter.getPosition(),
procedureCall.getSession()
);
if ( parameter.getMode() == ParameterMode.IN ) {
throw new ParameterMisuseException( "IN parameter not valid for output extraction" );
}
try {
if ( parameter.getMode() == ParameterMode.REF_CURSOR ) {
if ( parameter.getPosition() != null ) {
return (T) callableStatement.getObject( parameter.getPosition() );
}
else {
return (T) callableStatement.getObject( parameter.getName() );
}
}
catch (SQLException e) {
throw new ExecutionException( "Error extracting procedure output parameter value [" + parameter + "]", e );
else if ( hibernateType instanceof AllowableOutputParameterType<?> ) {
//noinspection unchecked
if ( parameter.getPosition() != null ) {
return (T) ( (AllowableOutputParameterType<?>) hibernateType ).extract(
callableStatement,
parameter.getPosition(),
procedureCall.getSession()
);
}
else {
return (T) ( (AllowableOutputParameterType<?>) hibernateType ).extract(
callableStatement,
parameter.getName(),
procedureCall.getSession()
);
}
}
else {
throw new ParameterMisuseException( "Parameter type cannot extract procedure output parameters" );
}
}
else {
throw new ParameterMisuseException( "Parameter type cannot extract procedure output parameters" );
catch (SQLException e) {
throw new ExecutionException(
"Error extracting procedure output parameter value ["
+ parameter.getPosition() != null ?
String.valueOf( parameter.getPosition() ) :
parameter.getName() + "]",
e
);
}
}

View File

@ -8,11 +8,15 @@ package org.hibernate.procedure.internal;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.cache.spi.QueryKey;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.procedure.spi.ProcedureParameterBindingImplementor;
import org.hibernate.procedure.spi.ProcedureParameterImplementor;
import org.hibernate.query.procedure.ProcedureParameterBinding;
import org.hibernate.query.spi.QueryParameterBinding;
import org.hibernate.query.spi.QueryParameterBindingTypeResolver;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.query.spi.QueryParameterImplementor;
@ -107,4 +111,13 @@ public class ProcedureParamBindings implements QueryParameterBindings {
return false;
}
@Override
public void visitBindings(BiConsumer<QueryParameterImplementor<?>, QueryParameterBinding<?>> action) {
bindingMap.forEach( action );
}
@Override
public QueryKey.ParameterBindingsMemento generateQueryKeyMemento(SharedSessionContractImplementor persistenceContext) {
return NO_PARAMETER_BINDING_MEMENTO;
}
}

View File

@ -11,19 +11,31 @@ import java.sql.SQLException;
import java.util.Objects;
import jakarta.persistence.ParameterMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.metamodel.model.domain.AllowableParameterType;
import org.hibernate.metamodel.model.domain.AllowableTemporalParameterType;
import org.hibernate.procedure.spi.NamedCallableQueryMemento;
import org.hibernate.procedure.spi.ParameterStrategy;
import org.hibernate.procedure.spi.ProcedureCallImplementor;
import org.hibernate.procedure.spi.ProcedureParameterImplementor;
import org.hibernate.query.AbstractQueryParameter;
import org.hibernate.query.spi.QueryParameterBinding;
import org.hibernate.type.ProcedureParameterNamedBinder;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.jdbc.JdbcTypeDescriptor;
import org.hibernate.type.spi.TypeConfiguration;
import org.jboss.logging.Logger;
/**
* @author Steve Ebersole
*/
public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> implements ProcedureParameterImplementor<T> {
private static final Logger log = Logger.getLogger( ProcedureParameterImpl.class );
private final String name;
private final Integer position;
private Integer position;
private final ParameterMode mode;
@ -122,23 +134,28 @@ public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> impleme
CallableStatement statement,
int startIndex,
ProcedureCallImplementor<?> procedureCall) throws SQLException {
throw new NotYetImplementedFor6Exception( getClass() );
// final QueryParameterBinding<?> binding = procedureCall.getParameterBindings().getBinding( this );
//
// // initially set up the Type we will use for binding as the explicit type.
// AllowableParameterType typeToUse = getHibernateType();
//
// // however, for Calendar binding with an explicit TemporalType we may need to adjust this...
// if ( binding != null && binding.getExplicitTemporalPrecision() != null ) {
// typeToUse = ( (AllowableTemporalParameterType) typeToUse ).resolveTemporalPrecision(
// binding.getExplicitTemporalPrecision(),
// procedureCall.getSession().getFactory().getTypeConfiguration()
// );
// }
//
// this.startIndex = startIndex;
// if ( mode == ParameterMode.IN || mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) {
// if ( mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) {
final QueryParameterBinding<?> binding = procedureCall.getParameterBindings().getBinding( this );
final AllowableParameterType typeToUse;
// for Calendar binding with an explicit TemporalType we may need to adjust this...
final TypeConfiguration typeConfiguration = procedureCall.getSession().getFactory().getTypeConfiguration();
if ( binding != null && binding.getExplicitTemporalPrecision() != null ) {
typeToUse = ( (AllowableTemporalParameterType) getHibernateType() ).resolveTemporalPrecision(
binding.getExplicitTemporalPrecision(),
typeConfiguration
);
}
else {
//set up the Type we will use for binding as the explicit type.
typeToUse = getHibernateType();
}
final JdbcTypeDescriptor recommendedJdbcType = typeToUse.getExpressableJavaTypeDescriptor()
.getRecommendedJdbcType( typeConfiguration.getCurrentBaseSqlTypeIndicators() );
final ValueBinder binder = recommendedJdbcType.getBinder( typeToUse.getExpressableJavaTypeDescriptor() );
if ( mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) {
// if ( sqlTypesToUse.length > 1 ) {
// // there is more than one column involved; see if the Hibernate Type can handle
// // multi-param extraction...
@ -152,88 +169,100 @@ public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> impleme
// );
// }
// }
// // TODO: sqlTypesToUse.length > 1 does not seem to have a working use case (HHH-10769).
// // The idea is that an embeddable/custom type can have more than one column values
// // that correspond with embeddable/custom attribute value. This does not seem to
// // be working yet. For now, if sqlTypesToUse.length > 1, then register
// // the out parameters by position (since we only have one name).
// // This will cause a failure if there are other parameters bound by
// // name and the dialect does not support "mixed" named/positional parameters;
// // e.g., Oracle.
// if ( sqlTypesToUse.length == 1 &&
// procedureCall.getParameterStrategy() == ParameterStrategy.NAMED &&
// canDoNameParameterBinding( typeToUse ) ) {
// statement.registerOutParameter( getName(), sqlTypesToUse[0] );
// }
// else {
// TODO: sqlTypesToUse.length > 1 does not seem to have a working use case (HHH-10769).
// The idea is that an embeddable/custom type can have more than one column values
// that correspond with embeddable/custom attribute value. This does not seem to
// be working yet. For now, if sqlTypesToUse.length > 1, then register
// the out parameters by position (since we only have one name).
// This will cause a failure if there are other parameters bound by
// name and the dialect does not support "mixed" named/positional parameters;
// e.g., Oracle.
if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED &&
canDoNameParameterBinding( typeToUse, procedureCall ) ) {
statement.registerOutParameter( getName(), recommendedJdbcType.getJdbcTypeCode() );
}
else {
// for ( int i = 0; i < sqlTypesToUse.length; i++ ) {
// statement.registerOutParameter( startIndex + i, sqlTypesToUse[i] );
if ( position == null ) {
position = startIndex;
}
statement.registerOutParameter( startIndex, recommendedJdbcType.getJdbcTypeCode() );
// }
// }
// }
//
// if ( mode == ParameterMode.INOUT || mode == ParameterMode.IN ) {
// if ( binding == null || binding.getBindValue() == null ) {
// // the user did not binding a value to the parameter being processed. This is the condition
// // defined by `passNulls` and that value controls what happens here. If `passNulls` is
// // {@code true} we will binding the NULL value into the statement; if `passNulls` is
// // {@code false} we will not.
// //
// // Unfortunately there is not a way to reliably know through JDBC metadata whether a procedure
// // parameter defines a default value. Deferring to that information would be the best option
// if ( isPassNullsEnabled() ) {
// log.debugf(
// "Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to true; binding NULL",
// procedureCall.getProcedureName(),
// this
// );
// if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && canDoNameParameterBinding( typeToUse ) ) {
// ((ProcedureParameterNamedBinder) typeToUse).nullSafeSet(
// statement,
// null,
// this.getName(),
// procedureCall.getSession()
// );
// }
// else {
// typeToUse.nullSafeSet( statement, null, startIndex, procedureCall.getSession() );
// }
// }
// else {
// log.debugf(
// "Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to false; assuming procedure defines default value",
// procedureCall.getProcedureName(),
// this
// );
// }
// }
// else {
// if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && canDoNameParameterBinding( typeToUse ) ) {
// ((ProcedureParameterNamedBinder) typeToUse).nullSafeSet(
// statement,
// binding.getBindValue(),
// this.getName(),
// procedureCall.getSession()
// );
// }
// else {
// typeToUse.nullSafeSet( statement, binding.getBindValue(), startIndex, procedureCall.getSession() );
// }
// }
// }
// }
// else {
// // we have a REF_CURSOR type param
// if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED ) {
// procedureCall.getSession().getFactory().getServiceRegistry()
// .getService( RefCursorSupport.class )
// .registerRefCursorParameter( statement, getName() );
// }
// else {
// procedureCall.getSession().getFactory().getServiceRegistry()
// .getService( RefCursorSupport.class )
// .registerRefCursorParameter( statement, startIndex );
// }
// }
}
}
if ( mode == ParameterMode.INOUT || mode == ParameterMode.IN ) {
if ( binding == null || binding.getBindValue() == null ) {
// the user did not binding a value to the parameter being processed. This is the condition
// defined by `passNulls` and that value controls what happens here. If `passNulls` is
// {@code true} we will binding the NULL value into the statement; if `passNulls` is
// {@code false} we will not.
//
// Unfortunately there is not a way to reliably know through JDBC metadata whether a procedure
// parameter defines a default value. Deferring to that information would be the best option
if ( ( binding != null && binding.isBound() ) || isPassNullsEnabled() ) {
log.debugf(
"Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to true; binding NULL",
procedureCall.getProcedureName(),
this
);
if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED
&& canDoNameParameterBinding( typeToUse, procedureCall ) ) {
( (ProcedureParameterNamedBinder) typeToUse ).nullSafeSet(
statement,
null,
this.getName(),
procedureCall.getSession()
);
}
else {
if ( position == null ) {
position = startIndex;
}
binder.bind( statement, null, position, procedureCall.getSession() );
}
}
else {
throw new IllegalArgumentException(
"The parameter " +
( name != null
? "named [" + name + "]"
: "at position [" + position + "]" )
+ " was not set! You need to call the setParameter method." );
}
}
else {
if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED
&& canDoNameParameterBinding( typeToUse, procedureCall ) ) {
( (ProcedureParameterNamedBinder) typeToUse ).nullSafeSet(
statement,
binding.getBindValue(),
this.getName(),
procedureCall.getSession()
);
}
else {
if ( position == null ) {
position = startIndex;
}
binder.bind( statement, binding.getBindValue(), position, procedureCall.getSession() );
}
}
}
}
private boolean canDoNameParameterBinding(
AllowableParameterType hibernateType,
ProcedureCallImplementor<?> procedureCall) {
final ExtractedDatabaseMetaData databaseMetaData = procedureCall.getSession()
.getJdbcCoordinator()
.getJdbcSessionOwner()
.getJdbcSessionContext()
.getServiceRegistry().getService( JdbcEnvironment.class )
.getExtractedDatabaseMetaData();
return
databaseMetaData.supportsNamedParameters()
&& ProcedureParameterNamedBinder.class.isInstance( hibernateType )
&& ( (ProcedureParameterNamedBinder) hibernateType ).canDoSetting();
}
}

View File

@ -13,6 +13,7 @@ import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import jakarta.persistence.Parameter;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
@ -21,7 +22,7 @@ import org.hibernate.procedure.spi.ParameterStrategy;
import org.hibernate.query.QueryParameter;
import org.hibernate.query.procedure.ProcedureParameter;
import org.hibernate.procedure.spi.ProcedureParameterImplementor;
import org.hibernate.query.spi.ParameterMetadataImplementor;
import org.hibernate.query.spi.ProcedureParameterMetadataImplementor;
import org.hibernate.query.spi.QueryParameterImplementor;
/**
@ -30,7 +31,7 @@ import org.hibernate.query.spi.QueryParameterImplementor;
*
* @author Steve Ebersole
*/
public class ProcedureParameterMetadataImpl implements ParameterMetadataImplementor {
public class ProcedureParameterMetadataImpl implements ProcedureParameterMetadataImplementor {
private ParameterStrategy parameterStrategy = ParameterStrategy.UNKNOWN;
private List<ProcedureParameterImplementor<?>> parameters;
@ -103,12 +104,18 @@ public class ProcedureParameterMetadataImpl implements ParameterMetadataImplemen
@Override
public int getParameterCount() {
if ( parameters == null ) {
return 0;
}
return parameters.size();
}
@Override
@SuppressWarnings("SuspiciousMethodCalls")
public boolean containsReference(QueryParameter parameter) {
if ( parameters == null ) {
return false;
}
return parameters.contains( parameter );
}
@ -157,7 +164,11 @@ public class ProcedureParameterMetadataImpl implements ParameterMetadataImplemen
@Override
public ProcedureParameterImplementor<?> resolve(Parameter param) {
if ( param instanceof ProcedureParameterImplementor ) {
return (ProcedureParameterImplementor) param;
for ( ProcedureParameterImplementor p : parameters ) {
if ( p == param ) {
return p;
}
}
}
return null;
@ -166,13 +177,24 @@ public class ProcedureParameterMetadataImpl implements ParameterMetadataImplemen
@Override
public Set<? extends QueryParameter<?>> getRegistrations() {
//noinspection unchecked
return (Set) parameters;
return parameters.stream().collect( Collectors.toSet());
}
@Override
public List<? extends ProcedureParameterImplementor<?>> getRegistrationsAsList() {
//noinspection unchecked
if ( parameters == null ) {
return Collections.EMPTY_LIST;
}
return parameters;
}
@Override
public void visitRegistrations(Consumer<? extends QueryParameter<?>> action) {
//noinspection unchecked
parameters.forEach( (Consumer) action );
if ( parameters != null ) {
parameters.forEach( (Consumer) action );
}
}
@Override

View File

@ -6,27 +6,25 @@
*/
package org.hibernate.procedure.internal;
import java.sql.CallableStatement;
import java.util.function.Consumer;
import jakarta.persistence.ParameterMode;
import java.util.List;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.QueryException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.procedure.spi.CallableStatementSupport;
import org.hibernate.procedure.spi.ParameterStrategy;
import org.hibernate.procedure.spi.ProcedureParameterImplementor;
import org.hibernate.query.spi.ParameterMetadataImplementor;
import org.hibernate.query.spi.QueryParameterImplementor;
import org.hibernate.query.spi.ProcedureParameterMetadataImplementor;
import org.hibernate.sql.exec.internal.JdbcCallImpl;
import org.hibernate.sql.exec.spi.JdbcCall;
import jakarta.persistence.ParameterMode;
/**
* Standard implementation of CallableStatementSupport
*
* @author Steve Ebersole
*/
public class StandardCallableStatementSupport implements CallableStatementSupport {
public class StandardCallableStatementSupport extends AbstractStandardCallableStatementSupport {
/**
* Singleton access - without REF_CURSOR support
*/
@ -47,47 +45,36 @@ public class StandardCallableStatementSupport implements CallableStatementSuppor
public JdbcCall interpretCall(
String procedureName,
FunctionReturnImpl functionReturn,
ParameterMetadataImplementor parameterMetadata,
ProcedureParameterMetadataImplementor parameterMetadata,
ProcedureParamBindings paramBindings,
SharedSessionContractImplementor session) {
final StringBuilder buffer = new StringBuilder().append( "{call " )
final List<? extends ProcedureParameterImplementor<?>> registrations = parameterMetadata.getRegistrationsAsList();
final StringBuilder buffer = new StringBuilder(9 + procedureName.length() + registrations.size() * 2).append( "{call " )
.append( procedureName )
.append( "(" );
parameterMetadata.visitParameters(
new Consumer<QueryParameterImplementor<?>>() {
String sep = "";
String sep = "";
for ( int i = 0; i < registrations.size(); i++ ) {
if ( registrations.get( i ).getMode() == ParameterMode.REF_CURSOR ) {
verifyRefCursorSupport( session.getJdbcServices().getJdbcEnvironment().getDialect() );
buffer.append( sep ).append( "?" );
sep = ",";
}
else {
buffer.append( sep ).append( "?" );
sep = ",";
}
}
@Override
public void accept(QueryParameterImplementor<?> param) {
if ( param == null ) {
throw new QueryException( "Parameter registrations had gaps" );
}
buffer.append( ")}" );
final ProcedureParameterImplementor parameter = (ProcedureParameterImplementor) param;
return new JdbcCallImpl.Builder(
buffer.toString(),
parameterMetadata.hasNamedParameters() ?
ParameterStrategy.NAMED :
ParameterStrategy.POSITIONAL
).buildJdbcCall();
if ( parameter.getMode() == ParameterMode.REF_CURSOR ) {
verifyRefCursorSupport( session.getJdbcServices().getJdbcEnvironment().getDialect() );
buffer.append( sep ).append( "?" );
sep = ",";
}
else {
throw new NotYetImplementedFor6Exception( getClass() );
// parameter.getHibernateType().visitJdbcTypes(
// sqlExpressableType -> {
// buffer.append( sep ).append( "?" );
// sep = ",";
// },
// Clause.IRRELEVANT,
// session.getFactory().getTypeConfiguration()
// );
}
}
}
);
throw new NotYetImplementedFor6Exception( getClass() );
// return buffer.append( ")}" ).toString();
}
private void verifyRefCursorSupport(Dialect dialect) {
@ -95,38 +82,4 @@ public class StandardCallableStatementSupport implements CallableStatementSuppor
throw new QueryException( "Dialect [" + dialect.getClass().getName() + "] not known to support REF_CURSOR parameters" );
}
}
@Override
public void registerParameters(
String procedureName,
CallableStatement statement,
ParameterStrategy parameterStrategy,
ParameterMetadataImplementor parameterMetadata,
SharedSessionContractImplementor session) {
throw new NotYetImplementedFor6Exception( getClass() );
// final AtomicInteger count = new AtomicInteger( 1 );
//
// try {
// parameterMetadata.visitParameters(
// param -> {
// final ProcedureParameterImplementor parameter = (ProcedureParameterImplementor) param;
// parameter.prepare( statement, count.get() );
// if ( parameter.getMode() == ParameterMode.REF_CURSOR ) {
// i++;
// }
// else {
// i += parameter.getSqlTypes().length;
// }
// }
// );
// }
// catch (SQLException e) {
// throw session.getJdbcServices().getSqlExceptionHelper().convert(
// e,
// "Error registering CallableStatement parameters",
// procedureName
// );
// }
}
}

View File

@ -11,7 +11,7 @@ import java.sql.CallableStatement;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.procedure.internal.FunctionReturnImpl;
import org.hibernate.procedure.internal.ProcedureParamBindings;
import org.hibernate.query.spi.ParameterMetadataImplementor;
import org.hibernate.query.spi.ProcedureParameterMetadataImplementor;
import org.hibernate.sql.exec.spi.JdbcCall;
/**
@ -21,14 +21,15 @@ public interface CallableStatementSupport {
JdbcCall interpretCall(
String procedureName,
FunctionReturnImpl functionReturn,
ParameterMetadataImplementor parameterMetadata,
ProcedureParameterMetadataImplementor parameterMetadata,
ProcedureParamBindings paramBindings,
SharedSessionContractImplementor session);
void registerParameters(
String procedureName,
ProcedureCallImplementor procedureCall,
CallableStatement statement,
ParameterStrategy parameterStrategy,
ParameterMetadataImplementor parameterMetadata,
ProcedureParameterMetadataImplementor parameterMetadata,
SharedSessionContractImplementor session);
}

View File

@ -0,0 +1,16 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.query.spi;
import java.util.List;
import org.hibernate.procedure.spi.ProcedureParameterImplementor;
public interface ProcedureParameterMetadataImplementor extends ParameterMetadataImplementor {
List<? extends ProcedureParameterImplementor<?>> getRegistrationsAsList();
}

View File

@ -9,7 +9,6 @@ package org.hibernate.query.spi;
import java.util.function.BiConsumer;
import org.hibernate.Incubating;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.cache.spi.QueryKey;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.query.QueryParameter;
@ -83,13 +82,9 @@ public interface QueryParameterBindings {
* in creating a {@link QueryKey}
* @param persistenceContext
*/
default QueryKey.ParameterBindingsMemento generateQueryKeyMemento(SharedSessionContractImplementor persistenceContext) {
throw new NotYetImplementedFor6Exception( );
}
QueryKey.ParameterBindingsMemento generateQueryKeyMemento(SharedSessionContractImplementor persistenceContext);
default void visitBindings(BiConsumer<QueryParameterImplementor<?>, QueryParameterBinding<?>> action) {
throw new NotYetImplementedFor6Exception( getClass() );
}
void visitBindings(BiConsumer<QueryParameterImplementor<?>, QueryParameterBinding<?>> action);
QueryKey.ParameterBindingsMemento NO_PARAMETER_BINDING_MEMENTO = new QueryKey.ParameterBindingsMemento(){
};

View File

@ -6,19 +6,47 @@
*/
package org.hibernate.result.internal;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import jakarta.persistence.ParameterMode;
import org.hibernate.JDBCException;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.CoreLogging;
import org.hibernate.result.NoMoreReturnsException;
import org.hibernate.procedure.internal.ProcedureCallImpl;
import org.hibernate.procedure.internal.ScalarDomainResultBuilder;
import org.hibernate.query.procedure.ProcedureParameter;
import org.hibernate.query.results.ResultSetMapping;
import org.hibernate.query.results.ResultSetMappingImpl;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryOptionsAdapter;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.result.Output;
import org.hibernate.result.Outputs;
import org.hibernate.result.spi.ResultContext;
import org.hibernate.sql.exec.spi.Callback;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.results.NoMoreOutputsException;
import org.hibernate.sql.results.internal.ResultsHelper;
import org.hibernate.sql.results.internal.RowProcessingStateStandardImpl;
import org.hibernate.sql.results.internal.RowTransformerPassThruImpl;
import org.hibernate.sql.results.jdbc.internal.DirectResultSetAccess;
import org.hibernate.sql.results.jdbc.internal.JdbcValuesResultSetImpl;
import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl;
import org.hibernate.sql.results.jdbc.spi.JdbcValues;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions;
import org.hibernate.sql.results.spi.RowReader;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.descriptor.java.spi.JavaTypeDescriptorRegistry;
import org.jboss.logging.Logger;
@ -65,12 +93,11 @@ public class OutputsImpl implements Outputs {
}
protected JDBCException convert(SQLException e, String message) {
// return context.getSession().getJdbcServices().getSqlExceptionHelper().convert(
// e,
// message,
// context.getSql()
// );
throw new NotYetImplementedFor6Exception( getClass() );
return context.getSession().getJdbcServices().getSqlExceptionHelper().convert(
e,
message,
jdbcStatement.toString()
);
}
@Override
@ -122,13 +149,142 @@ public class OutputsImpl implements Outputs {
}
protected List extractResults(ResultSet resultSet) {
throw new NotYetImplementedFor6Exception( getClass() );
// try {
// return loader.processResultSet( resultSet );
// }
final DirectResultSetAccess resultSetAccess = new DirectResultSetAccess(
context.getSession(),
jdbcStatement,
resultSet
);
ResultSetMapping resultSetMapping = new ResultSetMappingImpl( null );
final ProcedureCallImpl procedureCall = (ProcedureCallImpl) context;
final JavaTypeDescriptorRegistry javaTypeDescriptorRegistry = context.getSession()
.getTypeConfiguration()
.getJavaTypeDescriptorRegistry();
procedureCall.getParameterBindings().visitBindings(
(parameterImplementor, queryParameterBinding) -> {
ProcedureParameter parameter = (ProcedureParameter) parameterImplementor;
if ( parameter.getMode() == ParameterMode.IN || parameter.getMode() == ParameterMode.INOUT ) {
final JavaTypeDescriptor<?> basicType = javaTypeDescriptorRegistry.getDescriptor(
parameterImplementor.getParameterType() );
if ( basicType != null ) {
resultSetMapping.addResultBuilder( new ScalarDomainResultBuilder<>( basicType ) );
}
else {
throw new NotYetImplementedFor6Exception( getClass() );
}
}
}
);
final ExecutionContext executionContext = new ExecutionContext() {
@Override
public SharedSessionContractImplementor getSession() {
return OutputsImpl.this.context.getSession();
}
@Override
public QueryOptions getQueryOptions() {
return new QueryOptionsAdapter() {
@Override
public Boolean isReadOnly() {
return false;
}
};
}
@Override
public String getQueryIdentifier(String sql) {
return sql;
}
@Override
public QueryParameterBindings getQueryParameterBindings() {
return QueryParameterBindings.NO_PARAM_BINDINGS;
}
@Override
public Callback getCallback() {
throw new UnsupportedOperationException( "Follow-on locking not supported yet" );
}
};
final JdbcValues jdbcValues = new JdbcValuesResultSetImpl(
resultSetAccess,
null,
null,
this.context.getQueryOptions(),
resultSetMapping.resolve( resultSetAccess, getSessionFactory() ),
executionContext
);
final RowReader<Object[]> rowReader = (RowReader<Object[]>) ResultsHelper.createRowReader(
executionContext,
null,
RowTransformerPassThruImpl.INSTANCE,
jdbcValues
);
/*
* Processing options effectively are only used for entity loading. Here we don't need these values.
*/
final JdbcValuesSourceProcessingOptions processingOptions = new JdbcValuesSourceProcessingOptions() {
@Override
public Object getEffectiveOptionalObject() {
return null;
}
@Override
public String getEffectiveOptionalEntityName() {
return null;
}
@Override
public Serializable getEffectiveOptionalId() {
return null;
}
@Override
public boolean shouldReturnProxies() {
return true;
}
};
final JdbcValuesSourceProcessingStateStandardImpl jdbcValuesSourceProcessingState =
new JdbcValuesSourceProcessingStateStandardImpl(
executionContext,
processingOptions,
executionContext::registerLoadingEntityEntry
);
try {
final RowProcessingStateStandardImpl rowProcessingState = new RowProcessingStateStandardImpl(
jdbcValuesSourceProcessingState,
executionContext,
rowReader,
jdbcValues
);
final List results = new ArrayList<>();
while ( rowProcessingState.next() ) {
results.add( rowReader.readRow( rowProcessingState, processingOptions ) );
rowProcessingState.finishRowProcessing();
}
return results;
}
// catch (SQLException e) {
// throw convert( e, "Error extracting results from CallableStatement" );
// throw context.getSession().getExceptionConverter().convert( e, "Error processing return rows" );
// }
finally {
rowReader.finishUp( jdbcValuesSourceProcessingState );
jdbcValuesSourceProcessingState.finishUp();
jdbcValues.finishUp( this.context.getSession() );
}
}
private SessionFactoryImplementor getSessionFactory() {
return context.getSession().getFactory();
}
/**
@ -184,7 +340,7 @@ public class OutputsImpl implements Outputs {
return buildExtendedReturn();
}
throw new NoMoreReturnsException();
throw new NoMoreOutputsException();
}
// hooks for stored procedure (out param) processing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,34 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.exec.internal;
import jakarta.persistence.ParameterMode;
import org.hibernate.metamodel.model.domain.AllowableParameterType;
import org.hibernate.sql.exec.spi.JdbcCallFunctionReturn;
/**
* @author Steve Ebersole
*/
public class JdbcCallFunctionReturnImpl extends JdbcCallParameterRegistrationImpl implements JdbcCallFunctionReturn {
public JdbcCallFunctionReturnImpl(
int jdbcTypeCode,
AllowableParameterType ormType,
JdbcCallParameterExtractorImpl parameterExtractor,
JdbcCallRefCursorExtractorImpl refCursorExtractor) {
super(
null,
0,
ParameterMode.OUT,
jdbcTypeCode,
ormType,
null,
parameterExtractor,
refCursorExtractor
);
}
}

View File

@ -0,0 +1,196 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.exec.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.hibernate.HibernateException;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.internal.FilterJdbcParameter;
import org.hibernate.procedure.spi.ParameterStrategy;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.sql.exec.spi.JdbcCall;
import org.hibernate.sql.exec.spi.JdbcCallFunctionReturn;
import org.hibernate.sql.exec.spi.JdbcCallParameterExtractor;
import org.hibernate.sql.exec.spi.JdbcCallParameterRegistration;
import org.hibernate.sql.exec.spi.JdbcCallRefCursorExtractor;
import org.hibernate.sql.exec.spi.JdbcParameterBinder;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer;
/**
* Models the actual call, allowing iterative building of the parts.
*
* @author Steve Ebersole
*/
public class JdbcCallImpl implements JdbcCall {
private final String callableName;
private final JdbcCallFunctionReturn functionReturn;
private final List<JdbcCallParameterRegistration> parameterRegistrations;
private final List<JdbcParameterBinder> parameterBinders;
private final List<JdbcCallParameterExtractor> parameterExtractors;
private final List<JdbcCallRefCursorExtractor> refCursorExtractors;
public JdbcCallImpl(Builder builder) {
this.callableName = builder.callableName;
this.functionReturn = builder.functionReturn;
this.parameterRegistrations = builder.parameterRegistrations == null
? Collections.emptyList()
: Collections.unmodifiableList( builder.parameterRegistrations );
this.parameterBinders = builder.parameterBinders == null
? Collections.emptyList()
: Collections.unmodifiableList( builder.parameterBinders );
this.parameterExtractors = builder.parameterExtractors == null
? Collections.emptyList()
: Collections.unmodifiableList( builder.parameterExtractors );
this.refCursorExtractors = builder.refCursorExtractors == null
? Collections.emptyList()
: Collections.unmodifiableList( builder.refCursorExtractors );
}
@Override
public String getSql() {
return callableName;
}
@Override
public JdbcCallFunctionReturn getFunctionReturn() {
return functionReturn;
}
@Override
public List<JdbcCallParameterRegistration> getParameterRegistrations() {
return parameterRegistrations == null ? Collections.emptyList() : parameterRegistrations;
}
@Override
public List<JdbcParameterBinder> getParameterBinders() {
return parameterBinders == null ? Collections.emptyList() : parameterBinders;
}
@Override
public Set<String> getAffectedTableNames() {
throw new NotYetImplementedFor6Exception();
}
@Override
public Set<FilterJdbcParameter> getFilterJdbcParameters() {
return null;
}
@Override
public boolean dependsOnParameterBindings() {
return false;
}
@Override
public boolean isCompatibleWith(
JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions) {
return true;
}
@Override
public List<JdbcCallParameterExtractor> getParameterExtractors() {
return parameterExtractors == null ? Collections.emptyList() : parameterExtractors;
}
@Override
public List<JdbcCallRefCursorExtractor> getCallRefCursorExtractors() {
return refCursorExtractors == null ? Collections.emptyList() : refCursorExtractors;
}
@Override
public JdbcValuesMappingProducer getJdbcValuesMappingProducer() {
return null;
}
public static class Builder {
private final String callableName;
private final ParameterStrategy parameterStrategy;
private JdbcCallFunctionReturn functionReturn;
private List<JdbcCallParameterRegistration> parameterRegistrations;
private List<JdbcParameterBinder> parameterBinders;
private List<JdbcCallParameterExtractor> parameterExtractors;
private List<JdbcCallRefCursorExtractor> refCursorExtractors;
public Builder(String callableName, ParameterStrategy parameterStrategy) {
this.callableName = callableName;
this.parameterStrategy = parameterStrategy;
}
public JdbcCall buildJdbcCall() {
return new JdbcCallImpl( this );
}
public void setFunctionReturn(JdbcCallFunctionReturn functionReturn) {
this.functionReturn = functionReturn;
}
public void addParameterRegistration(JdbcCallParameterRegistration registration) {
if ( parameterRegistrations == null ) {
parameterRegistrations = new ArrayList<>();
}
// todo (6.0) : add validation based on ParameterStrategy
parameterRegistrations.add( registration );
switch ( registration.getParameterMode() ) {
case REF_CURSOR: {
addRefCursorExtractor( registration.getRefCursorExtractor() );
break;
}
case IN: {
addParameterBinder( registration.getParameterBinder() );
break;
}
case INOUT: {
addParameterBinder( registration.getParameterBinder() );
addParameterExtractor( registration.getParameterExtractor() );
break;
}
case OUT: {
addParameterExtractor( registration.getParameterExtractor() );
break;
}
default: {
throw new HibernateException( "Unexpected ParameterMode : " + registration.getParameterMode() );
}
}
}
private void addParameterBinder(JdbcParameterBinder binder) {
if ( parameterBinders == null ) {
parameterBinders = new ArrayList<>();
}
parameterBinders.add( binder );
}
private void addParameterExtractor(JdbcCallParameterExtractor extractor) {
if ( parameterExtractors == null ) {
parameterExtractors = new ArrayList<>();
}
parameterExtractors.add( extractor );
}
private void addRefCursorExtractor(JdbcCallRefCursorExtractor extractor) {
if ( refCursorExtractors == null ) {
refCursorExtractors = new ArrayList<>();
}
refCursorExtractors.add( extractor );
}
}
}

View File

@ -0,0 +1,84 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.exec.internal;
import java.sql.CallableStatement;
import java.sql.SQLException;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.metamodel.model.domain.AllowableParameterType;
import org.hibernate.metamodel.model.domain.BasicDomainType;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcCallParameterExtractor;
/**
* Standard implementation of JdbcCallParameterExtractor
*
* @author Steve Ebersole
*/
public class JdbcCallParameterExtractorImpl<T> implements JdbcCallParameterExtractor {
private final String callableName;
private final String parameterName;
private final int parameterPosition;
private final BasicDomainType ormType;
public JdbcCallParameterExtractorImpl(
String callableName,
String parameterName,
int parameterPosition,
AllowableParameterType ormType) {
if ( ! (ormType instanceof BasicDomainType ) ) {
throw new NotYetImplementedFor6Exception(
"Support for JDBC CallableStatement parameter extraction not yet supported for non-basic types"
);
}
this.callableName = callableName;
this.parameterName = parameterName;
this.parameterPosition = parameterPosition;
this.ormType = (BasicDomainType) ormType;
}
@Override
public String getParameterName() {
return parameterName;
}
@Override
public int getParameterPosition() {
return parameterPosition;
}
@Override
@SuppressWarnings("unchecked")
public T extractValue(
CallableStatement callableStatement,
boolean shouldUseJdbcNamedParameters,
ExecutionContext executionContext) {
final boolean useNamed = shouldUseJdbcNamedParameters
&& parameterName != null;
// todo (6.0) : we should just ask BasicValuedExpressableType for the JdbcValueExtractor...
try {
if ( useNamed ) {
return (T) ormType.extract( callableStatement, parameterName, executionContext.getSession() );
}
else {
return (T) ormType.extract( callableStatement, parameterPosition, executionContext.getSession() );
}
}
catch (SQLException e) {
throw executionContext.getSession().getJdbcServices().getSqlExceptionHelper().convert(
e,
"Unable to extract OUT/INOUT parameter value"
);
}
}
}

View File

@ -0,0 +1,137 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.exec.internal;
import java.sql.CallableStatement;
import java.sql.SQLException;
import jakarta.persistence.ParameterMode;
import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.model.domain.AllowableParameterType;
import org.hibernate.metamodel.model.domain.BasicDomainType;
import org.hibernate.sql.exec.spi.JdbcCallParameterExtractor;
import org.hibernate.sql.exec.spi.JdbcCallParameterRegistration;
import org.hibernate.sql.exec.spi.JdbcParameterBinder;
import org.hibernate.type.descriptor.jdbc.JdbcTypeDescriptor;
/**
* @author Steve Ebersole
*/
public class JdbcCallParameterRegistrationImpl implements JdbcCallParameterRegistration {
private final String name;
private final int jdbcParameterPositionStart;
private final ParameterMode parameterMode;
private final int jdbcTypeCode;
private final AllowableParameterType ormType;
private final JdbcParameterBinder parameterBinder;
private final JdbcCallParameterExtractorImpl parameterExtractor;
private final JdbcCallRefCursorExtractorImpl refCursorExtractor;
public JdbcCallParameterRegistrationImpl(
String name,
int jdbcParameterPositionStart,
ParameterMode parameterMode,
int jdbcTypeCode,
AllowableParameterType ormType,
JdbcParameterBinder parameterBinder,
JdbcCallParameterExtractorImpl parameterExtractor,
JdbcCallRefCursorExtractorImpl refCursorExtractor) {
this.name = name;
this.jdbcParameterPositionStart = jdbcParameterPositionStart;
this.parameterMode = parameterMode;
this.jdbcTypeCode = jdbcTypeCode;
this.ormType = ormType;
this.parameterBinder = parameterBinder;
this.parameterExtractor = parameterExtractor;
this.refCursorExtractor = refCursorExtractor;
}
@Override
public JdbcParameterBinder getParameterBinder() {
return parameterBinder;
}
@Override
public JdbcCallParameterExtractor getParameterExtractor() {
return parameterExtractor;
}
@Override
public JdbcCallRefCursorExtractorImpl getRefCursorExtractor() {
return refCursorExtractor;
}
@Override
public ParameterMode getParameterMode() {
return parameterMode;
}
@Override
public AllowableParameterType getParameterType() {
return ormType;
}
@Override
public void registerParameter(
CallableStatement callableStatement, SharedSessionContractImplementor session) {
switch ( parameterMode ) {
case REF_CURSOR: {
registerRefCursorParameter( callableStatement, session );
break;
}
case IN: {
// nothing to prepare
break;
}
default: {
// OUT and INOUT
registerOutputParameter( callableStatement, session );
break;
}
}
}
private void registerRefCursorParameter(
CallableStatement callableStatement,
SharedSessionContractImplementor session) {
if ( name != null ) {
session.getFactory().getServiceRegistry()
.getService( RefCursorSupport.class )
.registerRefCursorParameter( callableStatement, name );
}
else {
session.getFactory().getServiceRegistry()
.getService( RefCursorSupport.class )
.registerRefCursorParameter( callableStatement, jdbcParameterPositionStart );
}
}
private void registerOutputParameter(
CallableStatement callableStatement,
SharedSessionContractImplementor session) {
final JdbcTypeDescriptor sqlTypeDescriptor = ( (BasicDomainType) ormType ).getJdbcTypeDescriptor();
try {
if ( name != null ) {
callableStatement.registerOutParameter( name, sqlTypeDescriptor.getJdbcTypeCode() );
}
else {
callableStatement.registerOutParameter(
jdbcParameterPositionStart,
sqlTypeDescriptor.getJdbcTypeCode()
);
}
}
catch (SQLException e) {
throw session.getJdbcServices().getSqlExceptionHelper().convert(
e,
"Unable to register CallableStatement out parameter"
);
}
}
}

View File

@ -0,0 +1,58 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.exec.internal;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.sql.exec.spi.JdbcCallRefCursorExtractor;
/**
* Controls extracting values from REF_CURSOR parameters.
* <p/>
* For extracting results from OUT/INOUT params, see {@link JdbcCallParameterExtractorImpl} instead.
*
* @author Steve Ebersole
*/
public class JdbcCallRefCursorExtractorImpl implements JdbcCallRefCursorExtractor {
private final String jdbcParameterName;
private final int jdbcParameterPosition;
public JdbcCallRefCursorExtractorImpl(
String jdbcParameterName,
int jdbcParameterPosition) {
this.jdbcParameterName = jdbcParameterName;
this.jdbcParameterPosition = jdbcParameterPosition;
}
@Override
public ResultSet extractResultSet(
CallableStatement callableStatement,
SharedSessionContractImplementor session) {
final boolean supportsNamedParameters = session.getJdbcServices()
.getJdbcEnvironment()
.getExtractedDatabaseMetaData()
.supportsNamedParameters();
final boolean useNamed = supportsNamedParameters && jdbcParameterName != null;
if ( useNamed ) {
return session.getFactory()
.getServiceRegistry()
.getService( RefCursorSupport.class )
.getResultSet( callableStatement, jdbcParameterName );
}
else {
return session.getFactory()
.getServiceRegistry()
.getService( RefCursorSupport.class )
.getResultSet( callableStatement, jdbcParameterPosition );
}
}
}

View File

@ -6,31 +6,33 @@
*/
package org.hibernate.sql.exec.spi;
import java.util.List;
/**
* @author Steve Ebersole
*/
public interface JdbcCall extends JdbcAnonBlock {
// /**
// * If the call is a function, returns the function return descriptor
// */
// JdbcCallFunctionReturn getFunctionReturn();
//
// /**
// * Get the list of any parameter registrations we need to register
// * against the generated CallableStatement
// */
// List<JdbcCallParameterRegistration> getParameterRegistrations();
//
// /**
// * Extractors for reading back any OUT/INOUT parameters.
// *
// * @apiNote Note that REF_CURSOR parameters should be handled via
// * {@link #getCallRefCursorExtractors()}
// */
// List<JdbcCallParameterExtractor> getParameterExtractors();
//
// /**
// * Extractors for REF_CURSOR (ResultSet) parameters
// */
// List<JdbcCallRefCursorExtractor> getCallRefCursorExtractors();
/**
* If the call is a function, returns the function return descriptor
*/
JdbcCallFunctionReturn getFunctionReturn();
/**
* Get the list of any parameter registrations we need to register
* against the generated CallableStatement
*/
List<JdbcCallParameterRegistration> getParameterRegistrations();
/**
* Extractors for reading back any OUT/INOUT parameters.
*
* @apiNote Note that REF_CURSOR parameters should be handled via
* {@link #getCallRefCursorExtractors()}
*/
List<JdbcCallParameterExtractor> getParameterExtractors();
/**
* Extractors for REF_CURSOR (ResultSet) parameters
*/
List<JdbcCallRefCursorExtractor> getCallRefCursorExtractors();
}

View File

@ -0,0 +1,17 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.exec.spi;
/**
* Models the function return when the JdbcCall represents a call to a database
* function.
*
* @author Steve Ebersole
*/
public interface JdbcCallFunctionReturn extends JdbcCallParameterRegistration {
}

View File

@ -0,0 +1,29 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.exec.spi;
import java.sql.CallableStatement;
import org.hibernate.sql.exec.internal.JdbcCallRefCursorExtractorImpl;
/**
* Controls extracting values from OUT/INOUT parameters.
* <p/>
* For extracting REF_CURSOR results, see {@link JdbcCallRefCursorExtractorImpl} instead.
*
* @author Steve Ebersole
*/
public interface JdbcCallParameterExtractor<T> {
String getParameterName();
int getParameterPosition();
T extractValue(
CallableStatement callableStatement,
boolean shouldUseJdbcNamedParameters,
ExecutionContext executionContext);
}

View File

@ -0,0 +1,34 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.exec.spi;
import java.sql.CallableStatement;
import jakarta.persistence.ParameterMode;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.model.domain.AllowableParameterType;
import org.hibernate.sql.exec.internal.JdbcCallRefCursorExtractorImpl;
/**
* @author Steve Ebersole
*/
public interface JdbcCallParameterRegistration {
ParameterMode getParameterMode();
void registerParameter(
CallableStatement callableStatement,
SharedSessionContractImplementor session);
JdbcParameterBinder getParameterBinder();
JdbcCallParameterExtractor getParameterExtractor();
JdbcCallRefCursorExtractorImpl getRefCursorExtractor();
AllowableParameterType getParameterType();
}

View File

@ -0,0 +1,20 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.exec.spi;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
/**
* @author Steve Ebersole
*/
public interface JdbcCallRefCursorExtractor {
ResultSet extractResultSet(CallableStatement callableStatement, SharedSessionContractImplementor session);
}

View File

@ -7,35 +7,37 @@
package org.hibernate.orm.test.jpa.compliance.tck2_2;
import java.util.Date;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.procedure.ProcedureCall;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Parameter;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.Table;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.procedure.ProcedureCall;
import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.Test;
import static org.junit.Assert.fail;
import static org.junit.jupiter.api.Assertions.fail;
/**
* @author Steve Ebersole
*/
@RequiresDialect( H2Dialect.class )
@FailureExpected( message = "Procedure/function support not yet implemented", jiraKey = "n/a" )
public class StoredProcedureApiTests extends BaseNonConfigCoreFunctionalTestCase {
@DomainModel(
annotatedClasses = StoredProcedureApiTests.Person.class
)
@SessionFactory
public class StoredProcedureApiTests {
@Test
public void parameterValueAccess() {
inTransaction(
public void parameterValueAccess(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final ProcedureCall call = session.createStoredProcedureCall( "test" );
@ -48,8 +50,8 @@ public class StoredProcedureApiTests extends BaseNonConfigCoreFunctionalTestCase
}
@Test
public void parameterValueAccessByName() {
inTransaction(
public void parameterValueAccessByName(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final ProcedureCall call = session.createStoredProcedureCall( "test" );
@ -62,8 +64,8 @@ public class StoredProcedureApiTests extends BaseNonConfigCoreFunctionalTestCase
}
@Test
public void testInvalidParameterReference() {
inTransaction(
public void testInvalidParameterReference(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final ProcedureCall call1 = session.createStoredProcedureCall( "test" );
call1.registerStoredProcedureParameter(1, Integer.class, ParameterMode.IN);
@ -86,8 +88,8 @@ public class StoredProcedureApiTests extends BaseNonConfigCoreFunctionalTestCase
}
@Test
public void testParameterBindTypeMismatch() {
inTransaction(
public void testParameterBindTypeMismatch(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
try {
final ProcedureCall call1 = session.createStoredProcedureCall( "test" );
@ -102,47 +104,6 @@ public class StoredProcedureApiTests extends BaseNonConfigCoreFunctionalTestCase
);
}
@Override
protected void applyMetadataSources(MetadataSources sources) {
super.applyMetadataSources( sources );
sources.addAnnotatedClass( Person.class );
}
@Override
protected void afterMetadataBuilt(Metadata metadata) {
super.afterMetadataBuilt( metadata );
// metadata.getDatabase().addAuxiliaryDatabaseObject(
// new StoredProcedureResultSetMappingTest.ProcedureDefinition() {
// @Override
// public boolean appliesToDialect(Dialect dialect) {
// return H2Dialect.class.isInstance( dialect );
// }
//
// @Override
// public boolean beforeTablesOnCreation() {
// return false;
// }
//
// @Override
// public String getExportIdentifier() {
// return "StoredProcedure#test"
// }
//
// @Override
// public String[] sqlCreateStrings(Dialect dialect) {
// return super.sqlCreateStrings( dialect );
// }
//
// @Override
// public String[] sqlDropStrings(Dialect dialect) {
// return super.sqlDropStrings( dialect );
// }
// }
// );
}
@Entity( name = "Person" )
@Table( name = "person" )
public static class Person {

View File

@ -0,0 +1,418 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.procedure;
import java.sql.CallableStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
import org.hibernate.Session;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.procedure.ProcedureCall;
import org.hibernate.query.procedure.ProcedureParameter;
import org.hibernate.result.Output;
import org.hibernate.result.ResultSetOutput;
import org.hibernate.type.StringType;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.test.procedure.Person;
import org.hibernate.test.procedure.Phone;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.EntityManager;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.StoredProcedureQuery;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect(value = MySQLDialect.class, version = 500)
@Jpa(
annotatedClasses = {
Person.class,
Phone.class,
}
)
public class MySQLStoredProcedureTest {
@BeforeEach
public void init(EntityManagerFactoryScope scope) {
EntityManager entityManager = scope.getEntityManagerFactory().createEntityManager();
try {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
Statement statement = null;
try {
statement = connection.createStatement();
statement.executeUpdate(
"CREATE PROCEDURE sp_count_phones (" +
" IN personId INT, " +
" OUT phoneCount INT " +
") " +
"BEGIN " +
" SELECT COUNT(*) INTO phoneCount " +
" FROM Phone p " +
" WHERE p.person_id = personId; " +
"END"
);
statement.executeUpdate(
"CREATE PROCEDURE sp_phones(IN personId INT) " +
"BEGIN " +
" SELECT * " +
" FROM Phone " +
" WHERE person_id = personId; " +
"END"
);
statement.executeUpdate(
"CREATE FUNCTION fn_count_phones(personId integer) " +
"RETURNS integer " +
"DETERMINISTIC " +
"READS SQL DATA " +
"BEGIN " +
" DECLARE phoneCount integer; " +
" SELECT COUNT(*) INTO phoneCount " +
" FROM Phone p " +
" WHERE p.person_id = personId; " +
" RETURN phoneCount; " +
"END"
);
statement.executeUpdate(
"CREATE PROCEDURE sp_is_null (" +
" IN param varchar(255), " +
" OUT result tinyint(1) " +
") " +
"BEGIN " +
" IF (param IS NULL) THEN SET result = true; " +
" ELSE SET result = false; " +
" END IF; " +
"END"
);
} finally {
if ( statement != null ) {
statement.close();
}
}
} );
}
finally {
entityManager.close();
}
scope.inTransaction(
em -> {
Person person1 = new Person( "John Doe" );
person1.setNickName( "JD" );
person1.setAddress( "Earth" );
person1.setCreatedOn( Timestamp.from( LocalDateTime.of( 2000, 1, 1, 0, 0, 0 )
.toInstant( ZoneOffset.UTC ) ) );
em.persist( person1 );
Phone phone1 = new Phone( "123-456-7890" );
phone1.setId( 1L );
person1.addPhone( phone1 );
Phone phone2 = new Phone( "098_765-4321" );
phone2.setId( 2L );
person1.addPhone( phone2 );
}
);
}
@AfterEach
public void destroy(EntityManagerFactoryScope scope) {
EntityManager entityManager = scope.getEntityManagerFactory().createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try (Statement statement = connection.createStatement()) {
statement.executeUpdate( "DROP PROCEDURE IF EXISTS sp_count_phones" );
}
catch (SQLException ignore) {
}
} );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
entityManager = scope.getEntityManagerFactory().createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try (Statement statement = connection.createStatement()) {
statement.executeUpdate( "DROP PROCEDURE IF EXISTS sp_phones" );
}
catch (SQLException ignore) {
}
} );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
entityManager = scope.getEntityManagerFactory().createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try (Statement statement = connection.createStatement()) {
statement.executeUpdate( "DROP FUNCTION IF EXISTS fn_count_phones" );
}
catch (SQLException ignore) {
}
} );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
entityManager = scope.getEntityManagerFactory().createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try (Statement statement = connection.createStatement()) {
statement.executeUpdate( "DROP PROCEDURE IF EXISTS sp_is_null" );
}
catch (SQLException ignore) {
}
} );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
scope.releaseEntityManagerFactory();
}
@Test
public void testStoredProcedureOutParameter(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_count_phones" );
query.registerStoredProcedureParameter( "personId", Long.class, ParameterMode.IN );
query.registerStoredProcedureParameter( "phoneCount", Long.class, ParameterMode.OUT );
query.setParameter( "personId", 1L );
query.execute();
Long phoneCount = (Long) query.getOutputParameterValue( "phoneCount" );
assertEquals( Long.valueOf( 2 ), phoneCount );
}
);
}
@Test
public void testHibernateProcedureCallOutParameter(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
Session session = entityManager.unwrap( Session.class );
ProcedureCall call = session.createStoredProcedureCall( "sp_count_phones" );
final ProcedureParameter<Long> inParam = call.registerParameter(
"personId",
Long.class,
ParameterMode.IN
);
call.registerParameter( "phoneCount", Long.class, ParameterMode.OUT );
call.setParameter( inParam, 1L );
Long phoneCount = (Long) call.getOutputs().getOutputParameterValue( "phoneCount" );
assertEquals( Long.valueOf( 2 ), phoneCount );
}
);
}
@Test
public void testStoredProcedureRefCursor(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
try {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_phones" );
query.registerStoredProcedureParameter( 1, void.class, ParameterMode.REF_CURSOR );
query.registerStoredProcedureParameter( 2, Long.class, ParameterMode.IN );
query.setParameter( 2, 1L );
List<Object[]> personComments = query.getResultList();
assertEquals( 2, personComments.size() );
}
catch (Exception e) {
assertTrue( Pattern.compile( "Dialect .*? not known to support REF_CURSOR parameters" )
.matcher( e.getCause().getMessage() )
.matches() );
}
}
);
}
@Test
public void testStoredProcedureReturnValue(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_phones" );
query.registerStoredProcedureParameter( 1, Long.class, ParameterMode.IN );
query.setParameter( 1, 1L );
List<Object[]> personComments = query.getResultList();
assertEquals( 2, personComments.size() );
}
);
}
@Test
public void testHibernateProcedureCallReturnValueParameter(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
Session session = entityManager.unwrap( Session.class );
ProcedureCall call = session.createStoredProcedureCall( "sp_phones" );
final ProcedureParameter<Long> parameter = call.registerParameter(
1,
Long.class,
ParameterMode.IN
);
call.setParameter( parameter, 1L );
Output output = call.getOutputs().getCurrent();
List<Object[]> personComments = ( (ResultSetOutput) output ).getResultList();
assertEquals( 2, personComments.size() );
}
);
}
@Test
public void testFunctionWithJDBC(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
final AtomicReference<Integer> phoneCount = new AtomicReference<>();
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try (CallableStatement function = connection.prepareCall(
"{ ? = call fn_count_phones(?) }" )) {
function.registerOutParameter( 1, Types.INTEGER );
function.setInt( 2, 1 );
function.execute();
phoneCount.set( function.getInt( 1 ) );
}
} );
assertEquals( Integer.valueOf( 2 ), phoneCount.get() );
}
);
}
@Test
@TestForIssue(jiraKey = "HHH-12905")
public void testStoredProcedureNullParameter(EntityManagerFactoryScope scope) {
scope.inTransaction( entityManager -> {
ProcedureCall procedureCall = entityManager.unwrap( Session.class )
.createStoredProcedureCall( "sp_is_null" );
procedureCall.registerParameter( 1, StringType.class, ParameterMode.IN ).enablePassingNulls( true );
procedureCall.registerParameter( 2, Boolean.class, ParameterMode.OUT );
procedureCall.setParameter( 1, null );
Boolean result = (Boolean) procedureCall.getOutputParameterValue( 2 );
assertTrue( result );
} );
scope.inTransaction( entityManager -> {
ProcedureCall procedureCall = entityManager.unwrap( Session.class )
.createStoredProcedureCall( "sp_is_null" );
procedureCall.registerParameter( 1, StringType.class, ParameterMode.IN ).enablePassingNulls( true );
procedureCall.registerParameter( 2, Boolean.class, ParameterMode.OUT );
procedureCall.setParameter( 1, "test" );
Boolean result = (Boolean) procedureCall.getOutputParameterValue( 2 );
assertFalse( result );
} );
}
@Test
@TestForIssue(jiraKey = "HHH-12905")
public void testStoredProcedureNullParameterHibernateWithoutEnablePassingNulls(EntityManagerFactoryScope scope) {
scope.inTransaction( entityManager -> {
ProcedureCall procedureCall = entityManager.unwrap( Session.class )
.createStoredProcedureCall( "sp_is_null" );
procedureCall.registerParameter( 1, StringType.class, ParameterMode.IN );
procedureCall.registerParameter( 2, Boolean.class, ParameterMode.OUT );
procedureCall.setParameter( 1, null );
procedureCall.getOutputParameterValue( 2 );
} );
}
@Test
public void testStoredProcedureNullParameterHibernateWithoutSettingTheParameter(
EntityManagerFactoryScope
scope) {
scope.inTransaction( entityManager -> {
try {
ProcedureCall procedureCall = entityManager.unwrap( Session.class ).createStoredProcedureCall(
"sp_is_null" );
procedureCall.registerParameter( 1, StringType.class, ParameterMode.IN );
procedureCall.registerParameter( 2, Boolean.class, ParameterMode.OUT );
procedureCall.getOutputParameterValue( 2 );
fail( "Should have thrown exception" );
}
catch (IllegalArgumentException e) {
assertEquals(
"The parameter at position [1] was not set! You need to call the setParameter method.",
e.getMessage()
);
}
} );
}
}

View File

@ -0,0 +1,522 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.procedure;
import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.procedure.ProcedureCall;
import org.hibernate.query.procedure.ProcedureParameter;
import org.hibernate.result.Output;
import org.hibernate.result.ResultSetOutput;
import org.hibernate.type.NumericBooleanType;
import org.hibernate.type.YesNoType;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.test.procedure.Person;
import org.hibernate.test.procedure.Phone;
import org.hibernate.test.procedure.Vote;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Id;
import jakarta.persistence.NamedStoredProcedureQueries;
import jakarta.persistence.NamedStoredProcedureQuery;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.StoredProcedureParameter;
import jakarta.persistence.StoredProcedureQuery;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
/**
* @author Vlad Mihalcea
*/
@Jpa(
annotatedClasses = {
Person.class,
Phone.class,
OracleStoredProcedureTest.IdHolder.class,
Vote.class
}
)
@RequiresDialect(value = OracleDialect.class, version = 800)
public class OracleStoredProcedureTest {
@NamedStoredProcedureQueries({
@NamedStoredProcedureQuery(
name = "singleRefCursor",
procedureName = "singleRefCursor",
parameters = {
@StoredProcedureParameter(mode = ParameterMode.REF_CURSOR, type = void.class)
}
),
@NamedStoredProcedureQuery(
name = "outAndRefCursor",
procedureName = "outAndRefCursor",
parameters = {
@StoredProcedureParameter(mode = ParameterMode.REF_CURSOR, type = void.class),
@StoredProcedureParameter(mode = ParameterMode.OUT, type = Long.class),
}
)
})
@Entity(name = "IdHolder")
public static class IdHolder {
@Id
Long id;
}
@BeforeEach
public void init(EntityManagerFactoryScope scope) {
EntityManager entityManager = scope.getEntityManagerFactory().createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
Statement statement = null;
try {
statement = connection.createStatement();
statement.executeUpdate(
"CREATE OR REPLACE PROCEDURE sp_count_phones ( " +
" personId IN NUMBER, " +
" phoneCount OUT NUMBER ) " +
"AS " +
"BEGIN " +
" SELECT COUNT(*) INTO phoneCount " +
" FROM phone " +
" WHERE person_id = personId; " +
"END;"
);
statement.executeUpdate(
"CREATE OR REPLACE PROCEDURE sp_person_phones ( " +
" personId IN NUMBER, " +
" personPhones OUT SYS_REFCURSOR ) " +
"AS " +
"BEGIN " +
" OPEN personPhones FOR " +
" SELECT *" +
" FROM phone " +
" WHERE person_id = personId; " +
"END;"
);
statement.executeUpdate(
"CREATE OR REPLACE FUNCTION fn_count_phones ( " +
" personId IN NUMBER ) " +
" RETURN NUMBER " +
"IS " +
" phoneCount NUMBER; " +
"BEGIN " +
" SELECT COUNT(*) INTO phoneCount " +
" FROM phone " +
" WHERE person_id = personId; " +
" RETURN( phoneCount ); " +
"END;"
);
statement.executeUpdate(
"CREATE OR REPLACE FUNCTION fn_person_and_phones ( " +
" personId IN NUMBER ) " +
" RETURN SYS_REFCURSOR " +
"IS " +
" personAndPhones SYS_REFCURSOR; " +
"BEGIN " +
" OPEN personAndPhones FOR " +
" SELECT " +
" pr.id AS \"pr.id\", " +
" pr.name AS \"pr.name\", " +
" pr.nickName AS \"pr.nickName\", " +
" pr.address AS \"pr.address\", " +
" pr.createdOn AS \"pr.createdOn\", " +
" pr.version AS \"pr.version\", " +
" ph.id AS \"ph.id\", " +
" ph.person_id AS \"ph.person_id\", " +
" ph.phone_number AS \"ph.phone_number\", " +
" ph.valid AS \"ph.valid\" " +
" FROM person pr " +
" JOIN phone ph ON pr.id = ph.person_id " +
" WHERE pr.id = personId; " +
" RETURN personAndPhones; " +
"END;"
);
statement.executeUpdate(
"CREATE OR REPLACE " +
"PROCEDURE singleRefCursor(p_recordset OUT SYS_REFCURSOR) AS " +
" BEGIN " +
" OPEN p_recordset FOR " +
" SELECT 1 as id " +
" FROM dual; " +
" END; "
);
statement.executeUpdate(
"CREATE OR REPLACE " +
"PROCEDURE outAndRefCursor(p_recordset OUT SYS_REFCURSOR, p_value OUT NUMBER) AS " +
" BEGIN " +
" OPEN p_recordset FOR " +
" SELECT 1 as id " +
" FROM dual; " +
" SELECT 1 INTO p_value FROM dual; " +
" END; "
);
statement.executeUpdate(
"CREATE OR REPLACE PROCEDURE sp_phone_validity ( " +
" validity IN NUMBER, " +
" personPhones OUT SYS_REFCURSOR ) " +
"AS " +
"BEGIN " +
" OPEN personPhones FOR " +
" SELECT phone_number " +
" FROM phone " +
" WHERE valid = validity; " +
"END;"
);
statement.executeUpdate(
"CREATE OR REPLACE PROCEDURE sp_votes ( " +
" validity IN CHAR, " +
" votes OUT SYS_REFCURSOR ) " +
"AS " +
"BEGIN " +
" OPEN votes FOR " +
" SELECT id " +
" FROM vote " +
" WHERE vote_choice = validity; " +
"END;"
);
}
finally {
if ( statement != null ) {
statement.close();
}
}
} );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
entityManager = scope.getEntityManagerFactory().createEntityManager();
entityManager.getTransaction().begin();
try {
Person person1 = new Person( "John Doe" );
person1.setNickName( "JD" );
person1.setAddress( "Earth" );
person1.setCreatedOn( Timestamp.from( LocalDateTime.of( 2000, 1, 1, 0, 0, 0 )
.toInstant( ZoneOffset.UTC ) ) );
entityManager.persist( person1 );
Phone phone1 = new Phone( "123-456-7890" );
phone1.setId( 1L );
phone1.setValid( true );
person1.addPhone( phone1 );
Phone phone2 = new Phone( "098_765-4321" );
phone2.setId( 2L );
phone2.setValid( false );
person1.addPhone( phone2 );
entityManager.getTransaction().commit();
}
finally {
entityManager.close();
}
}
@AfterEach
public void destroy(EntityManagerFactoryScope scope) {
EntityManager entityManager = scope.getEntityManagerFactory().createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try (Statement statement = connection.createStatement()) {
statement.executeUpdate( "DROP PROCEDURE sp_count_phones" );
}
catch (SQLException ignore) {
}
} );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
entityManager = scope.getEntityManagerFactory().createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try (Statement statement = connection.createStatement()) {
statement.executeUpdate( "DROP PROCEDURE sp_person_phones" );
}
catch (SQLException ignore) {
}
} );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
entityManager = scope.getEntityManagerFactory().createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try (Statement statement = connection.createStatement()) {
statement.executeUpdate( "DROP FUNCTION fn_count_phones" );
}
catch (SQLException ignore) {
}
} );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
scope.releaseEntityManagerFactory();
}
@Test
public void testStoredProcedureOutParameter(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_count_phones" );
query.registerStoredProcedureParameter( 1, Long.class, ParameterMode.IN );
query.registerStoredProcedureParameter( 2, Long.class, ParameterMode.OUT );
query.setParameter( 1, 1L );
query.execute();
Long phoneCount = (Long) query.getOutputParameterValue( 2 );
assertEquals( Long.valueOf( 2 ), phoneCount );
}
);
}
@Test
public void testStoredProcedureRefCursor(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_person_phones" );
query.registerStoredProcedureParameter( 1, Long.class, ParameterMode.IN );
query.registerStoredProcedureParameter( 2, Class.class, ParameterMode.REF_CURSOR );
query.setParameter( 1, 1L );
query.execute();
List<Object[]> postComments = query.getResultList();
assertNotNull( postComments );
}
);
}
@Test
public void testHibernateProcedureCallRefCursor(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
Session session = entityManager.unwrap( Session.class );
ProcedureCall call = session.createStoredProcedureCall( "sp_person_phones" );
final ProcedureParameter<Long> inParam = call.registerParameter(
1,
Long.class,
ParameterMode.IN
);
call.setParameter( inParam, 1L );
call.registerParameter( 2, Class.class, ParameterMode.REF_CURSOR );
Output output = call.getOutputs().getCurrent();
List<Object[]> postComments = ( (ResultSetOutput) output ).getResultList();
assertEquals( 2, postComments.size() );
}
);
}
@Test
public void testStoredProcedureReturnValue(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
BigDecimal phoneCount = (BigDecimal) entityManager
.createNativeQuery( "SELECT fn_count_phones(:personId) FROM DUAL" )
.setParameter( "personId", 1 )
.getSingleResult();
assertEquals( BigDecimal.valueOf( 2 ), phoneCount );
}
);
}
@Test
public void testNamedNativeQueryStoredProcedureRefCursor(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
List<Object[]> postAndComments = entityManager
.createNamedQuery(
"fn_person_and_phones" )
.setParameter( 1, 1L )
.getResultList();
Object[] postAndComment = postAndComments.get( 0 );
Person person = (Person) postAndComment[0];
Phone phone = (Phone) postAndComment[1];
assertEquals( 2, postAndComments.size() );
}
);
}
@Test
public void testNamedNativeQueryStoredProcedureRefCursorWithJDBC(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try (CallableStatement function = connection.prepareCall(
"{ ? = call fn_person_and_phones( ? ) }" )) {
try {
function.registerOutParameter( 1, Types.REF_CURSOR );
}
catch (SQLException e) {
//OracleTypes.CURSOR
function.registerOutParameter( 1, -10 );
}
function.setInt( 2, 1 );
function.execute();
try (ResultSet resultSet = (ResultSet) function.getObject( 1 );) {
while ( resultSet.next() ) {
Long postCommentId = resultSet.getLong( 1 );
String review = resultSet.getString( 2 );
}
}
}
} );
}
);
}
@Test
@TestForIssue(jiraKey = "HHH-11863")
public void testSysRefCursorAsOutParameter(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
StoredProcedureQuery function = entityManager.createNamedStoredProcedureQuery( "singleRefCursor" );
function.execute();
assertFalse( function.hasMoreResults() );
Long value = null;
try (ResultSet resultSet = (ResultSet) function.getOutputParameterValue( 1 )) {
while ( resultSet.next() ) {
value = resultSet.getLong( 1 );
}
}
catch (SQLException e) {
fail( e.getMessage() );
}
assertEquals( Long.valueOf( 1 ), value );
} );
}
@Test
@TestForIssue(jiraKey = "HHH-11863")
public void testOutAndSysRefCursorAsOutParameter(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
StoredProcedureQuery function = entityManager.createNamedStoredProcedureQuery( "outAndRefCursor" );
function.execute();
assertFalse( function.hasMoreResults() );
Long value = null;
try (ResultSet resultSet = (ResultSet) function.getOutputParameterValue( 1 )) {
while ( resultSet.next() ) {
value = resultSet.getLong( 1 );
}
}
catch (SQLException e) {
fail( e.getMessage() );
}
assertEquals( value, function.getOutputParameterValue( 2 ) );
} );
}
@Test
@TestForIssue(jiraKey = "HHH-12661")
public void testBindParameterAsHibernateType(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_phone_validity" )
.registerStoredProcedureParameter( 1, NumericBooleanType.class, ParameterMode.IN )
.registerStoredProcedureParameter( 2, Class.class, ParameterMode.REF_CURSOR )
.setParameter( 1, true );
query.execute();
List phones = query.getResultList();
assertEquals( 1, phones.size() );
assertEquals( "123-456-7890", phones.get( 0 ) );
} );
scope.inTransaction(
entityManager -> {
Vote vote1 = new Vote();
vote1.setId( 1L );
vote1.setVoteChoice( true );
entityManager.persist( vote1 );
Vote vote2 = new Vote();
vote2.setId( 2L );
vote2.setVoteChoice( false );
entityManager.persist( vote2 );
} );
scope.inTransaction(
entityManager -> {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_votes" )
.registerStoredProcedureParameter( 1, YesNoType.class, ParameterMode.IN )
.registerStoredProcedureParameter( 2, Class.class, ParameterMode.REF_CURSOR )
.setParameter( 1, true );
query.execute();
List votes = query.getResultList();
assertEquals( 1, votes.size() );
assertEquals( 1, ( (Number) votes.get( 0 ) ).intValue() );
} );
}
}

View File

@ -4,7 +4,7 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.procedure;
package org.hibernate.orm.test.procedure;
import java.sql.CallableStatement;
import java.sql.ResultSet;
@ -22,9 +22,10 @@ import org.hibernate.Session;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.procedure.ProcedureCall;
import org.hibernate.type.StringType;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue;
import org.hibernate.type.StringType;
import org.junit.Before;
import org.junit.Test;
@ -34,6 +35,9 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.hibernate.test.procedure.Person;
import org.hibernate.test.procedure.Phone;
/**
* @author Vlad Mihalcea
*/
@ -388,20 +392,36 @@ public class PostgreSQLStoredProcedureTest extends BaseEntityManagerFunctionalTe
@TestForIssue(jiraKey = "HHH-12905")
public void testStoredProcedureNullParameterHibernateWithoutEnablePassingNulls() {
doInJPA( this::entityManagerFactory, entityManager -> {
ProcedureCall procedureCall = entityManager.unwrap( Session.class )
.createStoredProcedureCall( "sp_is_null" );
procedureCall.registerParameter( "param", StringType.class, ParameterMode.IN );
procedureCall.registerParameter( "result", Boolean.class, ParameterMode.OUT );
procedureCall.setParameter( "param", null );
procedureCall.getOutputParameterValue( "result" );
} );
}
@Test
public void testStoredProcedureNullParameterHibernateWithoutSettingTheParameter() {
doInJPA( this::entityManagerFactory, entityManager -> {
try {
ProcedureCall procedureCall = entityManager.unwrap( Session.class )
.createStoredProcedureCall( "sp_is_null" );
.createStoredProcedureCall( "sp_is_null" );
procedureCall.registerParameter( "param", StringType.class, ParameterMode.IN );
procedureCall.registerParameter( "result", Boolean.class, ParameterMode.OUT );
procedureCall.setParameter( "param", null );
procedureCall.getOutputParameterValue( "result" );
fail("Should have thrown exception");
fail( "Should have thrown exception" );
}
catch (IllegalArgumentException e) {
assertEquals( "The parameter named [param] was null. You need to call ParameterRegistration#enablePassingNulls(true) in order to pass null parameters.", e.getMessage() );
assertEquals(
"The parameter named [param] was not set! You need to call the setParameter method.",
e.getMessage()
);
}
} );
}

View File

@ -4,53 +4,53 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.procedure;
package org.hibernate.orm.test.procedure;
import java.sql.CallableStatement;
import java.sql.Types;
import java.util.Collections;
import jakarta.persistence.EntityManager;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.StoredProcedureQuery;
import org.hibernate.Session;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.SQLServer2012Dialect;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue;
import org.junit.Before;
import org.junit.Test;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.FailureExpected;
import org.hibernate.testing.orm.junit.Jpa;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.test.procedure.Person;
import org.hibernate.test.procedure.Phone;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.StoredProcedureQuery;
import static org.hibernate.testing.transaction.TransactionUtil.doInAutoCommit;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect(SQLServer2012Dialect.class)
@RequiresDialect(value = SQLServerDialect.class, version = 11)
@TestForIssue( jiraKey = "HHH-12704" )
public class SQLServerStoredProcedureCrossDatabaseTest extends BaseEntityManagerFunctionalTestCase {
@Jpa(
annotatedClasses = {
Person.class,
Phone.class,
}
)
public class SQLServerStoredProcedureCrossDatabaseTest {
private final String DATABASE_NAME_TOKEN = "databaseName=";
private final String DATABASE_NAME = "hibernate_orm_test_sp";
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class,
Phone.class,
};
}
@Before
public void init() {
@BeforeEach
public void init(EntityManagerFactoryScope scope) {
doInAutoCommit(
"DROP DATABASE " + DATABASE_NAME,
"CREATE DATABASE " + DATABASE_NAME
@ -75,11 +75,10 @@ public class SQLServerStoredProcedureCrossDatabaseTest extends BaseEntityManager
}
@Test
@FailureExpected( jiraKey = "HHH-12704", message = "SQL Server JDBC Driver does not support registering name parameters properly")
public void testStoredProcedureViaJPANamedParameters() {
doInJPA( this::entityManagerFactory, entityManager -> {
@FailureExpected( jiraKey = "HHH-12704", reason = "SQL Server JDBC Driver does not support registering name parameters properly")
public void testStoredProcedureViaJPANamedParameters(EntityManagerFactoryScope scope) {
scope.inTransaction( entityManager -> {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( DATABASE_NAME + ".dbo.sp_square_number" );
query.registerStoredProcedureParameter( "inputNumber", Integer.class, ParameterMode.IN );
query.registerStoredProcedureParameter( "outputNumber", Integer.class, ParameterMode.OUT );
query.setParameter( "inputNumber", 7 );
@ -91,8 +90,8 @@ public class SQLServerStoredProcedureCrossDatabaseTest extends BaseEntityManager
}
@Test
public void testStoredProcedureViaJPA() {
doInJPA( this::entityManagerFactory, entityManager -> {
public void testStoredProcedureViaJPA(EntityManagerFactoryScope scope) {
scope.inTransaction( entityManager -> {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( DATABASE_NAME + ".dbo.sp_square_number" );
query.registerStoredProcedureParameter( 1, Integer.class, ParameterMode.IN );
query.registerStoredProcedureParameter( 2, Integer.class, ParameterMode.OUT );
@ -106,9 +105,8 @@ public class SQLServerStoredProcedureCrossDatabaseTest extends BaseEntityManager
}
@Test
public void testStoredProcedureViaJDBC() {
doInJPA( this::entityManagerFactory, entityManager -> {
public void testStoredProcedureViaJDBC(EntityManagerFactoryScope scope) {
scope.inTransaction( entityManager -> {
entityManager.unwrap( Session.class ).doWork( connection -> {
try (CallableStatement storedProcedure = connection.prepareCall(
"{ call " + DATABASE_NAME + ".dbo.sp_square_number(?, ?) }" )) {

View File

@ -0,0 +1,69 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.procedure;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.test.procedure.Person;
import org.hibernate.test.procedure.Phone;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.StoredProcedureQuery;
import static org.hibernate.testing.transaction.TransactionUtil.doInAutoCommit;
import static org.junit.Assert.assertEquals;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect(value = SQLServerDialect.class, version = 11)
@Jpa(
annotatedClasses = {
Person.class,
Phone.class,
}
)
public class SQLServerStoredProcedureCrossSchemaTest {
@BeforeEach
public void init() {
doInAutoCommit(
"DROP PROCEDURE sp_test.sp_square_number",
"DROP SCHEMA sp_test",
"CREATE SCHEMA sp_test",
"CREATE PROCEDURE sp_test.sp_square_number " +
" @inputNumber INT, " +
" @outputNumber INT OUTPUT " +
"AS " +
"BEGIN " +
" SELECT @outputNumber = @inputNumber * @inputNumber; " +
"END"
);
}
@Test
public void testStoredProcedureViaJPA(EntityManagerFactoryScope scope) {
scope.inTransaction( entityManager -> {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_test.sp_square_number" );
query.registerStoredProcedureParameter( "inputNumber", Integer.class, ParameterMode.IN );
query.registerStoredProcedureParameter( "outputNumber", Integer.class, ParameterMode.OUT );
query.setParameter( "inputNumber", 7 );
query.execute();
int result = (int) query.getOutputParameterValue( "outputNumber" );
assertEquals( 49, result );
} );
}
}

View File

@ -0,0 +1,171 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.procedure;
import java.sql.CallableStatement;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.List;
import java.util.regex.Pattern;
import org.hibernate.Session;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.test.procedure.Person;
import org.hibernate.test.procedure.Phone;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.StoredProcedureQuery;
import static org.hibernate.testing.transaction.TransactionUtil.doInAutoCommit;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect(value = SQLServerDialect.class, version = 11)
@Jpa(
annotatedClasses = {
Person.class,
Phone.class,
}
)
public class SQLServerStoredProcedureTest {
@BeforeEach
public void init(EntityManagerFactoryScope scope) {
doInAutoCommit(
"DROP PROCEDURE sp_count_phones",
"DROP FUNCTION fn_count_phones",
"DROP PROCEDURE sp_phones",
"CREATE PROCEDURE sp_count_phones " +
" @personId INT, " +
" @phoneCount INT OUTPUT " +
"AS " +
"BEGIN " +
" SELECT @phoneCount = COUNT(*) " +
" FROM Phone " +
" WHERE person_id = @personId " +
"END",
"CREATE FUNCTION fn_count_phones (@personId INT) " +
"RETURNS INT " +
"AS " +
"BEGIN " +
" DECLARE @phoneCount int; " +
" SELECT @phoneCount = COUNT(*) " +
" FROM Phone " +
" WHERE person_id = @personId; " +
" RETURN(@phoneCount); " +
"END",
"CREATE PROCEDURE sp_phones " +
" @personId INT, " +
" @phones CURSOR VARYING OUTPUT " +
"AS " +
" SET NOCOUNT ON; " +
" SET @phones = CURSOR " +
" FORWARD_ONLY STATIC FOR " +
" SELECT * " +
" FROM Phone " +
" WHERE person_id = @personId; " +
" OPEN @phones;"
);
scope.inTransaction( entityManager -> {
Person person1 = new Person( "John Doe" );
person1.setNickName( "JD" );
person1.setAddress( "Earth" );
person1.setCreatedOn( Timestamp.from( LocalDateTime.of( 2000, 1, 1, 0, 0, 0 )
.toInstant( ZoneOffset.UTC ) ) );
entityManager.persist( person1 );
Phone phone1 = new Phone( "123-456-7890" );
phone1.setId( 1L );
person1.addPhone( phone1 );
Phone phone2 = new Phone( "098_765-4321" );
phone2.setId( 2L );
person1.addPhone( phone2 );
} );
}
@AfterEach
public void tearDown(EntityManagerFactoryScope scope){
scope.releaseEntityManagerFactory();
}
@Test
public void testStoredProcedureOutParameter(EntityManagerFactoryScope scope) {
scope.inTransaction( entityManager -> {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_count_phones" );
query.registerStoredProcedureParameter( "personId", Long.class, ParameterMode.IN );
query.registerStoredProcedureParameter( "phoneCount", Long.class, ParameterMode.OUT );
query.setParameter( "personId", 1L );
query.execute();
Long phoneCount = (Long) query.getOutputParameterValue( "phoneCount" );
assertEquals( Long.valueOf( 2 ), phoneCount );
} );
}
@Test
public void testStoredProcedureRefCursor(EntityManagerFactoryScope scope) {
scope.inTransaction( entityManager -> {
try {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_phones" );
query.registerStoredProcedureParameter( 1, Long.class, ParameterMode.IN );
query.registerStoredProcedureParameter( 2, Class.class, ParameterMode.REF_CURSOR );
query.setParameter( 1, 1L );
query.execute();
List<Object[]> postComments = query.getResultList();
assertNotNull( postComments );
}
catch (Exception e) {
assertTrue( Pattern.compile( "Dialect .*? not known to support REF_CURSOR parameters" )
.matcher( e.getCause().getMessage() )
.matches() );
}
} );
}
@Test
public void testStoredProcedureReturnValue(EntityManagerFactoryScope scope) {
scope.inTransaction( entityManager -> {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
CallableStatement function = null;
try {
function = connection.prepareCall( "{ ? = call fn_count_phones(?) }" );
function.registerOutParameter( 1, Types.INTEGER );
function.setInt( 2, 1 );
function.execute();
int phoneCount = function.getInt( 1 );
assertEquals( 2, phoneCount );
}
finally {
if ( function != null ) {
function.close();
}
}
} );
} );
}
}

View File

@ -4,7 +4,7 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.procedure;
package org.hibernate.orm.test.procedure;
import java.math.BigDecimal;
import java.math.BigInteger;
@ -412,17 +412,11 @@ public class StoredProcedureParameterTypeTest {
public void testStringTypeInParameterIsNullWithoutEnablePassingNulls(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
try {
ProcedureCall procedureCall = session.createStoredProcedureCall( "test" );
procedureCall.registerParameter( 1, StringType.class, ParameterMode.IN );
procedureCall.setParameter( 1, null );
fail("Should have thrown exception");
}
catch (IllegalArgumentException e) {
assertTrue( e.getMessage().endsWith( "You need to call ParameterRegistration#enablePassingNulls(true) in order to pass null parameters." ) );
}
}
);
}
}

View File

@ -8,7 +8,6 @@ import org.hibernate.dialect.H2Dialect;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.hibernate.testing.orm.junit.NotImplementedYet;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
@ -22,7 +21,6 @@ public class ProcedureCallImplTest {
@Test
@TestForIssue( jiraKey = "HHH-13644" )
@RequiresDialect( H2Dialect.class )
@NotImplementedYet(reason = "org.hibernate.procedure.internal.ProcedureCallImpl.buildOutputs not yet implemented")
public void testNoNullPointerExceptionThrown(EntityManagerFactoryScope scope) {
scope.inTransaction( em -> {
em.createNativeQuery("CREATE ALIAS GET_RANDOM_VALUE FOR \"java.lang.Math.random\";").executeUpdate();

View File

@ -26,7 +26,7 @@ import org.hibernate.procedure.ProcedureOutputs;
import org.hibernate.result.ResultSetOutput;
import org.hibernate.testing.orm.junit.BaseSessionFactoryFunctionalTest;
import org.hibernate.testing.orm.junit.NotImplementedYet;
import org.hibernate.testing.orm.junit.FailureExpected;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.junit.jupiter.api.Test;
@ -36,8 +36,7 @@ import static org.hibernate.testing.orm.junit.ExtraAssertions.assertTyping;
* @author Steve Ebersole
*/
@RequiresDialect( value = PostgreSQLDialect.class )
//@FailureExpected( jiraKey = "HHH-8445", reason = "Waiting on EG clarification" )
@NotImplementedYet(reason = "org.hibernate.procedure.internal.ProcedureCallImpl.buildOutputs not yet implemented")
@FailureExpected( jiraKey = "HHH-8445", reason = "Waiting on EG clarification" )
public class PostgresRefCursorSupportTest extends BaseSessionFactoryFunctionalTest {
public static class ProcedureDefinitions implements AuxiliaryDatabaseObject, AuxiliaryDatabaseObject.Expandable {

View File

@ -14,7 +14,6 @@ import org.hibernate.result.Output;
import org.hibernate.result.ResultSetOutput;
import org.hibernate.testing.orm.junit.BaseSessionFactoryFunctionalTest;
import org.hibernate.testing.orm.junit.NotImplementedYet;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.junit.jupiter.api.Test;
@ -26,7 +25,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
* @author Steve Ebersole
*/
@RequiresDialect( H2Dialect.class )
@NotImplementedYet(reason = "org.hibernate.procedure.internal.ProcedureCallImpl.buildOutputs not yet implemented")
public class ResultMappingTest extends BaseSessionFactoryFunctionalTest {
@Override

View File

@ -26,7 +26,6 @@ import org.hibernate.result.ResultSetOutput;
import org.hibernate.testing.orm.junit.BaseSessionFactoryFunctionalTest;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.testing.orm.junit.NotImplementedYet;
import org.junit.jupiter.api.Test;
@ -37,7 +36,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
* @author Steve Ebersole
*/
@RequiresDialect( H2Dialect.class )
@NotImplementedYet(reason = "org.hibernate.procedure.internal.ProcedureCallImpl.buildOutputs not yet implemented")
public class StoredProcedureResultSetMappingTest extends BaseSessionFactoryFunctionalTest {
@Entity( name = "Employee" )
@Table( name = "EMP" )

View File

@ -15,7 +15,6 @@ import org.hibernate.procedure.ProcedureOutputs;
import org.hibernate.result.Output;
import org.hibernate.result.ResultSetOutput;
import org.hibernate.testing.orm.junit.NotImplementedYet;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.testing.orm.junit.BaseSessionFactoryFunctionalTest;
import org.junit.jupiter.api.Test;
@ -29,7 +28,6 @@ import static org.junit.jupiter.api.Assertions.fail;
* @author Steve Ebersole
*/
@RequiresDialect(H2Dialect.class)
@NotImplementedYet(reason = "org.hibernate.procedure.internal.ProcedureCallImpl.buildOutputs not yet implemented")
public class StoredProcedureTest extends BaseSessionFactoryFunctionalTest {
@Override

View File

@ -1,429 +0,0 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.procedure;
import java.sql.CallableStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
import jakarta.persistence.EntityManager;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.StoredProcedureQuery;
import org.hibernate.Session;
import org.hibernate.dialect.MySQL5Dialect;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.procedure.ProcedureCall;
import org.hibernate.query.procedure.ProcedureParameter;
import org.hibernate.result.Output;
import org.hibernate.result.ResultSetOutput;
import org.hibernate.type.StringType;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect(MySQL5Dialect.class)
public class MySQLStoredProcedureTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class,
Phone.class,
};
}
@Before
public void init() {
destroy();
EntityManager entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
Statement statement = null;
try {
statement = connection.createStatement();
statement.executeUpdate(
"CREATE PROCEDURE sp_count_phones (" +
" IN personId INT, " +
" OUT phoneCount INT " +
") " +
"BEGIN " +
" SELECT COUNT(*) INTO phoneCount " +
" FROM Phone p " +
" WHERE p.person_id = personId; " +
"END"
);
statement.executeUpdate(
"CREATE PROCEDURE sp_phones(IN personId INT) " +
"BEGIN " +
" SELECT * " +
" FROM Phone " +
" WHERE person_id = personId; " +
"END"
);
statement.executeUpdate(
"CREATE FUNCTION fn_count_phones(personId integer) " +
"RETURNS integer " +
"DETERMINISTIC " +
"READS SQL DATA " +
"BEGIN " +
" DECLARE phoneCount integer; " +
" SELECT COUNT(*) INTO phoneCount " +
" FROM Phone p " +
" WHERE p.person_id = personId; " +
" RETURN phoneCount; " +
"END"
);
statement.executeUpdate(
"CREATE PROCEDURE sp_is_null (" +
" IN param varchar(255), " +
" OUT result tinyint(1) " +
") " +
"BEGIN " +
" IF (param IS NULL) THEN SET result = true; " +
" ELSE SET result = false; " +
" END IF; " +
"END"
);
} finally {
if ( statement != null ) {
statement.close();
}
}
} );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
Person person1 = new Person( "John Doe" );
person1.setNickName( "JD" );
person1.setAddress( "Earth" );
person1.setCreatedOn( Timestamp.from( LocalDateTime.of( 2000, 1, 1, 0, 0, 0 ).toInstant( ZoneOffset.UTC ) ) );
entityManager.persist( person1 );
Phone phone1 = new Phone( "123-456-7890" );
phone1.setId( 1L );
person1.addPhone( phone1 );
Phone phone2 = new Phone( "098_765-4321" );
phone2.setId( 2L );
person1.addPhone( phone2 );
entityManager.getTransaction().commit();
}
finally {
entityManager.close();
}
}
@After
public void destroy() {
EntityManager entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try (Statement statement = connection.createStatement()) {
statement.executeUpdate( "DROP PROCEDURE IF EXISTS sp_count_phones" );
}
catch (SQLException ignore) {
}
} );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try (Statement statement = connection.createStatement()) {
statement.executeUpdate( "DROP PROCEDURE IF EXISTS sp_phones" );
}
catch (SQLException ignore) {
}
} );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try (Statement statement = connection.createStatement()) {
statement.executeUpdate( "DROP FUNCTION IF EXISTS fn_count_phones" );
}
catch (SQLException ignore) {
}
} );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try (Statement statement = connection.createStatement()) {
statement.executeUpdate( "DROP PROCEDURE IF EXISTS sp_is_null" );
}
catch (SQLException ignore) {
}
} );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
}
@Test
public void testStoredProcedureOutParameter() {
EntityManager entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_count_phones" );
query.registerStoredProcedureParameter( "personId", Long.class, ParameterMode.IN );
query.registerStoredProcedureParameter( "phoneCount", Long.class, ParameterMode.OUT );
query.setParameter( "personId", 1L );
query.execute();
Long phoneCount = (Long) query.getOutputParameterValue( "phoneCount" );
assertEquals( Long.valueOf( 2 ), phoneCount );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
}
@Test
public void testHibernateProcedureCallOutParameter() {
EntityManager entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
ProcedureCall call = session.createStoredProcedureCall( "sp_count_phones" );
final ProcedureParameter<Long> inParam = call.registerParameter(
"personId",
Long.class,
ParameterMode.IN
);
call.registerParameter( "phoneCount", Long.class, ParameterMode.OUT );
call.setParameter( inParam, 1L );
Long phoneCount = (Long) call.getOutputs().getOutputParameterValue( "phoneCount" );
assertEquals( Long.valueOf( 2 ), phoneCount );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
}
@Test
public void testStoredProcedureRefCursor() {
EntityManager entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_phones" );
query.registerStoredProcedureParameter( 1, void.class, ParameterMode.REF_CURSOR );
query.registerStoredProcedureParameter( 2, Long.class, ParameterMode.IN );
query.setParameter( 2, 1L );
List<Object[]> personComments = query.getResultList();
assertEquals( 2, personComments.size() );
}
catch (Exception e) {
assertTrue( Pattern.compile( "Dialect .*? not known to support REF_CURSOR parameters" )
.matcher( e.getCause().getMessage() )
.matches() );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
}
@Test
public void testStoredProcedureReturnValue() {
EntityManager entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_phones" );
query.registerStoredProcedureParameter( 1, Long.class, ParameterMode.IN );
query.setParameter( 1, 1L );
List<Object[]> personComments = query.getResultList();
assertEquals( 2, personComments.size() );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
}
@Test
public void testHibernateProcedureCallReturnValueParameter() {
EntityManager entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
ProcedureCall call = session.createStoredProcedureCall( "sp_phones" );
final ProcedureParameter<Long> parameter = call.registerParameter(
1,
Long.class,
ParameterMode.IN
);
call.setParameter( parameter, 1L );
Output output = call.getOutputs().getCurrent();
List<Object[]> personComments = ( (ResultSetOutput) output ).getResultList();
assertEquals( 2, personComments.size() );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
}
@Test
public void testFunctionWithJDBC() {
EntityManager entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
final AtomicReference<Integer> phoneCount = new AtomicReference<>();
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try (CallableStatement function = connection.prepareCall(
"{ ? = call fn_count_phones(?) }" )) {
function.registerOutParameter( 1, Types.INTEGER );
function.setInt( 2, 1 );
function.execute();
phoneCount.set( function.getInt( 1 ) );
}
} );
assertEquals( Integer.valueOf( 2 ), phoneCount.get() );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
}
@Test
@TestForIssue( jiraKey = "HHH-12905")
public void testStoredProcedureNullParameter() {
doInJPA( this::entityManagerFactory, entityManager -> {
ProcedureCall procedureCall = entityManager.unwrap( Session.class ).createStoredProcedureCall("sp_is_null");
procedureCall.registerParameter( 1, StringType.class, ParameterMode.IN).enablePassingNulls( true);
procedureCall.registerParameter( 2, Boolean.class, ParameterMode.OUT);
procedureCall.setParameter(1, null);
Boolean result = (Boolean) procedureCall.getOutputParameterValue( 2 );
assertTrue( result );
});
doInJPA( this::entityManagerFactory, entityManager -> {
ProcedureCall procedureCall = entityManager.unwrap( Session.class ).createStoredProcedureCall("sp_is_null");
procedureCall.registerParameter( 1, StringType.class, ParameterMode.IN).enablePassingNulls( true);
procedureCall.registerParameter( 2, Boolean.class, ParameterMode.OUT);
procedureCall.setParameter(1, "test");
Boolean result = (Boolean) procedureCall.getOutputParameterValue( 2 );
assertFalse( result );
});
}
@Test
@TestForIssue( jiraKey = "HHH-12905")
public void testStoredProcedureNullParameterHibernateWithoutEnablePassingNulls() {
doInJPA( this::entityManagerFactory, entityManager -> {
try {
ProcedureCall procedureCall = entityManager.unwrap( Session.class ).createStoredProcedureCall("sp_is_null");
procedureCall.registerParameter( 1, StringType.class, ParameterMode.IN);
procedureCall.registerParameter( 2, Boolean.class, ParameterMode.OUT);
procedureCall.setParameter(1, null);
procedureCall.getOutputParameterValue( 2 );
fail("Should have thrown exception");
}
catch (IllegalArgumentException e) {
assertEquals( "The parameter at position [1] was null. You need to call ParameterRegistration#enablePassingNulls(true) in order to pass null parameters.", e.getMessage() );
}
});
}
}

View File

@ -1,542 +0,0 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.procedure;
import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.List;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Id;
import jakarta.persistence.NamedStoredProcedureQueries;
import jakarta.persistence.NamedStoredProcedureQuery;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.StoredProcedureParameter;
import jakarta.persistence.StoredProcedureQuery;
import org.hibernate.Session;
import org.hibernate.dialect.Oracle8iDialect;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.procedure.ProcedureCall;
import org.hibernate.query.procedure.ProcedureParameter;
import org.hibernate.result.Output;
import org.hibernate.result.ResultSetOutput;
import org.hibernate.type.NumericBooleanType;
import org.hibernate.type.YesNoType;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect(Oracle8iDialect.class)
public class OracleStoredProcedureTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class,
Phone.class,
IdHolder.class,
Vote.class
};
}
@NamedStoredProcedureQueries({
@NamedStoredProcedureQuery(
name = "singleRefCursor",
procedureName = "singleRefCursor",
parameters = {
@StoredProcedureParameter(mode = ParameterMode.REF_CURSOR, type = void.class)
}
),
@NamedStoredProcedureQuery(
name = "outAndRefCursor",
procedureName = "outAndRefCursor",
parameters = {
@StoredProcedureParameter(mode = ParameterMode.REF_CURSOR, type = void.class),
@StoredProcedureParameter(mode = ParameterMode.OUT, type = Long.class),
}
)
})
@Entity(name = "IdHolder")
public static class IdHolder {
@Id
Long id;
}
@Before
public void init() {
EntityManager entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
Statement statement = null;
try {
statement = connection.createStatement();
statement.executeUpdate(
"CREATE OR REPLACE PROCEDURE sp_count_phones ( " +
" personId IN NUMBER, " +
" phoneCount OUT NUMBER ) " +
"AS " +
"BEGIN " +
" SELECT COUNT(*) INTO phoneCount " +
" FROM phone " +
" WHERE person_id = personId; " +
"END;"
);
statement.executeUpdate(
"CREATE OR REPLACE PROCEDURE sp_person_phones ( " +
" personId IN NUMBER, " +
" personPhones OUT SYS_REFCURSOR ) " +
"AS " +
"BEGIN " +
" OPEN personPhones FOR " +
" SELECT *" +
" FROM phone " +
" WHERE person_id = personId; " +
"END;"
);
statement.executeUpdate(
"CREATE OR REPLACE FUNCTION fn_count_phones ( " +
" personId IN NUMBER ) " +
" RETURN NUMBER " +
"IS " +
" phoneCount NUMBER; " +
"BEGIN " +
" SELECT COUNT(*) INTO phoneCount " +
" FROM phone " +
" WHERE person_id = personId; " +
" RETURN( phoneCount ); " +
"END;"
);
statement.executeUpdate(
"CREATE OR REPLACE FUNCTION fn_person_and_phones ( " +
" personId IN NUMBER ) " +
" RETURN SYS_REFCURSOR " +
"IS " +
" personAndPhones SYS_REFCURSOR; " +
"BEGIN " +
" OPEN personAndPhones FOR " +
" SELECT " +
" pr.id AS \"pr.id\", " +
" pr.name AS \"pr.name\", " +
" pr.nickName AS \"pr.nickName\", " +
" pr.address AS \"pr.address\", " +
" pr.createdOn AS \"pr.createdOn\", " +
" pr.version AS \"pr.version\", " +
" ph.id AS \"ph.id\", " +
" ph.person_id AS \"ph.person_id\", " +
" ph.phone_number AS \"ph.phone_number\", " +
" ph.valid AS \"ph.valid\" " +
" FROM person pr " +
" JOIN phone ph ON pr.id = ph.person_id " +
" WHERE pr.id = personId; " +
" RETURN personAndPhones; " +
"END;"
);
statement.executeUpdate(
"CREATE OR REPLACE " +
"PROCEDURE singleRefCursor(p_recordset OUT SYS_REFCURSOR) AS " +
" BEGIN " +
" OPEN p_recordset FOR " +
" SELECT 1 as id " +
" FROM dual; " +
" END; "
);
statement.executeUpdate(
"CREATE OR REPLACE " +
"PROCEDURE outAndRefCursor(p_recordset OUT SYS_REFCURSOR, p_value OUT NUMBER) AS " +
" BEGIN " +
" OPEN p_recordset FOR " +
" SELECT 1 as id " +
" FROM dual; " +
" SELECT 1 INTO p_value FROM dual; " +
" END; "
);
statement.executeUpdate(
"CREATE OR REPLACE PROCEDURE sp_phone_validity ( " +
" validity IN NUMBER, " +
" personPhones OUT SYS_REFCURSOR ) " +
"AS " +
"BEGIN " +
" OPEN personPhones FOR " +
" SELECT phone_number " +
" FROM phone " +
" WHERE valid = validity; " +
"END;"
);
statement.executeUpdate(
"CREATE OR REPLACE PROCEDURE sp_votes ( " +
" validity IN CHAR, " +
" votes OUT SYS_REFCURSOR ) " +
"AS " +
"BEGIN " +
" OPEN votes FOR " +
" SELECT id " +
" FROM vote " +
" WHERE vote_choice = validity; " +
"END;"
);
} finally {
if ( statement != null ) {
statement.close();
}
}
} );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
Person person1 = new Person("John Doe" );
person1.setNickName( "JD" );
person1.setAddress( "Earth" );
person1.setCreatedOn( Timestamp.from( LocalDateTime.of( 2000, 1, 1, 0, 0, 0 ).toInstant( ZoneOffset.UTC ) )) ;
entityManager.persist(person1);
Phone phone1 = new Phone( "123-456-7890" );
phone1.setId( 1L );
phone1.setValid( true );
person1.addPhone( phone1 );
Phone phone2 = new Phone( "098_765-4321" );
phone2.setId( 2L );
phone2.setValid( false );
person1.addPhone( phone2 );
entityManager.getTransaction().commit();
}
finally {
entityManager.close();
}
}
@After
public void destroy() {
EntityManager entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try(Statement statement = connection.createStatement()) {
statement.executeUpdate("DROP PROCEDURE sp_count_phones");
}
catch (SQLException ignore) {
}
} );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try(Statement statement = connection.createStatement()) {
statement.executeUpdate("DROP PROCEDURE sp_person_phones");
}
catch (SQLException ignore) {
}
} );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try(Statement statement = connection.createStatement()) {
statement.executeUpdate("DROP FUNCTION fn_count_phones");
}
catch (SQLException ignore) {
}
} );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
}
@Test
public void testStoredProcedureOutParameter() {
EntityManager entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery("sp_count_phones");
query.registerStoredProcedureParameter(1, Long.class, ParameterMode.IN);
query.registerStoredProcedureParameter(2, Long.class, ParameterMode.OUT);
query.setParameter(1, 1L);
query.execute();
Long phoneCount = (Long) query.getOutputParameterValue(2);
assertEquals(Long.valueOf(2), phoneCount);
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
}
@Test
public void testStoredProcedureRefCursor() {
EntityManager entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_person_phones" );
query.registerStoredProcedureParameter( 1, Long.class, ParameterMode.IN );
query.registerStoredProcedureParameter( 2, Class.class, ParameterMode.REF_CURSOR );
query.setParameter( 1, 1L );
query.execute();
List<Object[]> postComments = query.getResultList();
assertNotNull( postComments );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
}
@Test
public void testHibernateProcedureCallRefCursor() {
EntityManager entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap(Session.class);
ProcedureCall call = session.createStoredProcedureCall( "sp_person_phones");
final ProcedureParameter<Long> inParam = call.registerParameter(
1,
Long.class,
ParameterMode.IN
);
call.setParameter( inParam, 1L );
call.registerParameter(2, Class.class, ParameterMode.REF_CURSOR);
Output output = call.getOutputs().getCurrent();
List<Object[]> postComments = ( (ResultSetOutput) output ).getResultList();
assertEquals(2, postComments.size());
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
}
@Test
public void testStoredProcedureReturnValue() {
EntityManager entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
BigDecimal phoneCount = (BigDecimal) entityManager
.createNativeQuery("SELECT fn_count_phones(:personId) FROM DUAL")
.setParameter("personId", 1)
.getSingleResult();
assertEquals(BigDecimal.valueOf(2), phoneCount);
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
}
@Test
public void testNamedNativeQueryStoredProcedureRefCursor() {
EntityManager entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
List<Object[]> postAndComments = entityManager
.createNamedQuery(
"fn_person_and_phones")
.setParameter(1, 1L)
.getResultList();
Object[] postAndComment = postAndComments.get(0);
Person person = (Person) postAndComment[0];
Phone phone = (Phone) postAndComment[1];
assertEquals(2, postAndComments.size());
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
}
@Test
public void testNamedNativeQueryStoredProcedureRefCursorWithJDBC() {
EntityManager entityManager = createEntityManager();
entityManager.getTransaction().begin();
try {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try (CallableStatement function = connection.prepareCall(
"{ ? = call fn_person_and_phones( ? ) }" )) {
try {
function.registerOutParameter( 1, Types.REF_CURSOR );
}
catch ( SQLException e ) {
//OracleTypes.CURSOR
function.registerOutParameter( 1, -10 );
}
function.setInt( 2, 1 );
function.execute();
try (ResultSet resultSet = (ResultSet) function.getObject( 1);) {
while (resultSet.next()) {
Long postCommentId = resultSet.getLong(1);
String review = resultSet.getString(2);
}
}
}
} );
}
finally {
entityManager.getTransaction().rollback();
entityManager.close();
}
}
@Test
@TestForIssue( jiraKey = "HHH-11863")
public void testSysRefCursorAsOutParameter() {
doInJPA( this::entityManagerFactory, entityManager -> {
StoredProcedureQuery function = entityManager.createNamedStoredProcedureQuery("singleRefCursor");
function.execute();
assertFalse( function.hasMoreResults() );
Long value = null;
try ( ResultSet resultSet = (ResultSet) function.getOutputParameterValue( 1 ) ) {
while ( resultSet.next() ) {
value = resultSet.getLong( 1 );
}
}
catch (SQLException e) {
fail(e.getMessage());
}
assertEquals( Long.valueOf( 1 ), value );
} );
}
@Test
@TestForIssue( jiraKey = "HHH-11863")
public void testOutAndSysRefCursorAsOutParameter() {
doInJPA( this::entityManagerFactory, entityManager -> {
StoredProcedureQuery function = entityManager.createNamedStoredProcedureQuery("outAndRefCursor");
function.execute();
assertFalse( function.hasMoreResults() );
Long value = null;
try ( ResultSet resultSet = (ResultSet) function.getOutputParameterValue( 1 ) ) {
while ( resultSet.next() ) {
value = resultSet.getLong( 1 );
}
}
catch (SQLException e) {
fail(e.getMessage());
}
assertEquals( value, function.getOutputParameterValue( 2 ) );
} );
}
@Test
@TestForIssue( jiraKey = "HHH-12661")
public void testBindParameterAsHibernateType() {
doInJPA( this::entityManagerFactory, entityManager -> {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery("sp_phone_validity")
.registerStoredProcedureParameter( 1, NumericBooleanType.class, ParameterMode.IN )
.registerStoredProcedureParameter( 2, Class.class, ParameterMode.REF_CURSOR )
.setParameter( 1, true );
query.execute();
List phones = query.getResultList();
assertEquals( 1, phones.size() );
assertEquals( "123-456-7890", phones.get( 0 ) );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Vote vote1 = new Vote();
vote1.setId( 1L );
vote1.setVoteChoice( true );
entityManager.persist( vote1 );
Vote vote2 = new Vote();
vote2.setId( 2L );
vote2.setVoteChoice( false );
entityManager.persist( vote2 );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery("sp_votes")
.registerStoredProcedureParameter( 1, YesNoType.class, ParameterMode.IN )
.registerStoredProcedureParameter( 2, Class.class, ParameterMode.REF_CURSOR )
.setParameter( 1, true );
query.execute();
List votes = query.getResultList();
assertEquals( 1, votes.size() );
assertEquals( 1, ((Number) votes.get( 0 )).intValue() );
} );
}
}

View File

@ -1,69 +0,0 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.procedure;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.StoredProcedureQuery;
import org.hibernate.dialect.SQLServer2012Dialect;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.RequiresDialect;
import org.junit.Before;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInAutoCommit;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect(SQLServer2012Dialect.class)
public class SQLServerStoredProcedureCrossSchemaTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class,
Phone.class,
};
}
@Before
public void init() {
doInAutoCommit(
"DROP PROCEDURE sp_test.sp_square_number",
"DROP SCHEMA sp_test",
"CREATE SCHEMA sp_test",
"CREATE PROCEDURE sp_test.sp_square_number " +
" @inputNumber INT, " +
" @outputNumber INT OUTPUT " +
"AS " +
"BEGIN " +
" SELECT @outputNumber = @inputNumber * @inputNumber; " +
"END"
);
}
@Test
public void testStoredProcedureViaJPA() {
doInJPA( this::entityManagerFactory, entityManager -> {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery("sp_test.sp_square_number");
query.registerStoredProcedureParameter("inputNumber", Integer.class, ParameterMode.IN);
query.registerStoredProcedureParameter("outputNumber", Integer.class, ParameterMode.OUT);
query.setParameter("inputNumber", 7);
query.execute();
int result = (int) query.getOutputParameterValue("outputNumber");
assertEquals( 49, result );
} );
}
}

View File

@ -1,162 +0,0 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.procedure;
import java.sql.CallableStatement;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.List;
import java.util.regex.Pattern;
import jakarta.persistence.EntityManager;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.StoredProcedureQuery;
import org.hibernate.Session;
import org.hibernate.dialect.SQLServer2012Dialect;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.RequiresDialect;
import org.junit.Before;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInAutoCommit;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect(SQLServer2012Dialect.class)
public class SQLServerStoredProcedureTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class,
Phone.class,
};
}
@Before
public void init() {
doInAutoCommit(
"DROP PROCEDURE sp_count_phones",
"DROP FUNCTION fn_count_phones",
"DROP PROCEDURE sp_phones",
"CREATE PROCEDURE sp_count_phones " +
" @personId INT, " +
" @phoneCount INT OUTPUT " +
"AS " +
"BEGIN " +
" SELECT @phoneCount = COUNT(*) " +
" FROM Phone " +
" WHERE person_id = @personId " +
"END",
"CREATE FUNCTION fn_count_phones (@personId INT) " +
"RETURNS INT " +
"AS " +
"BEGIN " +
" DECLARE @phoneCount int; " +
" SELECT @phoneCount = COUNT(*) " +
" FROM Phone " +
" WHERE person_id = @personId; " +
" RETURN(@phoneCount); " +
"END",
"CREATE PROCEDURE sp_phones " +
" @personId INT, " +
" @phones CURSOR VARYING OUTPUT " +
"AS " +
" SET NOCOUNT ON; " +
" SET @phones = CURSOR " +
" FORWARD_ONLY STATIC FOR " +
" SELECT * " +
" FROM Phone " +
" WHERE person_id = @personId; " +
" OPEN @phones;"
);
doInJPA( this::entityManagerFactory, entityManager -> {
Person person1 = new Person( "John Doe" );
person1.setNickName( "JD" );
person1.setAddress( "Earth" );
person1.setCreatedOn( Timestamp.from( LocalDateTime.of( 2000, 1, 1, 0, 0, 0 ).toInstant( ZoneOffset.UTC ) ) );
entityManager.persist( person1 );
Phone phone1 = new Phone( "123-456-7890" );
phone1.setId( 1L );
person1.addPhone( phone1 );
Phone phone2 = new Phone( "098_765-4321" );
phone2.setId( 2L );
person1.addPhone( phone2 );
} );
}
@Test
public void testStoredProcedureOutParameter() {
doInJPA( this::entityManagerFactory, entityManager -> {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery("sp_count_phones");
query.registerStoredProcedureParameter("personId", Long.class, ParameterMode.IN);
query.registerStoredProcedureParameter("phoneCount", Long.class, ParameterMode.OUT);
query.setParameter("personId", 1L);
query.execute();
Long phoneCount = (Long) query.getOutputParameterValue("phoneCount");
assertEquals(Long.valueOf(2), phoneCount);
} );
}
@Test
public void testStoredProcedureRefCursor() {
doInJPA( this::entityManagerFactory, entityManager -> {
try {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery("sp_phones");
query.registerStoredProcedureParameter(1, Long.class, ParameterMode.IN);
query.registerStoredProcedureParameter(2, Class.class, ParameterMode.REF_CURSOR);
query.setParameter(1, 1L);
query.execute();
List<Object[]> postComments = query.getResultList();
assertNotNull(postComments);
}
catch (Exception e) {
assertTrue( Pattern.compile( "Dialect .*? not known to support REF_CURSOR parameters").matcher( e.getCause().getMessage()).matches());
}
} );
}
@Test
public void testStoredProcedureReturnValue() {
doInJPA( this::entityManagerFactory, entityManager -> {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
CallableStatement function = null;
try {
function = connection.prepareCall("{ ? = call fn_count_phones(?) }");
function.registerOutParameter(1, Types.INTEGER);
function.setInt(2, 1);
function.execute();
int phoneCount = function.getInt(1);
assertEquals(2, phoneCount);
}
finally {
if ( function != null ) {
function.close();
}
}
} );
} );
}
}

View File

@ -51,6 +51,9 @@ Discuss `@JavaType`, `@JavaTypeRegistration`
Multiple component mappings for the same java class with different property mappings is not supported anymore. Every property mapping combination should have its own java class"
=== Procedure Parameters, enable passing nulls
Passing null or not is now triggered by whether setting the parameter was called at all. In other ords a distinction is made between calling `setParameter` passing null versus not calling `setParameter` at all. In the first case, we pass along the null value; in the second we do not.
=== SQM