Add REF_CURSOR support for stored procedures.

When registered procedure output parameters, if the method annotated with @Procedure has a collection return type or an entity return type, then use ResultSet. Otherwise, handle either the array (Object[]) or the primitive types.

* Throw a proper exception when using an @Procedure-annotated method and no transaction is active.
* Introduce refCursor as a boolean flag to @Procedure denoting when to use REF_CURSORs for OUT parameters..
* Extract a ProcedureParameter command object to ease usage of a parameter's name, type and mode. (NOTE: javax.persistence already has "StoredProcedureParameter".)

NOTE: Integration testing of stored procedures is very limited by lack of HSQL's support for REF CURSORS. See ##2256 to track future work for potential testing against MySQL, Oracle, Postgres, or SQL Server.

See: #1959, #409.
Related: #2014.
This commit is contained in:
Gabriel Basilio Brito
2020-01-14 15:30:15 -04:00
committed by Greg L. Turnquist
parent 2e30a353c9
commit 554bd3d6ed
10 changed files with 366 additions and 134 deletions

View File

@@ -53,6 +53,7 @@ import org.springframework.util.ReflectionUtils;
* @author Christoph Strobl
* @author Nicolas Cirigliano
* @author Jens Schauder
* @author Gabriel Basilio
*/
public abstract class JpaQueryExecution {
@@ -301,6 +302,8 @@ public abstract class JpaQueryExecution {
*/
static class ProcedureExecution extends JpaQueryExecution {
private static final String NO_SURROUNDING_TRANSACTION = "You're trying to execute a @Procedure method without a surrounding transaction that keeps the connection open so that the ResultSet can actually be consumed. Make sure the consumer code uses @Transactional or any other way of declaring a (read-only) transaction.";
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.JpaQueryExecution#doExecute(org.springframework.data.jpa.repository.query.AbstractJpaQuery, java.lang.Object[])
@@ -312,7 +315,24 @@ public abstract class JpaQueryExecution {
StoredProcedureJpaQuery storedProcedureJpaQuery = (StoredProcedureJpaQuery) jpaQuery;
StoredProcedureQuery storedProcedure = storedProcedureJpaQuery.createQuery(accessor);
storedProcedure.execute();
boolean returnsResultSet = storedProcedure.execute();
if (returnsResultSet) {
if (!SurroundingTransactionDetectorMethodInterceptor.INSTANCE.isSurroundingTransactionActive())
throw new InvalidDataAccessApiUsageException(NO_SURROUNDING_TRANSACTION);
List<?> result = storedProcedure.getResultList();
if (!storedProcedureJpaQuery.getQueryMethod().isCollectionQuery()) {
if (result.isEmpty())
return null;
if (result.size() == 1)
return result.get(0);
}
return result;
}
return storedProcedureJpaQuery.extractOutputValue(storedProcedure);
}

View File

@@ -26,6 +26,7 @@ import java.lang.annotation.Target;
* @author Thomas Darimont
* @author Oliver Gierke
* @author Christoph Strobl
* @author Gabriel Basilio
* @since 1.6
*/
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@@ -51,4 +52,9 @@ public @interface Procedure {
* The name of the outputParameter, defaults to {@code ""}.
*/
String outputParameterName() default "";
/**
* Whether the procedure returns a Ref Cursor from the database {@code false}.
*/
boolean refCursor() default false;
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.query;
import org.springframework.lang.Nullable;
import javax.persistence.ParameterMode;
/**
* This class represents a Stored Procedure Parameter
* and an instance of the annotation {@link javax.persistence.StoredProcedureParameter}.
*
* @author Gabriel Basilio
*/
public class ProcedureParameter {
private final String name;
private final ParameterMode mode;
private final Class<?> type;
public ProcedureParameter(@Nullable String name, ParameterMode mode, Class<?> type) {
this.name = name;
this.mode = mode;
this.type = type;
}
public String getName() {
return name;
}
public ParameterMode getMode() {
return mode;
}
public Class<?> getType() {
return type;
}
}

View File

@@ -22,6 +22,7 @@ import java.util.List;
import javax.persistence.NamedStoredProcedureQueries;
import javax.persistence.NamedStoredProcedureQuery;
import javax.persistence.ParameterMode;
import javax.persistence.StoredProcedureParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
@@ -38,6 +39,7 @@ import org.springframework.util.StringUtils;
* @author Mark Paluch
* @author Diego Diez
* @author Jeff Sheets
* @author Gabriel Basilio
* @since 1.6
*/
enum StoredProcedureAttributeSource {
@@ -72,7 +74,7 @@ enum StoredProcedureAttributeSource {
+ method);
}
return new StoredProcedureAttributes(procedureName, procedure.outputParameterName(), method.getReturnType());
return new StoredProcedureAttributes(procedureName, createOutputProcedureParameterFrom(method, procedure));
}
/**
@@ -102,28 +104,29 @@ enum StoredProcedureAttributeSource {
private StoredProcedureAttributes newProcedureAttributesFrom(Method method,
NamedStoredProcedureQuery namedStoredProc, Procedure procedure) {
List<String> outputParameterNames = new ArrayList<>();
List<Class<?>> outputParameterTypes = new ArrayList<>();
List<ProcedureParameter> outputParameters = new ArrayList<>();
if (!procedure.outputParameterName().isEmpty()) {
// we give the output parameter definition from the @Procedure annotation precedence
outputParameterNames.add(procedure.outputParameterName());
outputParameters.add(createOutputProcedureParameterFrom(method, procedure));
} else {
// try to discover the output parameter
List<StoredProcedureParameter> outputParameters = extractOutputParametersFrom(namedStoredProc);
List<StoredProcedureParameter> namedProcedureOutputParameters = extractOutputParametersFrom(namedStoredProc);
for (StoredProcedureParameter outputParameter : outputParameters) {
outputParameterNames.add(outputParameter.name());
outputParameterTypes.add(outputParameter.type());
for (StoredProcedureParameter outputParameter : namedProcedureOutputParameters) {
outputParameters.add(new ProcedureParameter(
outputParameter.name(), outputParameter.mode(), outputParameter.type()));
}
}
if (outputParameterTypes.isEmpty()) {
outputParameterTypes.add(method.getReturnType());
}
return new StoredProcedureAttributes(namedStoredProc.name(), outputParameters, true);
}
return new StoredProcedureAttributes(namedStoredProc.name(), outputParameterNames, outputParameterTypes, true);
private ProcedureParameter createOutputProcedureParameterFrom(Method method, Procedure procedure) {
return new ProcedureParameter(procedure.outputParameterName(),
procedure.refCursor() ? ParameterMode.REF_CURSOR : ParameterMode.OUT,
method.getReturnType());
}
private List<StoredProcedureParameter> extractOutputParametersFrom(NamedStoredProcedureQuery namedStoredProc) {

View File

@@ -33,6 +33,7 @@ import java.util.stream.IntStream;
* @author Mark Paluch
* @author Jeff Sheets
* @author Jens Schauder
* @author Gabriel Basilio
* @since 1.6
*/
class StoredProcedureAttributes {
@@ -42,52 +43,51 @@ class StoredProcedureAttributes {
private final boolean namedStoredProcedure;
private final String procedureName;
private final List<String> outputParameterNames;
private final List<Class<?>> outputParameterTypes;
private final List<ProcedureParameter> outputProcedureParameters;
/**
* Creates a new {@link StoredProcedureAttributes}.
*
* @param procedureName must not be {@literal null}.
* @param outputParameterName may be {@literal null}.
* @param outputParameterType must not be {@literal null}.
*/
StoredProcedureAttributes(String procedureName, @Nullable String outputParameterName,
Class<?> outputParameterType) {
this(procedureName, Collections.singletonList(outputParameterName), Collections.singletonList(outputParameterType), false);
StoredProcedureAttributes(String procedureName, ProcedureParameter parameter) {
this(procedureName, Collections.singletonList(parameter), false);
}
/**
* Creates a new {@link StoredProcedureAttributes}.
*
* @param procedureName must not be {@literal null}.
* @param outputParameterNames may be empty, but not {@literal null}.
* @param outputParameterTypes must not be empty, and cannot be a single element of {@literal null}.
* @param namedStoredProcedure flag signaling if the stored procedure has a name.
*/
StoredProcedureAttributes(String procedureName, List<String> outputParameterNames,
List<Class<?>> outputParameterTypes, boolean namedStoredProcedure) {
StoredProcedureAttributes(String procedureName, List<ProcedureParameter> outputProcedureParameters, boolean namedStoredProcedure) {
Assert.notNull(procedureName, "ProcedureName must not be null!");
Assert.notNull(outputParameterNames, "OutputParameterNames must not be null!");
Assert.notEmpty(outputParameterTypes, "OutputParameterTypes must not be empty!");
Assert.isTrue(outputParameterTypes.size() != 1 || outputParameterTypes.get(0) != null, "OutputParameterTypes must not have size 1 with a null value");
Assert.notNull(outputProcedureParameters, "OutputProcedureParameters must not be null!");
Assert.isTrue(outputProcedureParameters.size() != 1 || outputProcedureParameters.get(0) != null, "ProcedureParameters must not have size 1 with a null value");
this.procedureName = procedureName;
this.outputParameterNames = namedStoredProcedure
? outputParameterNames
: completeOutputParameterNames(outputParameterNames);
this.outputParameterTypes = outputParameterTypes;
this.namedStoredProcedure = namedStoredProcedure;
if (namedStoredProcedure) {
this.outputProcedureParameters = outputProcedureParameters;
} else {
this.outputProcedureParameters = getParametersWithCompletedNames(outputProcedureParameters);
}
}
private List<String> completeOutputParameterNames(List<String> outputParameterNames) {
return IntStream.range(0, outputParameterNames.size()) //
.mapToObj(i -> completeOutputParameterName(i, outputParameterNames.get(i))) //
private List<ProcedureParameter> getParametersWithCompletedNames(List<ProcedureParameter> procedureParameters) {
return IntStream.range(0, procedureParameters.size())
.mapToObj(i -> getParameterWithCompletedName(procedureParameters.get(i), i))
.collect(Collectors.toList());
}
private ProcedureParameter getParameterWithCompletedName(ProcedureParameter parameter, int i) {
return new ProcedureParameter(
completeOutputParameterName(i, parameter.getName()),
parameter.getMode(), parameter.getType());
}
private String completeOutputParameterName(int i, String paramName) {
return StringUtils.hasText(paramName) //
@@ -113,34 +113,27 @@ class StoredProcedureAttributes {
*
* @return
*/
public List<String> getOutputParameterNames() {
return outputParameterNames;
}
/**
* Returns the types of the output parameters.
*
* @return
*/
public List<Class<?>> getOutputParameterTypes() {
return outputParameterTypes;
}
/**
* Returns whether the stored procedure is a named one.
*
* @return
*/
public boolean isNamedStoredProcedure() {
return namedStoredProcedure;
}
/**
* @return Returns the stored procedure output parameter list
*/
public List<ProcedureParameter> getOutputProcedureParameters() {
return outputProcedureParameters;
}
/**
* Returns whether the stored procedure will produce a result.
*
* @return
*/
public boolean hasReturnValue() {
return !(outputParameterTypes.size() == 1 && (void.class.equals(outputParameterTypes.get(0)) || Void.class.equals(outputParameterTypes.get(0))));
if (getOutputProcedureParameters().isEmpty())
return false;
Class<?> outputType = getOutputProcedureParameters().get(0).getType();
return !(void.class.equals(outputType) || Void.class.equals(outputType));
}
}

View File

@@ -23,6 +23,7 @@ import java.util.stream.IntStream;
import javax.persistence.EntityManager;
import javax.persistence.NamedStoredProcedureQuery;
import javax.persistence.ParameterMode;
import javax.persistence.StoredProcedureParameter;
import javax.persistence.StoredProcedureQuery;
import javax.persistence.TypedQuery;
@@ -132,42 +133,32 @@ class StoredProcedureJpaQuery extends AbstractJpaQuery {
return null;
}
Map<String, Object> outputValues = new HashMap<>();
List<String> parameterNames = procedureAttributes.getOutputParameterNames();
List<ProcedureParameter> outputParameters = procedureAttributes.getOutputProcedureParameters();
for (int i = 0; i < parameterNames.size(); i++) {
String name = parameterNames.get(i);
outputValues.put(name, extractOutputParameter(storedProcedureQuery, i));
if (outputParameters.size() == 1) {
return extractOutputParameterValue(outputParameters.get(0), 0, storedProcedureQuery);
}
return outputValues.size() == 1 ? outputValues.values().iterator().next() : outputValues;
}
Map<String, Object> outputValues = new HashMap<>();
private Object extractOutputParameter(StoredProcedureQuery storedProcedureQuery, Integer index) {
for (int i = 0; i < outputParameters.size(); i++) {
ProcedureParameter outputParameter = outputParameters.get(i);
outputValues.put(outputParameter.getName(), extractOutputParameterValue(outputParameter, i, storedProcedureQuery));
}
String outputParameterName = procedureAttributes.getOutputParameterNames().get(index);
JpaParameters parameters = getQueryMethod().getParameters();
return extractOutputParameterValue(storedProcedureQuery, outputParameterName, index,
parameters.getNumberOfParameters());
return outputValues;
}
/**
* extract the value of an output parameter either by name or by index.
*
* @param storedProcedureQuery the query object of the stored procedure.
* @param name the name of the output parameter
* @param index index of the output parameter
* @param offset for index based access the index after which to find the output parameter values
* @return the value
* @return The value of an output parameter either by name or by index.
*/
private Object extractOutputParameterValue(StoredProcedureQuery storedProcedureQuery, String name, Integer index,
int offset) {
private Object extractOutputParameterValue(ProcedureParameter outputParameter, Integer index, StoredProcedureQuery storedProcedureQuery) {
return useNamedParameters && StringUtils.hasText(name) ? //
storedProcedureQuery.getOutputParameterValue(name)
: storedProcedureQuery.getOutputParameterValue(offset + index + 1);
JpaParameters methodParameters = getQueryMethod().getParameters();
return useNamedParameters && StringUtils.hasText(outputParameter.getName()) ?
storedProcedureQuery.getOutputParameterValue(outputParameter.getName())
: storedProcedureQuery.getOutputParameterValue(methodParameters.getNumberOfParameters() + index + 1);
}
/**
@@ -192,9 +183,7 @@ class StoredProcedureJpaQuery extends AbstractJpaQuery {
private StoredProcedureQuery newAdhocStoredProcedureQuery() {
JpaParameters params = getQueryMethod().getParameters();
String procedureName = procedureAttributes.getProcedureName();
StoredProcedureQuery procedureQuery = getEntityManager().createStoredProcedureQuery(procedureName);
StoredProcedureQuery procedureQuery = createAdhocStoredProcedureQuery();
for (JpaParameter param : params) {
@@ -214,23 +203,41 @@ class StoredProcedureJpaQuery extends AbstractJpaQuery {
if (procedureAttributes.hasReturnValue()) {
ParameterMode mode = ParameterMode.OUT;
ProcedureParameter procedureOutput = procedureAttributes.getOutputProcedureParameters().get(0);
IntStream.range(0, procedureAttributes.getOutputParameterTypes().size()).forEach(i -> {
Class<?> outputParameterType = procedureAttributes.getOutputParameterTypes().get(i);
/* If the stored procedure returns a ResultSet without using REF_CURSOR,
it is not necessary to declare an output parameter */
if ((isResultSetProcedure() && procedureOutput.getMode() == ParameterMode.REF_CURSOR) || !isResultSetProcedure()) {
if (useNamedParameters) {
String outputParameterName = procedureAttributes.getOutputParameterNames().get(i);
procedureQuery.registerStoredProcedureParameter(outputParameterName, outputParameterType, mode);
procedureQuery.registerStoredProcedureParameter(procedureOutput.getName(), procedureOutput.getType(), procedureOutput.getMode());
} else {
procedureQuery.registerStoredProcedureParameter(params.getNumberOfParameters() + i + 1, outputParameterType,
mode);
// Output parameter should be after the input parameters
int outputParameterIndex = params.getNumberOfParameters() + 1;
procedureQuery.registerStoredProcedureParameter(outputParameterIndex, procedureOutput.getType(),
procedureOutput.getMode());
}
});
}
}
return procedureQuery;
}
private StoredProcedureQuery createAdhocStoredProcedureQuery() {
String procedureName = procedureAttributes.getProcedureName();
if (getQueryMethod().isQueryForEntity()) {
return getEntityManager().createStoredProcedureQuery(procedureName,
getQueryMethod().getEntityInformation().getJavaType());
}
return getEntityManager().createStoredProcedureQuery(procedureName);
}
/**
* @return true if the stored procedure will use a ResultSet to return data and not output parameters
*/
private boolean isResultSetProcedure() {
return getQueryMethod().isCollectionQuery() || getQueryMethod().isQueryForEntity();
}
}

View File

@@ -25,9 +25,7 @@ import java.util.stream.Stream;
import javax.persistence.EntityManager;
import javax.persistence.Tuple;
import com.querydsl.core.types.EntityPath;
import org.slf4j.Logger;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.jpa.projection.CollectionAwareProjectionFactory;
@@ -40,6 +38,7 @@ import org.springframework.data.jpa.repository.query.EscapeCharacter;
import org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy;
import org.springframework.data.jpa.repository.query.JpaQueryMethod;
import org.springframework.data.jpa.repository.query.JpaQueryMethodFactory;
import org.springframework.data.jpa.repository.query.Procedure;
import org.springframework.data.jpa.util.JpaMetamodel;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.querydsl.EntityPathResolver;
@@ -59,6 +58,8 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import com.querydsl.core.types.EntityPath;
/**
* JPA specific generic repository factory.
*
@@ -97,7 +98,7 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
addRepositoryProxyPostProcessor(crudMethodMetadataPostProcessor);
addRepositoryProxyPostProcessor((factory, repositoryInformation) -> {
if (hasMethodReturningStream(repositoryInformation.getRepositoryInterface())) {
if (isTransactionNeeded(repositoryInformation.getRepositoryInterface())) {
factory.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE);
}
});
@@ -272,12 +273,12 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
return RepositoryFragments.empty();
}
private static boolean hasMethodReturningStream(Class<?> repositoryClass) {
private static boolean isTransactionNeeded(Class<?> repositoryClass) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(repositoryClass);
for (Method method : methods) {
if (Stream.class.isAssignableFrom(method.getReturnType())) {
if (Stream.class.isAssignableFrom(method.getReturnType()) || method.isAnnotationPresent(Procedure.class)) {
return true;
}
}
@@ -293,7 +294,8 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
*
* @author Oliver Gierke
* @since 2.0.5
* @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=289141">https://bugs.eclipse.org/bugs/show_bug.cgi?id=289141</a>
* @see <a href=
* "https://bugs.eclipse.org/bugs/show_bug.cgi?id=289141">https://bugs.eclipse.org/bugs/show_bug.cgi?id=289141</a>
*/
private static class EclipseLinkProjectionQueryCreationListener implements QueryCreationListener<AbstractJpaQuery> {

View File

@@ -47,6 +47,7 @@ import org.springframework.transaction.annotation.Transactional;
* @author Thomas Darimont
* @author Oliver Gierke
* @author Jens Schauder
* @author Gabriel Basilio
* @see scripts/schema-stored-procedures.sql for procedure definitions.
*/
@Transactional
@@ -54,7 +55,7 @@ import org.springframework.transaction.annotation.Transactional;
@ExtendWith(SpringExtension.class)
public class StoredProcedureIntegrationTests {
private static final String NOT_SUPPORTED = "Stored procedures with ResultSets are currently not supported for any JPA provider";
private static final String NOT_SUPPORTED = "Stored procedures with REF_CURSOR are currently not supported by HSQL dialect";
@PersistenceContext EntityManager em;
@Autowired DummyRepository repository;

View File

@@ -21,9 +21,11 @@ import static org.mockito.Mockito.*;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.ParameterMode;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -32,8 +34,8 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.jpa.domain.sample.Dummy;
import org.springframework.data.jpa.domain.sample.User;
import org.springframework.data.repository.query.Param;
import org.springframework.util.ReflectionUtils;
@@ -47,6 +49,7 @@ import org.springframework.util.ReflectionUtils;
* @author Diego Diez
* @author Jeff Sheets
* @author Jens Schauder
* @author Gabriel Basilio
* @since 1.6
*/
@ExtendWith(MockitoExtension.class)
@@ -54,8 +57,7 @@ import org.springframework.util.ReflectionUtils;
class StoredProcedureAttributeSourceUnitTests {
private StoredProcedureAttributeSource creator;
@Mock
JpaEntityMetadata<User> entityMetadata;
@Mock JpaEntityMetadata<User> entityMetadata;
@BeforeEach
void setup() {
@@ -70,10 +72,11 @@ class StoredProcedureAttributeSourceUnitTests {
void shouldCreateStoredProcedureAttributesFromProcedureMethodWithImplicitProcedureName() {
StoredProcedureAttributes attr = creator.createFrom(method("plus1inout", Integer.class), entityMetadata);
ProcedureParameter outputParameter = attr.getOutputProcedureParameters().get(0);
assertThat(attr.getProcedureName()).isEqualTo("plus1inout");
assertThat(attr.getOutputParameterTypes().get(0)).isEqualTo(Integer.class);
assertThat(attr.getOutputParameterNames().get(0)).isEqualTo(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME);
assertThat(outputParameter.getMode()).isEqualTo(ParameterMode.OUT);
assertThat(outputParameter.getType()).isEqualTo(Integer.class);
assertThat(outputParameter.getName()).isEqualTo(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME);
}
@Test // DATAJPA-455
@@ -82,9 +85,11 @@ class StoredProcedureAttributeSourceUnitTests {
StoredProcedureAttributes attr = creator.createFrom(method("explicitlyNamedPlus1inout", Integer.class),
entityMetadata);
ProcedureParameter outputParameter = attr.getOutputProcedureParameters().get(0);
assertThat(attr.getProcedureName()).isEqualTo("plus1inout");
assertThat(attr.getOutputParameterTypes().get(0)).isEqualTo(Integer.class);
assertThat(attr.getOutputParameterNames().get(0)).isEqualTo(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME);
assertThat(outputParameter.getMode()).isEqualTo(ParameterMode.OUT);
assertThat(outputParameter.getType()).isEqualTo(Integer.class);
assertThat(outputParameter.getName()).isEqualTo(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME);
}
@Test // DATAJPA-455
@@ -93,9 +98,11 @@ class StoredProcedureAttributeSourceUnitTests {
StoredProcedureAttributes attr = creator.createFrom(method("explicitlyNamedPlus1inout", Integer.class),
entityMetadata);
ProcedureParameter outputParameter = attr.getOutputProcedureParameters().get(0);
assertThat(attr.getProcedureName()).isEqualTo("plus1inout");
assertThat(attr.getOutputParameterTypes().get(0)).isEqualTo(Integer.class);
assertThat(attr.getOutputParameterNames().get(0)).isEqualTo(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME);
assertThat(outputParameter.getMode()).isEqualTo(ParameterMode.OUT);
assertThat(outputParameter.getType()).isEqualTo(Integer.class);
assertThat(outputParameter.getName()).isEqualTo(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME);
}
@Test // DATAJPA-455
@@ -104,9 +111,11 @@ class StoredProcedureAttributeSourceUnitTests {
StoredProcedureAttributes attr = creator
.createFrom(method("explicitPlus1inoutViaProcedureNameAlias", Integer.class), entityMetadata);
ProcedureParameter outputParameter = attr.getOutputProcedureParameters().get(0);
assertThat(attr.getProcedureName()).isEqualTo("plus1inout");
assertThat(attr.getOutputParameterTypes().get(0)).isEqualTo(Integer.class);
assertThat(attr.getOutputParameterNames().get(0)).isEqualTo(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME);
assertThat(outputParameter.getMode()).isEqualTo(ParameterMode.OUT);
assertThat(outputParameter.getType()).isEqualTo(Integer.class);
assertThat(outputParameter.getName()).isEqualTo(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME);
}
@Test // DATAJPA-1297
@@ -115,9 +124,11 @@ class StoredProcedureAttributeSourceUnitTests {
StoredProcedureAttributes attr = creator.createFrom(
method("explicitPlus1inoutViaProcedureNameAliasAndOutputParameterName", Integer.class), entityMetadata);
ProcedureParameter outputParameter = attr.getOutputProcedureParameters().get(0);
assertThat(attr.getProcedureName()).isEqualTo("plus1inout");
assertThat(attr.getOutputParameterTypes().get(0)).isEqualTo(Integer.class);
assertThat(attr.getOutputParameterNames().get(0)).isEqualTo("res");
assertThat(outputParameter.getMode()).isEqualTo(ParameterMode.OUT);
assertThat(outputParameter.getType()).isEqualTo(Integer.class);
assertThat(outputParameter.getName()).isEqualTo("res");
}
@Test // DATAJPA-455
@@ -126,9 +137,11 @@ class StoredProcedureAttributeSourceUnitTests {
StoredProcedureAttributes attr = creator
.createFrom(method("entityAnnotatedCustomNamedProcedurePlus1IO", Integer.class), entityMetadata);
ProcedureParameter outputParameter = attr.getOutputProcedureParameters().get(0);
assertThat(attr.getProcedureName()).isEqualTo("User.plus1IO");
assertThat(attr.getOutputParameterTypes().get(0)).isEqualTo(Integer.class);
assertThat(attr.getOutputParameterNames().get(0)).isEqualTo("res");
assertThat(outputParameter.getMode()).isEqualTo(ParameterMode.OUT);
assertThat(outputParameter.getType()).isEqualTo(Integer.class);
assertThat(outputParameter.getName()).isEqualTo("res");
}
@Test // DATAJPA-707
@@ -137,9 +150,11 @@ class StoredProcedureAttributeSourceUnitTests {
StoredProcedureAttributes attr = creator
.createFrom(method("entityAnnotatedCustomNamedProcedureOutputParamNamePlus1IO", Integer.class), entityMetadata);
ProcedureParameter outputParameter = attr.getOutputProcedureParameters().get(0);
assertThat(attr.getProcedureName()).isEqualTo("User.plus1IO");
assertThat(attr.getOutputParameterTypes().get(0)).isEqualTo(Integer.class);
assertThat(attr.getOutputParameterNames().get(0)).isEqualTo("override");
assertThat(outputParameter.getMode()).isEqualTo(ParameterMode.OUT);
assertThat(outputParameter.getType()).isEqualTo(Integer.class);
assertThat(outputParameter.getName()).isEqualTo("override");
}
@Test // DATAJPA-707
@@ -148,11 +163,18 @@ class StoredProcedureAttributeSourceUnitTests {
StoredProcedureAttributes attr = creator
.createFrom(method("entityAnnotatedCustomNamedProcedurePlus1IO2", Integer.class), entityMetadata);
ProcedureParameter firstOutputParameter = attr.getOutputProcedureParameters().get(0);
ProcedureParameter secondOutputParameter = attr.getOutputProcedureParameters().get(1);
assertThat(attr.getProcedureName()).isEqualTo("User.plus1IO2");
assertThat(attr.getOutputParameterTypes().get(0)).isEqualTo(Integer.class);
assertThat(attr.getOutputParameterNames().get(0)).isEqualTo("res");
assertThat(attr.getOutputParameterTypes().get(1)).isEqualTo(Integer.class);
assertThat(attr.getOutputParameterNames().get(1)).isEqualTo("res2");
assertThat(firstOutputParameter.getMode()).isEqualTo(ParameterMode.OUT);
assertThat(firstOutputParameter.getType()).isEqualTo(Integer.class);
assertThat(firstOutputParameter.getName()).isEqualTo("res");
assertThat(secondOutputParameter.getMode()).isEqualTo(ParameterMode.OUT);
assertThat(secondOutputParameter.getType()).isEqualTo(Integer.class);
assertThat(secondOutputParameter.getName()).isEqualTo("res2");
}
@Test // DATAJPA-455
@@ -160,9 +182,11 @@ class StoredProcedureAttributeSourceUnitTests {
StoredProcedureAttributes attr = creator.createFrom(method("plus1", Integer.class), entityMetadata);
ProcedureParameter outputParameter = attr.getOutputProcedureParameters().get(0);
assertThat(attr.getProcedureName()).isEqualTo("User.plus1");
assertThat(attr.getOutputParameterTypes().get(0)).isEqualTo(Integer.class);
assertThat(attr.getOutputParameterNames().get(0)).isEqualTo("res");
assertThat(outputParameter.getMode()).isEqualTo(ParameterMode.OUT);
assertThat(outputParameter.getType()).isEqualTo(Integer.class);
assertThat(outputParameter.getName()).isEqualTo("res");
}
@Test // DATAJPA-871
@@ -171,9 +195,11 @@ class StoredProcedureAttributeSourceUnitTests {
StoredProcedureAttributes attr = creator
.createFrom(method("plus1inoutWithComposedAnnotationOverridingProcedureName", Integer.class), entityMetadata);
ProcedureParameter outputParameter = attr.getOutputProcedureParameters().get(0);
assertThat(attr.getProcedureName()).isEqualTo("plus1inout");
assertThat(attr.getOutputParameterTypes().get(0)).isEqualTo(Integer.class);
assertThat(attr.getOutputParameterNames().get(0)).isEqualTo(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME);
assertThat(outputParameter.getMode()).isEqualTo(ParameterMode.OUT);
assertThat(outputParameter.getType()).isEqualTo(Integer.class);
assertThat(outputParameter.getName()).isEqualTo(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME);
}
@Test // DATAJPA-871
@@ -182,9 +208,102 @@ class StoredProcedureAttributeSourceUnitTests {
StoredProcedureAttributes attr = creator
.createFrom(method("plus1inoutWithComposedAnnotationOverridingName", Integer.class), entityMetadata);
ProcedureParameter outputParameter = attr.getOutputProcedureParameters().get(0);
assertThat(attr.getProcedureName()).isEqualTo("User.plus1");
assertThat(attr.getOutputParameterTypes().get(0)).isEqualTo(Integer.class);
assertThat(attr.getOutputParameterNames().get(0)).isEqualTo("res");
assertThat(outputParameter.getMode()).isEqualTo(ParameterMode.OUT);
assertThat(outputParameter.getType()).isEqualTo(Integer.class);
assertThat(outputParameter.getName()).isEqualTo("res");
}
@Test // DATAJPA-1657
public void testSingleEntityFrom1RowResultSetAndNoInput() {
StoredProcedureAttributes attr = creator.createFrom(method("singleEntityFrom1RowResultSetAndNoInput"),
entityMetadata);
ProcedureParameter outputParameter = attr.getOutputProcedureParameters().get(0);
assertThat(attr.getProcedureName()).isEqualTo("0_input_1_row_resultset");
assertThat(outputParameter.getMode()).isEqualTo(ParameterMode.OUT);
assertThat(outputParameter.getType()).isEqualTo(Dummy.class);
assertThat(outputParameter.getName()).isEqualTo(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME);
}
@Test // DATAJPA-1657
public void testSingleEntityFrom1RowResultSetWithInput() {
StoredProcedureAttributes attr = creator.createFrom(method("singleEntityFrom1RowResultSetWithInput", Integer.class),
entityMetadata);
ProcedureParameter outputParameter = attr.getOutputProcedureParameters().get(0);
assertThat(attr.getProcedureName()).isEqualTo("1_input_1_row_resultset");
assertThat(outputParameter.getMode()).isEqualTo(ParameterMode.OUT);
assertThat(outputParameter.getType()).isEqualTo(Dummy.class);
assertThat(outputParameter.getName()).isEqualTo(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME);
}
@Test // DATAJPA-1657
public void testEntityListFromResultSetWithNoInput() {
StoredProcedureAttributes attr = creator.createFrom(method("entityListFromResultSetWithNoInput"), entityMetadata);
ProcedureParameter outputParameter = attr.getOutputProcedureParameters().get(0);
assertThat(attr.getProcedureName()).isEqualTo("0_input_1_resultset");
assertThat(outputParameter.getMode()).isEqualTo(ParameterMode.OUT);
assertThat(outputParameter.getType()).isEqualTo(List.class);
assertThat(outputParameter.getName()).isEqualTo(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME);
}
//
@Test // DATAJPA-1657
public void testEntityListFromResultSetWithInput() {
StoredProcedureAttributes attr = creator.createFrom(method("entityListFromResultSetWithInput", Integer.class),
entityMetadata);
ProcedureParameter outputParameter = attr.getOutputProcedureParameters().get(0);
assertThat(attr.getProcedureName()).isEqualTo("1_input_1_resultset");
assertThat(outputParameter.getMode()).isEqualTo(ParameterMode.OUT);
assertThat(outputParameter.getType()).isEqualTo(List.class);
assertThat(outputParameter.getName()).isEqualTo(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME);
}
@Test // DATAJPA-1657
public void testGenericObjectListFromResultSetWithInput() {
StoredProcedureAttributes attr = creator
.createFrom(method("genericObjectListFromResultSetWithInput", Integer.class), entityMetadata);
ProcedureParameter outputParameter = attr.getOutputProcedureParameters().get(0);
assertThat(attr.getProcedureName()).isEqualTo("1_input_1_resultset");
assertThat(outputParameter.getMode()).isEqualTo(ParameterMode.OUT);
assertThat(outputParameter.getType()).isEqualTo(List.class);
assertThat(outputParameter.getName()).isEqualTo(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME);
}
@Test // DATAJPA-1657
public void testEntityListFromResultSetWithInputAndNamedOutput() {
StoredProcedureAttributes attr = creator
.createFrom(method("entityListFromResultSetWithInputAndNamedOutput", Integer.class), entityMetadata);
ProcedureParameter outputParameter = attr.getOutputProcedureParameters().get(0);
assertThat(attr.getProcedureName()).isEqualTo("1_input_1_resultset");
assertThat(outputParameter.getMode()).isEqualTo(ParameterMode.OUT);
assertThat(outputParameter.getType()).isEqualTo(List.class);
assertThat(outputParameter.getName()).isEqualTo("dummies");
}
@Test // DATAJPA-1657
public void testEntityListFromResultSetWithInputAndNamedOutputAndCursor() {
StoredProcedureAttributes attr = creator
.createFrom(method("entityListFromResultSetWithInputAndNamedOutputAndCursor", Integer.class), entityMetadata);
ProcedureParameter outputParameter = attr.getOutputProcedureParameters().get(0);
assertThat(attr.getProcedureName()).isEqualTo("1_input_1_resultset");
assertThat(outputParameter.getMode()).isEqualTo(ParameterMode.REF_CURSOR);
assertThat(outputParameter.getType()).isEqualTo(List.class);
assertThat(outputParameter.getName()).isEqualTo("dummies");
}
private static Method method(String name, Class<?>... paramTypes) {
@@ -195,8 +314,7 @@ class StoredProcedureAttributeSourceUnitTests {
* @author Thomas Darimont
*/
@SuppressWarnings("unused")
private
interface DummyRepository {
private interface DummyRepository {
/**
* Explicitly mapped to a procedure with name "plus1inout" in database.
@@ -235,8 +353,7 @@ class StoredProcedureAttributeSourceUnitTests {
Integer entityAnnotatedCustomNamedProcedurePlus1IO(@Param("arg") Integer arg);
/**
* Explicitly mapped to named stored procedure "User.plus1IO" in {@link EntityManager}.
* With a outputParameterName
* Explicitly mapped to named stored procedure "User.plus1IO" in {@link EntityManager}. With a outputParameterName
*/
@Procedure(name = "User.plus1IO", outputParameterName = "override")
// DATAJPA-707
@@ -261,6 +378,34 @@ class StoredProcedureAttributeSourceUnitTests {
@ComposedProcedureUsingAliasFor(emProcedureName = "User.plus1")
Integer plus1inoutWithComposedAnnotationOverridingName(Integer arg);
@Procedure("0_input_1_row_resultset")
// DATAJPA-1657
Dummy singleEntityFrom1RowResultSetAndNoInput();
@Procedure("1_input_1_row_resultset")
// DATAJPA-1657
Dummy singleEntityFrom1RowResultSetWithInput(Integer arg);
@Procedure("0_input_1_resultset")
// DATAJPA-1657
List<Dummy> entityListFromResultSetWithNoInput();
@Procedure("1_input_1_resultset")
// DATAJPA-1657
List<Dummy> entityListFromResultSetWithInput(Integer arg);
@Procedure("1_input_1_resultset")
// DATAJPA-1657
List<Object[]> genericObjectListFromResultSetWithInput(Integer arg);
@Procedure(value = "1_input_1_resultset", outputParameterName = "dummies")
// DATAJPA-1657
List<Dummy> entityListFromResultSetWithInputAndNamedOutput(Integer arg);
@Procedure(value = "1_input_1_resultset", outputParameterName = "dummies", refCursor = true)
// DATAJPA-1657
List<Dummy> entityListFromResultSetWithInputAndNamedOutputAndCursor(Integer arg);
}
@SuppressWarnings("unused")

View File

@@ -20,6 +20,8 @@ import static org.springframework.data.jpa.repository.query.StoredProcedureAttri
import org.junit.jupiter.api.Test;
import javax.persistence.ParameterMode;
/**
* Unit tests for {@link StoredProcedureAttributes}.
*
@@ -31,7 +33,8 @@ class StoredProcedureAttributesUnitTests {
@Test // DATAJPA-681
void usesSyntheticOutputParameterNameForAdhocProcedureWithoutOutputName() {
StoredProcedureAttributes attributes = new StoredProcedureAttributes("procedure", null, Long.class);
assertThat(attributes.getOutputParameterNames().get(0)).isEqualTo(SYNTHETIC_OUTPUT_PARAMETER_NAME);
ProcedureParameter outputParameter = new ProcedureParameter(null, ParameterMode.OUT, Long.class);
StoredProcedureAttributes attributes = new StoredProcedureAttributes("procedure", outputParameter);
assertThat(attributes.getOutputProcedureParameters().get(0).getName()).isEqualTo(SYNTHETIC_OUTPUT_PARAMETER_NAME);
}
}