Skip to content

Commit a19da95

Browse files
committed
Fix retrieval of multiple OUT parameters from a stored procedure returning also a ResultSet.
Closes #2381
1 parent c7a7847 commit a19da95

File tree

5 files changed

+75
-8
lines changed

5 files changed

+75
-8
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.lang.reflect.Method;
2424
import java.util.Collection;
2525
import java.util.List;
26+
import java.util.Map;
2627
import java.util.Optional;
2728

2829
import org.springframework.core.convert.ConversionService;
@@ -339,6 +340,7 @@ protected Object doExecute(AbstractJpaQuery jpaQuery, JpaParametersParameterAcce
339340

340341
StoredProcedureJpaQuery query = (StoredProcedureJpaQuery) jpaQuery;
341342
StoredProcedureQuery procedure = query.createQuery(accessor);
343+
Class<?> returnType = query.getQueryMethod().getReturnType();
342344

343345
try {
344346

@@ -350,7 +352,9 @@ protected Object doExecute(AbstractJpaQuery jpaQuery, JpaParametersParameterAcce
350352
throw new InvalidDataAccessApiUsageException(NO_SURROUNDING_TRANSACTION);
351353
}
352354

353-
return collectionQuery ? procedure.getResultList() : procedure.getSingleResult();
355+
if (!Map.class.isAssignableFrom(returnType)) {
356+
return collectionQuery ? procedure.getResultList() : procedure.getSingleResult();
357+
}
354358
}
355359

356360
return query.extractOutputValue(procedure);

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributes.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18+
import jakarta.persistence.StoredProcedureQuery;
19+
1820
import java.util.Collections;
1921
import java.util.List;
2022
import java.util.stream.Collectors;
2123
import java.util.stream.IntStream;
2224

23-
import jakarta.persistence.StoredProcedureQuery;
24-
2525
import org.springframework.util.Assert;
26+
import org.springframework.util.ClassUtils;
2627
import org.springframework.util.StringUtils;
2728

2829
/**
@@ -138,7 +139,12 @@ public boolean hasReturnValue() {
138139
if (getOutputProcedureParameters().isEmpty())
139140
return false;
140141

141-
Class<?> outputType = getOutputProcedureParameters().get(0).getType();
142-
return !(void.class.equals(outputType) || Void.class.equals(outputType));
142+
for (ProcedureParameter parameter : getOutputProcedureParameters()) {
143+
if (!ClassUtils.isVoidType(parameter.getType())) {
144+
return true;
145+
}
146+
}
147+
148+
return false;
143149
}
144150
}

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/PostgresStoredProcedureIntegrationTests.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
* @author Greg Turnquist
5858
* @author Yanming Zhou
5959
* @author Thorben Janssen
60+
* @author Mark Paluch
6061
*/
6162
@Transactional
6263
@ExtendWith(SpringExtension.class)
@@ -140,7 +141,7 @@ void testEntityListFromNamedProcedure() {
140141
new Employee(4, "Gabriel"));
141142
}
142143

143-
@Test // 3460
144+
@Test // GH-3460
144145
void testPositionalInOutParameter() {
145146

146147
Map results = repository.positionalInOut(1, 2);
@@ -149,6 +150,15 @@ void testPositionalInOutParameter() {
149150
assertThat(results.get("3")).isEqualTo(3);
150151
}
151152

153+
@Test // GH-3460
154+
void supportsMultipleOutParameters() {
155+
156+
Map<String, Object> results = repository.multiple_out(5);
157+
158+
assertThat(results).containsEntry("result1", 5).containsEntry("result2", 10);
159+
assertThat(results).containsKey("some_cursor");
160+
}
161+
152162
@Entity
153163
@NamedStoredProcedureQuery( //
154164
name = "get_employees_postgres", //
@@ -160,6 +170,13 @@ void testPositionalInOutParameter() {
160170
name = "Employee.noResultSet", //
161171
procedureName = "get_employees_count", //
162172
parameters = { @StoredProcedureParameter(mode = ParameterMode.OUT, name = "results", type = Integer.class) })
173+
@NamedStoredProcedureQuery( //
174+
name = "Employee.multiple_out", //
175+
procedureName = "multiple_out", //
176+
parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, name = "someNumber", type = Integer.class),
177+
@StoredProcedureParameter(mode = ParameterMode.REF_CURSOR, name = "some_cursor", type = void.class),
178+
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "result1", type = Integer.class),
179+
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "result2", type = Integer.class) })
163180
@NamedStoredProcedureQuery( //
164181
name = "positional_inout", //
165182
procedureName = "positional_inout_parameter_issue3460", //
@@ -243,6 +260,9 @@ public interface EmployeeRepositoryWithRefCursor extends JpaRepository<Employee,
243260
@Procedure(value = "get_employees_count")
244261
Integer noResultSet();
245262

263+
@Procedure(value = "multiple_out")
264+
Map<String, Object> multiple_out(int someNumber);
265+
246266
@Procedure(name = "get_employees_postgres", refCursor = true)
247267
List<Employee> entityListFromNamedProcedure();
248268

spring-data-jpa/src/test/resources/scripts/postgres-stored-procedures.sql

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,17 @@ $BODY$
5151
BEGIN
5252
outParam = 3;
5353
END;
54-
$BODY$;;
54+
$BODY$;;
55+
56+
CREATE OR REPLACE PROCEDURE multiple_out(IN someNumber integer, OUT some_cursor REFCURSOR,
57+
OUT result1 integer, OUT result2 integer)
58+
LANGUAGE 'plpgsql'
59+
AS
60+
$BODY$
61+
BEGIN
62+
result1 = 1 * someNumber;
63+
result2 = 2 * someNumber;
64+
65+
OPEN some_cursor FOR SELECT COUNT(*) FROM employee;
66+
END;
67+
$BODY$;;

src/main/antora/modules/ROOT/pages/jpa/stored-procedures.adoc

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,28 @@ Integer entityAnnotatedCustomNamedProcedurePlus1IO(@Param("arg") Integer arg);
9494
If the stored procedure getting called has a single out parameter that parameter may be returned as the return value of the method.
9595
If there are multiple out parameters specified in a `@NamedStoredProcedureQuery` annotation those can be returned as a `Map` with the key being the parameter name given in the `@NamedStoredProcedureQuery` annotation.
9696

97-
NOTE: Note that if the stored procedure returns a `ResultSet` then any `OUT` parameters are omitted as Java can only return a single method return value.
97+
NOTE: Note that if the stored procedure returns a `ResultSet` then any `OUT` parameters are omitted as Java can only return a single method return value unless the method declares a `Map` return type.
98+
99+
The following example shows how to obtain multiple `OUT` parameters if the stored procedure has multiple `OUT` parameters and is registered as `@NamedStoredProcedureQuery`. `@NamedStoredProcedureQuery` registration is required to provide parameter metadata.
100+
101+
.StoredProcedure metadata definitions on an entity.
102+
====
103+
[source,java]
104+
----
105+
@Entity
106+
@NamedStoredProcedureQuery(name = "User.multiple_out_parameters", procedureName = "multiple_out_parameters", parameters = {
107+
@StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class),
108+
@StoredProcedureParameter(mode = ParameterMode.REF_CURSOR, name = "some_cursor", type = void.class),
109+
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class) })
110+
public class User {}
111+
----
112+
====
113+
114+
.Returning multiple OUT parameters
115+
====
116+
[source,java]
117+
----
118+
@Procedure(name = "User.multiple_out_parameters")
119+
Map<String, Object> returnsMultipleOutParameters(@Param("arg") Integer arg);
120+
----
121+
====

0 commit comments

Comments
 (0)