diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java index d1997dc88a..1ac44cb03c 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java @@ -53,6 +53,7 @@ * @author Christoph Strobl * @author Nicolas Cirigliano * @author Jens Schauder + * @author Gabriel Basilio */ public abstract class JpaQueryExecution { @@ -300,6 +301,8 @@ protected Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccesso */ 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[]) @@ -311,7 +314,24 @@ protected Object doExecute(AbstractJpaQuery jpaQuery, JpaParametersParameterAcce 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); } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/Procedure.java b/src/main/java/org/springframework/data/jpa/repository/query/Procedure.java index f740c59892..fef0ac184b 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/Procedure.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/Procedure.java @@ -26,6 +26,7 @@ * @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 @@ * 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; } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/ProcedureParameter.java b/src/main/java/org/springframework/data/jpa/repository/query/ProcedureParameter.java new file mode 100644 index 0000000000..6d185e5025 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/query/ProcedureParameter.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSource.java b/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSource.java index 84dac1e123..05da37fe34 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSource.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSource.java @@ -22,6 +22,7 @@ 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 @@ * @author Mark Paluch * @author Diego Diez * @author Jeff Sheets + * @author Gabriel Basilio * @since 1.6 */ enum StoredProcedureAttributeSource { @@ -72,7 +74,7 @@ public StoredProcedureAttributes createFrom(Method method, JpaEntityMetadata + method); } - return new StoredProcedureAttributes(procedureName, procedure.outputParameterName(), method.getReturnType()); + return new StoredProcedureAttributes(procedureName, createOutputProcedureParameterFrom(method, procedure)); } /** @@ -102,28 +104,29 @@ private String deriveProcedureNameFrom(Method method, Procedure procedure) { private StoredProcedureAttributes newProcedureAttributesFrom(Method method, NamedStoredProcedureQuery namedStoredProc, Procedure procedure) { - List outputParameterNames = new ArrayList<>(); - List> outputParameterTypes = new ArrayList<>(); + List 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 outputParameters = extractOutputParametersFrom(namedStoredProc); + List 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 extractOutputParametersFrom(NamedStoredProcedureQuery namedStoredProc) { diff --git a/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributes.java b/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributes.java index 4699f0376a..2c4c751840 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributes.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributes.java @@ -33,6 +33,7 @@ * @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 outputParameterNames; - private final List> outputParameterTypes; + private final List 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 outputParameterNames, - List> outputParameterTypes, boolean namedStoredProcedure) { + StoredProcedureAttributes(String procedureName, List 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; - } - private List completeOutputParameterNames(List outputParameterNames) { + if (namedStoredProcedure) { + this.outputProcedureParameters = outputProcedureParameters; + } else { + this.outputProcedureParameters = getParametersWithCompletedNames(outputProcedureParameters); + } + } - return IntStream.range(0, outputParameterNames.size()) // - .mapToObj(i -> completeOutputParameterName(i, outputParameterNames.get(i))) // + private List getParametersWithCompletedNames(List 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,26 +113,15 @@ public String getProcedureName() { * * @return */ - public List getOutputParameterNames() { - return outputParameterNames; - } - - /** - * Returns the types of the output parameters. - * - * @return - */ - public List> getOutputParameterTypes() { - return outputParameterTypes; + public boolean isNamedStoredProcedure() { + return namedStoredProcedure; } /** - * Returns whether the stored procedure is a named one. - * - * @return + * @return Returns the stored procedure output parameter list */ - public boolean isNamedStoredProcedure() { - return namedStoredProcedure; + public List getOutputProcedureParameters() { + return outputProcedureParameters; } /** @@ -141,6 +130,10 @@ public boolean isNamedStoredProcedure() { * @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)); } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java index b460da57fc..f4a931292a 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java @@ -23,6 +23,7 @@ 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 @@ Object extractOutputValue(StoredProcedureQuery storedProcedureQuery) { return null; } - Map outputValues = new HashMap<>(); - List parameterNames = procedureAttributes.getOutputParameterNames(); - - for (int i = 0; i < parameterNames.size(); i++) { + List outputParameters = procedureAttributes.getOutputProcedureParameters(); - 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; - } - - private Object extractOutputParameter(StoredProcedureQuery storedProcedureQuery, Integer index) { + Map outputValues = new HashMap<>(); - String outputParameterName = procedureAttributes.getOutputParameterNames().get(index); - JpaParameters parameters = getQueryMethod().getParameters(); + for (int i = 0; i < outputParameters.size(); i++) { + ProcedureParameter outputParameter = outputParameters.get(i); + outputValues.put(outputParameter.getName(), extractOutputParameterValue(outputParameter, i, storedProcedureQuery)); + } - 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) { + + JpaParameters methodParameters = getQueryMethod().getParameters(); - return useNamedParameters && StringUtils.hasText(name) ? // - storedProcedureQuery.getOutputParameterValue(name) - : storedProcedureQuery.getOutputParameterValue(offset + index + 1); + return useNamedParameters && StringUtils.hasText(outputParameter.getName()) ? + storedProcedureQuery.getOutputParameterValue(outputParameter.getName()) + : storedProcedureQuery.getOutputParameterValue(methodParameters.getNumberOfParameters() + index + 1); } /** @@ -192,9 +183,7 @@ private StoredProcedureQuery newNamedStoredProcedureQuery() { 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 @@ private StoredProcedureQuery newAdhocStoredProcedureQuery() { 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(); + } } diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java index 03ec571dbf..437e055bb9 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java @@ -38,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.Procedure; import org.springframework.data.jpa.repository.query.JpaQueryMethodFactory; import org.springframework.data.jpa.util.JpaMetamodel; import org.springframework.data.projection.ProjectionFactory; @@ -97,7 +98,7 @@ public JpaRepositoryFactory(EntityManager entityManager) { addRepositoryProxyPostProcessor(crudMethodMetadataPostProcessor); addRepositoryProxyPostProcessor((factory, repositoryInformation) -> { - if (hasMethodReturningStream(repositoryInformation.getRepositoryInterface())) { + if (isTransactionNeeded(repositoryInformation.getRepositoryInterface())) { factory.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE); } }); @@ -259,12 +260,13 @@ protected RepositoryComposition.RepositoryFragments getRepositoryFragments(Repos return fragments; } - 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; } } diff --git a/src/test/java/org/springframework/data/jpa/repository/StoredProcedureIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/StoredProcedureIntegrationTests.java index 1901d7107a..4331f7d47a 100644 --- a/src/test/java/org/springframework/data/jpa/repository/StoredProcedureIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/StoredProcedureIntegrationTests.java @@ -46,6 +46,7 @@ * @author Thomas Darimont * @author Oliver Gierke * @author Jens Schauder + * @author Gabriel Basilio * @see scripts/schema-stored-procedures.sql for procedure definitions. */ @Transactional @@ -53,7 +54,7 @@ @RunWith(SpringJUnit4ClassRunner.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; diff --git a/src/test/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSourceUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSourceUnitTests.java index 347249b77a..01a91fd18e 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSourceUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSourceUnitTests.java @@ -21,14 +21,17 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; 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; import javax.persistence.EntityManager; +import javax.persistence.ParameterMode; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; +import java.util.List; import java.util.Map; import static org.assertj.core.api.Assertions.*; @@ -43,6 +46,7 @@ * @author Diego Diez * @author Jeff Sheets * @author Jens Schauder + * @author Gabriel Basilio * @since 1.6 */ @RunWith(MockitoJUnitRunner.class) @@ -65,10 +69,11 @@ public void setup() { public 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 @@ -77,9 +82,11 @@ public void shouldCreateStoredProcedureAttributesFromProcedureMethodWithExplictN 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 @@ -88,9 +95,11 @@ public void shouldCreateStoredProcedureAttributesFromProcedureMethodWithExplictP 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 @@ -99,9 +108,11 @@ public void shouldCreateStoredProcedureAttributesFromProcedureMethodWithExplictP 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 @@ -110,9 +121,11 @@ public void shouldCreateStoredProcedureAttributesFromProcedureMethodWithExplictP 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 @@ -121,9 +134,11 @@ public void shouldCreateStoredProcedureAttributesFromProcedureMethodBackedWithEx 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 @@ -132,9 +147,11 @@ public void shouldCreateStoredProcedureAttributesFromProcedureMethodBackedWithEx 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 @@ -143,11 +160,18 @@ public void shouldCreateStoredProcedureAttributesFromProcedureMethodBackedWithEx 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 @@ -155,9 +179,11 @@ public void shouldCreateStoredProcedureAttributesFromProcedureMethodBackedWithIm 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 @@ -166,9 +192,11 @@ public void aliasedStoredProcedure() { 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 @@ -177,9 +205,104 @@ public void aliasedStoredProcedure2() { 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) { @@ -255,6 +378,34 @@ interface DummyRepository { @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 entityListFromResultSetWithNoInput(); + + @Procedure("1_input_1_resultset") + // DATAJPA-1657 + List entityListFromResultSetWithInput(Integer arg); + + @Procedure("1_input_1_resultset") + // DATAJPA-1657 + List genericObjectListFromResultSetWithInput(Integer arg); + + @Procedure(value = "1_input_1_resultset", outputParameterName = "dummies") + // DATAJPA-1657 + List entityListFromResultSetWithInputAndNamedOutput(Integer arg); + + @Procedure(value = "1_input_1_resultset", outputParameterName = "dummies", refCursor = true) + // DATAJPA-1657 + List entityListFromResultSetWithInputAndNamedOutputAndCursor(Integer arg); } @SuppressWarnings("unused") diff --git a/src/test/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributesUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributesUnitTests.java index 257b2b67ec..a2981bdf82 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributesUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributesUnitTests.java @@ -20,6 +20,8 @@ import org.junit.Test; +import javax.persistence.ParameterMode; + /** * Unit tests for {@link StoredProcedureAttributes}. * @@ -31,7 +33,8 @@ public class StoredProcedureAttributesUnitTests { @Test // DATAJPA-681 public 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); } }