Implementing Procedure Call
This commit is contained in:
parent
6931635c4f
commit
95486ce5b1
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
// );
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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(){
|
||||
};
|
||||
|
|
|
@ -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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
|
@ -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() );
|
||||
} );
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
} );
|
||||
}
|
|
@ -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(?, ?) }" )) {
|
|
@ -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 );
|
||||
} );
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
} );
|
||||
} );
|
||||
}
|
||||
}
|
|
@ -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." ) );
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" )
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() );
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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() );
|
||||
} );
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
} );
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
} );
|
||||
} );
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue