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 3f697e2b4..331f8b78a 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 @@ -37,6 +37,7 @@ import org.springframework.util.StringUtils; * @author Christoph Strobl * @author Mark Paluch * @author Diego Diez + * @author Jeff Sheets * @since 1.6 */ enum StoredProcedureAttributeSource { @@ -101,36 +102,28 @@ enum StoredProcedureAttributeSource { private StoredProcedureAttributes newProcedureAttributesFrom(Method method, NamedStoredProcedureQuery namedStoredProc, Procedure procedure) { - String outputParameterName = null; - Class outputParameterType = null; + List outputParameterNames = new ArrayList<>(); + List> outputParameterTypes = new ArrayList<>(); if (!procedure.outputParameterName().isEmpty()) { - // we give the output parameter definition from the @Procedure annotation precedence - outputParameterName = procedure.outputParameterName(); + outputParameterNames.add(procedure.outputParameterName()); } else { // try to discover the output parameter List outputParameters = extractOutputParametersFrom(namedStoredProc); - if (outputParameters.size() != 1 && !void.class.equals(method.getReturnType())) { - throw new IllegalStateException(String.format( - "Could not create ProcedureAttributes from %s. We currently support exactly one output parameter!", method)); - } - - if (!outputParameters.isEmpty()) { - StoredProcedureParameter outputParameter = outputParameters.get(0); - outputParameterName = outputParameter.name(); - outputParameterType = outputParameter.type(); + for (StoredProcedureParameter outputParameter : outputParameters) { + outputParameterNames.add(outputParameter.name()); + outputParameterTypes.add(outputParameter.type()); } } - if (outputParameterType == null || Object.class.equals(outputParameterType) - || void.class.equals(outputParameterType)) { - outputParameterType = method.getReturnType(); + if (outputParameterTypes.isEmpty()) { + outputParameterTypes.add(method.getReturnType()); } - return new StoredProcedureAttributes(namedStoredProc.name(), outputParameterName, outputParameterType, true); + return new StoredProcedureAttributes(namedStoredProc.name(), outputParameterNames, outputParameterTypes, true); } 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 10c38c1ca..2e90eb638 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 @@ -21,12 +21,18 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + /** * Stored procedure configuration for JPA 2.1 {@link StoredProcedureQuery}s. * * @author Thomas Darimont * @author Oliver Gierke * @author Mark Paluch + * @author Jeff Sheets * @since 1.6 */ class StoredProcedureAttributes { @@ -36,8 +42,8 @@ class StoredProcedureAttributes { private final boolean namedStoredProcedure; private final String procedureName; - private final String outputParameterName; - private final Class outputParameterType; + private final List outputParameterNames; + private final List> outputParameterTypes; /** * Creates a new {@link StoredProcedureAttributes}. @@ -49,14 +55,31 @@ class StoredProcedureAttributes { */ public StoredProcedureAttributes(String procedureName, @Nullable String outputParameterName, Class outputParameterType, boolean namedStoredProcedure) { + this(procedureName, Arrays.asList(outputParameterName), Arrays.asList(outputParameterType), namedStoredProcedure); + } + + /** + * Creates a new {@link StoredProcedureAttributes}. + * + * @param procedureName must not be {@literal null} + * @param outputParameterNames may be empty, but not null + * @param outputParameterTypes must not be empty, and cannot be a single element of null + * @param namedStoredProcedure + */ + public StoredProcedureAttributes(String procedureName, List outputParameterNames, + List> outputParameterTypes, boolean namedStoredProcedure) { Assert.notNull(procedureName, "ProcedureName must not be null!"); - Assert.notNull(outputParameterType, "OutputParameterType 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"); this.procedureName = procedureName; - this.outputParameterName = !namedStoredProcedure && !StringUtils.hasText(outputParameterName) ? SYNTHETIC_OUTPUT_PARAMETER_NAME - : outputParameterName; - this.outputParameterType = outputParameterType; + this.outputParameterNames = namedStoredProcedure ? outputParameterNames : IntStream.range(0, outputParameterNames.size()).mapToObj(i -> { + String paramName = outputParameterNames.get(i); + return !StringUtils.hasText(paramName) ? SYNTHETIC_OUTPUT_PARAMETER_NAME + (i == 0 ? "" : i) : paramName; + }).collect(Collectors.toList()); + this.outputParameterTypes = outputParameterTypes; this.namedStoredProcedure = namedStoredProcedure; } @@ -70,21 +93,21 @@ class StoredProcedureAttributes { } /** - * Returns the name of the output parameter. + * Returns the names of the output parameters. * * @return */ - public String getOutputParameterName() { - return outputParameterName; + public List getOutputParameterNames() { + return outputParameterNames; } /** - * Returns the type of the output parameter. + * Returns the types of the output parameters. * * @return */ - public Class getOutputParameterType() { - return outputParameterType; + public List> getOutputParameterTypes() { + return outputParameterTypes; } /** @@ -102,6 +125,6 @@ class StoredProcedureAttributes { * @return */ public boolean hasReturnValue() { - return !(void.class.equals(outputParameterType) || Void.class.equals(outputParameterType)); + return !(outputParameterTypes.size() == 1 && (void.class.equals(outputParameterTypes.get(0)) || Void.class.equals(outputParameterTypes.get(0)))); } } 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 d2a4ebe84..ed48ff044 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 @@ -28,6 +28,10 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + /** * {@link AbstractJpaQuery} implementation that inspects a {@link JpaQueryMethod} for the existence of an * {@link Procedure} annotation and creates a JPA 2.1 {@link StoredProcedureQuery} from it. @@ -37,6 +41,7 @@ import org.springframework.util.StringUtils; * @author Christoph Strobl * @author Jens Schauder * @author Mark Paluch + * @author Jeff Sheets * @since 1.6 */ class StoredProcedureJpaQuery extends AbstractJpaQuery { @@ -105,6 +110,8 @@ class StoredProcedureJpaQuery extends AbstractJpaQuery { * Extracts the output value from the given {@link StoredProcedureQuery}. * * @param storedProcedureQuery must not be {@literal null}. + * + * Result is either a single value, or a Map of output parameter names to values */ @Nullable Object extractOutputValue(StoredProcedureQuery storedProcedureQuery) { @@ -115,12 +122,17 @@ class StoredProcedureJpaQuery extends AbstractJpaQuery { return null; } - String outputParameterName = procedureAttributes.getOutputParameterName(); - JpaParameters parameters = getQueryMethod().getParameters(); + Map outputValues = IntStream.range(0, procedureAttributes.getOutputParameterNames().size()) + .boxed().collect(Collectors.toMap(procedureAttributes.getOutputParameterNames()::get, i -> { + String outputParameterName = procedureAttributes.getOutputParameterNames().get(i); + JpaParameters parameters = getQueryMethod().getParameters(); - return useNamedParameters && StringUtils.hasText(outputParameterName) ? // - storedProcedureQuery.getOutputParameterValue(outputParameterName) - : storedProcedureQuery.getOutputParameterValue(parameters.getNumberOfParameters() + 1); + return useNamedParameters && StringUtils.hasText(outputParameterName) ? // + storedProcedureQuery.getOutputParameterValue(outputParameterName) + : storedProcedureQuery.getOutputParameterValue(parameters.getNumberOfParameters() + i + 1); + })); + + return outputValues.size() == 1 ? outputValues.values().iterator().next() : outputValues; } /** @@ -165,17 +177,20 @@ class StoredProcedureJpaQuery extends AbstractJpaQuery { if (procedureAttributes.hasReturnValue()) { - Class outputParameterType = procedureAttributes.getOutputParameterType(); ParameterMode mode = ParameterMode.OUT; - if (useNamedParameters) { + IntStream.range(0, procedureAttributes.getOutputParameterTypes().size()).forEach(i -> { + Class outputParameterType = procedureAttributes.getOutputParameterTypes().get(i); - String outputParameterName = procedureAttributes.getOutputParameterName(); - procedureQuery.registerStoredProcedureParameter(outputParameterName, outputParameterType, mode); + if (useNamedParameters) { - } else { - procedureQuery.registerStoredProcedureParameter(params.getNumberOfParameters() + 1, outputParameterType, mode); - } + String outputParameterName = procedureAttributes.getOutputParameterNames().get(i); + procedureQuery.registerStoredProcedureParameter(outputParameterName, outputParameterType, mode); + + } else { + procedureQuery.registerStoredProcedureParameter(params.getNumberOfParameters() + i + 1, outputParameterType, mode); + } + }); } return procedureQuery; diff --git a/src/test/java/org/springframework/data/jpa/domain/sample/User.java b/src/test/java/org/springframework/data/jpa/domain/sample/User.java index 2ca8208eb..5179446c0 100644 --- a/src/test/java/org/springframework/data/jpa/domain/sample/User.java +++ b/src/test/java/org/springframework/data/jpa/domain/sample/User.java @@ -58,6 +58,7 @@ import javax.persistence.TemporalType; * @author Thomas Darimont * @author Christoph Strobl * @author Jens Schauder + * @author Jeff Sheets */ @Entity @NamedEntityGraphs({ @NamedEntityGraph(name = "User.overview", attributeNodes = { @NamedAttributeNode("roles") }), @@ -92,7 +93,11 @@ import javax.persistence.TemporalType; @NamedStoredProcedureQueries({ // @NamedStoredProcedureQuery(name = "User.plus1", procedureName = "plus1inout", parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class), - @StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class) }) // + @StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class) }), // + @NamedStoredProcedureQuery(name = "User.plus1IO2", procedureName = "plus1inout2", + parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class), + @StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class), + @StoredProcedureParameter(mode = ParameterMode.OUT, name = "res2", type = Integer.class) }) // }) @NamedStoredProcedureQuery(name = "User.plus1IO", procedureName = "plus1inout", parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class), diff --git a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryStoredProcedureTests.java b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryStoredProcedureTests.java index c1eeb3f55..cb5fcbd3b 100644 --- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryStoredProcedureTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryStoredProcedureTests.java @@ -16,30 +16,34 @@ package org.springframework.data.jpa.repository; import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.Matchers.hasEntry; import static org.junit.Assert.*; import static org.junit.Assume.*; import static org.springframework.data.jpa.support.EntityManagerTestUtils.*; -import javax.persistence.EntityManager; -import javax.persistence.ParameterMode; -import javax.persistence.PersistenceContext; -import javax.persistence.StoredProcedureQuery; +import javax.persistence.*; import org.junit.Assume; import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.jpa.repository.sample.UserRepository; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; +import java.util.Map; + /** * Integration tests for JPA 2.1 stored procedure support. * * @author Thomas Darimont * @author Oliver Gierke + * @author Jeff Sheets * @since 1.6 */ @RunWith(SpringJUnit4ClassRunner.class) @@ -50,6 +54,8 @@ public class UserRepositoryStoredProcedureTests { @Autowired UserRepository repository; @PersistenceContext EntityManager em; + @Rule public ExpectedException exception = ExpectedException.none(); + @Test // DATAJPA-455 public void callProcedureWithInAndOutParameters() { @@ -58,6 +64,14 @@ public class UserRepositoryStoredProcedureTests { assertThat(repository.plus1inout(1), is(2)); } + @Test // DATAJPA-707 + public void callProcedureWithInAndOutParametersInvalidOutParamName() { + + assumeTrue(currentEntityManagerIsAJpa21EntityManager(em)); + + assertThat(repository.plus1inoutInvalidOutParamName(1), is(2)); + } + @Test // DATAJPA-455 public void callProcedureExplicitNameWithInAndOutParameters() { @@ -74,6 +88,36 @@ public class UserRepositoryStoredProcedureTests { assertThat(repository.entityAnnotatedCustomNamedProcedurePlus1IO(1), is(2)); } + @Test // DATAJPA-707 + public void entityAnnotatedCustomNamedProcedurePlus1IOInvalidOutParamName() { + + assumeTrue(currentEntityManagerIsAJpa21EntityManager(em)); + + exception.expect(InvalidDataAccessApiUsageException.class); + exception.expectMessage(containsString("Could not locate parameter registered under that name")); + + repository.entityAnnotatedCustomNamedProcedurePlus1IOInvalidOutParamName(1); + } + + @Test // DATAJPA-707 + public void entityAnnotatedCustomNamedProcedurePlus1IO2TwoOutParamsButNamingOne() { + + assumeTrue(currentEntityManagerIsAJpa21EntityManager(em)); + + assertThat(repository.entityAnnotatedCustomNamedProcedurePlus1IO2TwoOutParamsButNamingOne(1), is(3)); + } + + @Test // DATAJPA-707 + public void entityAnnotatedCustomNamedProcedurePlus1IO2() { + + assumeTrue(currentEntityManagerIsAJpa21EntityManager(em)); + + Map result = repository.entityAnnotatedCustomNamedProcedurePlus1IO2(1); + assertThat(result, hasEntry("res", 2)); + assertThat(result, hasEntry("res2", 3)); + assertThat(result.size(), is(2)); + } + @Test // DATAJPA-455 @Ignore public void plainJpa21() { @@ -103,4 +147,37 @@ public class UserRepositoryStoredProcedureTests { assertThat(proc.getOutputParameterValue("res"), is((Object) 2)); } + + @Test // DATAJPA-707 + @Ignore + public void plainJpa21_twoOutParams() { + + assumeTrue(currentEntityManagerIsAJpa21EntityManager(em)); + + StoredProcedureQuery proc = em.createStoredProcedureQuery("plus1inout2"); + proc.registerStoredProcedureParameter(1, Integer.class, ParameterMode.IN); + proc.registerStoredProcedureParameter(2, Integer.class, ParameterMode.OUT); + proc.registerStoredProcedureParameter(3, Integer.class, ParameterMode.OUT); + + proc.setParameter(1, 1); + proc.execute(); + + assertThat(proc.getOutputParameterValue(2), is((Object) 2)); + assertThat(proc.getOutputParameterValue(3), is((Object) 3)); + } + + @Test // DATAJPA-707 + @Ignore + public void plainJpa21_entityAnnotatedCustomNamedProcedurePlus1IO2() { + + Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em)); + + StoredProcedureQuery proc = em.createNamedStoredProcedureQuery("User.plus1IO2"); + + proc.setParameter("arg", 1); + proc.execute(); + + assertThat(proc.getOutputParameterValue("res"), is((Object) 2)); + assertThat(proc.getOutputParameterValue("res2"), is((Object) 3)); + } } 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 c8f6cb46b..6ebd0cb98 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 @@ -23,6 +23,7 @@ import static org.mockito.Mockito.*; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; +import java.util.Map; import javax.persistence.EntityManager; @@ -43,6 +44,7 @@ import org.springframework.util.ReflectionUtils; * @author Oliver Gierke * @author Christoph Strobl * @author Diego Diez + * @author Jeff Sheets * @since 1.6 */ @RunWith(MockitoJUnitRunner.class) @@ -66,8 +68,8 @@ public class StoredProcedureAttributeSourceUnitTests { StoredProcedureAttributes attr = creator.createFrom(method("plus1inout", Integer.class), entityMetadata); assertThat(attr.getProcedureName(), is("plus1inout")); - assertThat(attr.getOutputParameterType(), is(typeCompatibleWith(Integer.class))); - assertThat(attr.getOutputParameterName(), is(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME)); + assertThat(attr.getOutputParameterTypes().get(0), is(typeCompatibleWith(Integer.class))); + assertThat(attr.getOutputParameterNames().get(0), is(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME)); } @Test // DATAJPA-455 @@ -77,8 +79,8 @@ public class StoredProcedureAttributeSourceUnitTests { entityMetadata); assertThat(attr.getProcedureName(), is("plus1inout")); - assertThat(attr.getOutputParameterType(), is(typeCompatibleWith(Integer.class))); - assertThat(attr.getOutputParameterName(), is(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME)); + assertThat(attr.getOutputParameterTypes().get(0), is(typeCompatibleWith(Integer.class))); + assertThat(attr.getOutputParameterNames().get(0), is(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME)); } @Test // DATAJPA-455 @@ -88,8 +90,8 @@ public class StoredProcedureAttributeSourceUnitTests { entityMetadata); assertThat(attr.getProcedureName(), is("plus1inout")); - assertThat(attr.getOutputParameterType(), is(typeCompatibleWith(Integer.class))); - assertThat(attr.getOutputParameterName(), is(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME)); + assertThat(attr.getOutputParameterTypes().get(0), is(typeCompatibleWith(Integer.class))); + assertThat(attr.getOutputParameterNames().get(0), is(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME)); } @Test // DATAJPA-455 @@ -99,8 +101,8 @@ public class StoredProcedureAttributeSourceUnitTests { .createFrom(method("explicitPlus1inoutViaProcedureNameAlias", Integer.class), entityMetadata); assertThat(attr.getProcedureName(), is("plus1inout")); - assertThat(attr.getOutputParameterType(), is(typeCompatibleWith(Integer.class))); - assertThat(attr.getOutputParameterName(), is(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME)); + assertThat(attr.getOutputParameterTypes().get(0), is(typeCompatibleWith(Integer.class))); + assertThat(attr.getOutputParameterNames().get(0), is(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME)); } @Test // DATAJPA-1297 @@ -110,8 +112,8 @@ public class StoredProcedureAttributeSourceUnitTests { method("explicitPlus1inoutViaProcedureNameAliasAndOutputParameterName", Integer.class), entityMetadata); assertThat(attr.getProcedureName(), is("plus1inout")); - assertThat(attr.getOutputParameterType(), is(typeCompatibleWith(Integer.class))); - assertThat(attr.getOutputParameterName(), is("res")); + assertThat(attr.getOutputParameterTypes().get(0), is(typeCompatibleWith(Integer.class))); + assertThat(attr.getOutputParameterNames().get(0), is("res")); } @Test // DATAJPA-455 @@ -121,8 +123,32 @@ public class StoredProcedureAttributeSourceUnitTests { .createFrom(method("entityAnnotatedCustomNamedProcedurePlus1IO", Integer.class), entityMetadata); assertThat(attr.getProcedureName(), is("User.plus1IO")); - assertThat(attr.getOutputParameterType(), is(typeCompatibleWith(Integer.class))); - assertThat(attr.getOutputParameterName(), is("res")); + assertThat(attr.getOutputParameterTypes().get(0), is(typeCompatibleWith(Integer.class))); + assertThat(attr.getOutputParameterNames().get(0), is("res")); + } + + @Test // DATAJPA-707 + public void shouldCreateStoredProcedureAttributesFromProcedureMethodBackedWithExplicitlyNamedProcedureAndOutputParamName() { + + StoredProcedureAttributes attr = creator + .createFrom(method("entityAnnotatedCustomNamedProcedureOutputParamNamePlus1IO", Integer.class), entityMetadata); + + assertThat(attr.getProcedureName(), is("User.plus1IO")); + assertThat(attr.getOutputParameterTypes().get(0), is(typeCompatibleWith(Integer.class))); + assertThat(attr.getOutputParameterNames().get(0), is("override")); + } + + @Test // DATAJPA-707 + public void shouldCreateStoredProcedureAttributesFromProcedureMethodBackedWithExplicitlyNamedProcedureAnd2OutParams() { + + StoredProcedureAttributes attr = creator + .createFrom(method("entityAnnotatedCustomNamedProcedurePlus1IO2", Integer.class), entityMetadata); + + assertThat(attr.getProcedureName(), is("User.plus1IO2")); + assertThat(attr.getOutputParameterTypes().get(0), is(typeCompatibleWith(Integer.class))); + assertThat(attr.getOutputParameterNames().get(0), is("res")); + assertThat(attr.getOutputParameterTypes().get(1), is(typeCompatibleWith(Integer.class))); + assertThat(attr.getOutputParameterNames().get(1), is("res2")); } @Test // DATAJPA-455 @@ -131,8 +157,8 @@ public class StoredProcedureAttributeSourceUnitTests { StoredProcedureAttributes attr = creator.createFrom(method("plus1", Integer.class), entityMetadata); assertThat(attr.getProcedureName(), is("User.plus1")); - assertThat(attr.getOutputParameterType(), is(typeCompatibleWith(Integer.class))); - assertThat(attr.getOutputParameterName(), is("res")); + assertThat(attr.getOutputParameterTypes().get(0), is(typeCompatibleWith(Integer.class))); + assertThat(attr.getOutputParameterNames().get(0), is("res")); } @Test // DATAJPA-871 @@ -142,8 +168,8 @@ public class StoredProcedureAttributeSourceUnitTests { .createFrom(method("plus1inoutWithComposedAnnotationOverridingProcedureName", Integer.class), entityMetadata); assertThat(attr.getProcedureName(), is(equalTo("plus1inout"))); - assertThat(attr.getOutputParameterType(), is(typeCompatibleWith(Integer.class))); - assertThat(attr.getOutputParameterName(), is(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME)); + assertThat(attr.getOutputParameterTypes().get(0), is(typeCompatibleWith(Integer.class))); + assertThat(attr.getOutputParameterNames().get(0), is(StoredProcedureAttributes.SYNTHETIC_OUTPUT_PARAMETER_NAME)); } @Test // DATAJPA-871 @@ -153,8 +179,8 @@ public class StoredProcedureAttributeSourceUnitTests { .createFrom(method("plus1inoutWithComposedAnnotationOverridingName", Integer.class), entityMetadata); assertThat(attr.getProcedureName(), is(equalTo("User.plus1"))); - assertThat(attr.getOutputParameterType(), is(typeCompatibleWith(Integer.class))); - assertThat(attr.getOutputParameterName(), is(equalTo("res"))); + assertThat(attr.getOutputParameterTypes().get(0), is(typeCompatibleWith(Integer.class))); + assertThat(attr.getOutputParameterNames().get(0), is(equalTo("res"))); } private static Method method(String name, Class... paramTypes) { @@ -198,6 +224,19 @@ public class StoredProcedureAttributeSourceUnitTests { @Procedure(name = "User.plus1IO") // DATAJPA-455 Integer entityAnnotatedCustomNamedProcedurePlus1IO(@Param("arg") Integer arg); + /** + * Explicitly mapped to named stored procedure "User.plus1IO" in {@link EntityManager}. + * With a outputParameterName + */ + @Procedure(name = "User.plus1IO", outputParameterName = "override") // DATAJPA-707 + Integer entityAnnotatedCustomNamedProcedureOutputParamNamePlus1IO(@Param("arg") Integer arg); + + /** + * Explicitly mapped to named stored procedure "User.plus1IO2" in {@link EntityManager}. + */ + @Procedure(name = "User.plus1IO2") // DATAJPA-707 + Map entityAnnotatedCustomNamedProcedurePlus1IO2(@Param("arg") Integer arg); + /** * Implicitly mapped to named stored procedure "User.plus1" in {@link EntityManager}. */ 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 d770f438b..d6ada7498 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 @@ -32,6 +32,6 @@ public class StoredProcedureAttributesUnitTests { public void usesSyntheticOutputParameterNameForAdhocProcedureWithoutOutputName() { StoredProcedureAttributes attributes = new StoredProcedureAttributes("procedure", null, Long.class, false); - assertThat(attributes.getOutputParameterName(), is(SYNTHETIC_OUTPUT_PARAMETER_NAME)); + assertThat(attributes.getOutputParameterNames().get(0), is(SYNTHETIC_OUTPUT_PARAMETER_NAME)); } } diff --git a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index 39a28ab8f..2c7c2c43a 100644 --- a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -50,6 +50,7 @@ import com.google.common.base.Optional; * @author Oliver Gierke * @author Thomas Darimont * @author Kevin Peters + * @author Jeff Sheets */ public interface UserRepository extends JpaRepository, JpaSpecificationExecutor, UserRepositoryCustom { @@ -318,12 +319,40 @@ public interface UserRepository @Procedure(procedureName = "plus1inout") // DATAJPA-455 Integer plus1inout(Integer arg); + /** + * Implicitly mapped to a procedure with name "plus1inout" in database via alias. + * Showing that outputParameterName is ignored when not a NamedStoredProcedure + */ + @Procedure(procedureName = "plus1inout", outputParameterName = "fakeName") // DATAJPA-707 + Integer plus1inoutInvalidOutParamName(Integer arg); + /** * Explicitly mapped to named stored procedure "User.plus1IO" in {@link EntityManager}. */ @Procedure(name = "User.plus1IO") // DATAJPA-455 Integer entityAnnotatedCustomNamedProcedurePlus1IO(@Param("arg") Integer arg); + /** + * Explicitly mapped to named stored procedure "User.plus1IO" in {@link EntityManager}. + * with an invalid outputParameterName - test will fail + */ + @Procedure(name = "User.plus1IO", outputParameterName = "fakeName") // DATAJPA-707 + Integer entityAnnotatedCustomNamedProcedurePlus1IOInvalidOutParamName(@Param("arg") Integer arg); + + /** + * Explicitly mapped to named stored procedure "User.plus1IO2" in {@link EntityManager}. + * Stored Proc has 2 out params, but naming one out param here so it only returns one + */ + @Procedure(name = "User.plus1IO2", outputParameterName = "res2") // DATAJPA-707 + Integer entityAnnotatedCustomNamedProcedurePlus1IO2TwoOutParamsButNamingOne(@Param("arg") Integer arg); + + /** + * Explicitly mapped to named stored procedure "User.plus1IO2" in {@link EntityManager}. + * Returns 2 out params as a List + */ + @Procedure(name = "User.plus1IO2") // DATAJPA-707 + Map entityAnnotatedCustomNamedProcedurePlus1IO2(@Param("arg") Integer arg); + /** * Implicitly mapped to named stored procedure "User.plus1" in {@link EntityManager}. */ diff --git a/src/test/resources/scripts/schema-stored-procedures.sql b/src/test/resources/scripts/schema-stored-procedures.sql index f2df7ff73..12433eb92 100644 --- a/src/test/resources/scripts/schema-stored-procedures.sql +++ b/src/test/resources/scripts/schema-stored-procedures.sql @@ -6,6 +6,14 @@ BEGIN ATOMIC set res = arg + 1; END /; +DROP procedure IF EXISTS plus1inout2 +/; +CREATE procedure plus1inout2 (IN arg int, OUT res int, OUT res2 int) +BEGIN ATOMIC + set res = arg + 1; + set res2 = arg + 2; +END +/; DROP procedure IF EXISTS procedure_in1_out1 /; DROP procedure IF EXISTS procedure_in1_out0