Fix retrieval of multiple OUT parameters from a stored procedure returning also a ResultSet.

Closes #2381
This commit is contained in:
Mark Paluch 2024-10-21 15:27:49 +02:00
parent e5ec3ad418
commit b454c70131
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
5 changed files with 75 additions and 8 deletions

View File

@ -23,6 +23,7 @@ import jakarta.persistence.StoredProcedureQuery;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.core.convert.ConversionService;
@ -339,6 +340,7 @@ public abstract class JpaQueryExecution {
StoredProcedureJpaQuery query = (StoredProcedureJpaQuery) jpaQuery;
StoredProcedureQuery procedure = query.createQuery(accessor);
Class<?> returnType = query.getQueryMethod().getReturnType();
try {
@ -350,7 +352,9 @@ public abstract class JpaQueryExecution {
throw new InvalidDataAccessApiUsageException(NO_SURROUNDING_TRANSACTION);
}
return collectionQuery ? procedure.getResultList() : procedure.getSingleResult();
if (!Map.class.isAssignableFrom(returnType)) {
return collectionQuery ? procedure.getResultList() : procedure.getSingleResult();
}
}
return query.extractOutputValue(procedure);

View File

@ -15,14 +15,15 @@
*/
package org.springframework.data.jpa.repository.query;
import jakarta.persistence.StoredProcedureQuery;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import jakarta.persistence.StoredProcedureQuery;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
@ -138,7 +139,12 @@ class StoredProcedureAttributes {
if (getOutputProcedureParameters().isEmpty())
return false;
Class<?> outputType = getOutputProcedureParameters().get(0).getType();
return !(void.class.equals(outputType) || Void.class.equals(outputType));
for (ProcedureParameter parameter : getOutputProcedureParameters()) {
if (!ClassUtils.isVoidType(parameter.getType())) {
return true;
}
}
return false;
}
}

View File

@ -57,6 +57,7 @@ import org.testcontainers.containers.PostgreSQLContainer;
* @author Greg Turnquist
* @author Yanming Zhou
* @author Thorben Janssen
* @author Mark Paluch
*/
@Transactional
@ExtendWith(SpringExtension.class)
@ -140,7 +141,7 @@ class PostgresStoredProcedureIntegrationTests {
new Employee(4, "Gabriel"));
}
@Test // 3460
@Test // GH-3460
void testPositionalInOutParameter() {
Map results = repository.positionalInOut(1, 2);
@ -149,6 +150,15 @@ class PostgresStoredProcedureIntegrationTests {
assertThat(results.get("3")).isEqualTo(3);
}
@Test // GH-3460
void supportsMultipleOutParameters() {
Map<String, Object> results = repository.multiple_out(5);
assertThat(results).containsEntry("result1", 5).containsEntry("result2", 10);
assertThat(results).containsKey("some_cursor");
}
@Entity
@NamedStoredProcedureQuery( //
name = "get_employees_postgres", //
@ -160,6 +170,13 @@ class PostgresStoredProcedureIntegrationTests {
name = "Employee.noResultSet", //
procedureName = "get_employees_count", //
parameters = { @StoredProcedureParameter(mode = ParameterMode.OUT, name = "results", type = Integer.class) })
@NamedStoredProcedureQuery( //
name = "Employee.multiple_out", //
procedureName = "multiple_out", //
parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, name = "someNumber", type = Integer.class),
@StoredProcedureParameter(mode = ParameterMode.REF_CURSOR, name = "some_cursor", type = void.class),
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "result1", type = Integer.class),
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "result2", type = Integer.class) })
@NamedStoredProcedureQuery( //
name = "positional_inout", //
procedureName = "positional_inout_parameter_issue3460", //
@ -243,6 +260,9 @@ class PostgresStoredProcedureIntegrationTests {
@Procedure(value = "get_employees_count")
Integer noResultSet();
@Procedure(value = "multiple_out")
Map<String, Object> multiple_out(int someNumber);
@Procedure(name = "get_employees_postgres", refCursor = true)
List<Employee> entityListFromNamedProcedure();

View File

@ -51,4 +51,17 @@ $BODY$
BEGIN
outParam = 3;
END;
$BODY$;;
$BODY$;;
CREATE OR REPLACE PROCEDURE multiple_out(IN someNumber integer, OUT some_cursor REFCURSOR,
OUT result1 integer, OUT result2 integer)
LANGUAGE 'plpgsql'
AS
$BODY$
BEGIN
result1 = 1 * someNumber;
result2 = 2 * someNumber;
OPEN some_cursor FOR SELECT COUNT(*) FROM employee;
END;
$BODY$;;

View File

@ -94,4 +94,28 @@ Integer entityAnnotatedCustomNamedProcedurePlus1IO(@Param("arg") Integer arg);
If the stored procedure getting called has a single out parameter that parameter may be returned as the return value of the method.
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.
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.
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.
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.
.StoredProcedure metadata definitions on an entity.
====
[source,java]
----
@Entity
@NamedStoredProcedureQuery(name = "User.multiple_out_parameters", procedureName = "multiple_out_parameters", parameters = {
@StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class),
@StoredProcedureParameter(mode = ParameterMode.REF_CURSOR, name = "some_cursor", type = void.class),
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class) })
public class User {}
----
====
.Returning multiple OUT parameters
====
[source,java]
----
@Procedure(name = "User.multiple_out_parameters")
Map<String, Object> returnsMultipleOutParameters(@Param("arg") Integer arg);
----
====