Skip to content

Commit

Permalink
Fix retrieval of multiple OUT parameters from a stored procedure retu…
Browse files Browse the repository at this point in the history
…rning also a `ResultSet`.

Closes #2381
  • Loading branch information
mp911de committed Oct 21, 2024
1 parent e5ec3ad commit b454c70
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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;
Expand Down Expand Up @@ -339,6 +340,7 @@ protected Object doExecute(AbstractJpaQuery jpaQuery, JpaParametersParameterAcce

StoredProcedureJpaQuery query = (StoredProcedureJpaQuery) jpaQuery;
StoredProcedureQuery procedure = query.createQuery(accessor);
Class<?> returnType = query.getQueryMethod().getReturnType();

try {

Expand All @@ -350,7 +352,9 @@ protected Object doExecute(AbstractJpaQuery jpaQuery, JpaParametersParameterAcce
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -138,7 +139,12 @@ public boolean hasReturnValue() {
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
* @author Greg Turnquist
* @author Yanming Zhou
* @author Thorben Janssen
* @author Mark Paluch
*/
@Transactional
@ExtendWith(SpringExtension.class)
Expand Down Expand Up @@ -140,7 +141,7 @@ void testEntityListFromNamedProcedure() {
new Employee(4, "Gabriel"));
}

@Test // 3460
@Test // GH-3460
void testPositionalInOutParameter() {

Map results = repository.positionalInOut(1, 2);
Expand All @@ -149,6 +150,15 @@ void testPositionalInOutParameter() {
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", //
Expand All @@ -160,6 +170,13 @@ void testPositionalInOutParameter() {
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", //
Expand Down Expand Up @@ -243,6 +260,9 @@ public interface EmployeeRepositoryWithRefCursor extends JpaRepository<Employee,
@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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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$;;
26 changes: 25 additions & 1 deletion src/main/antora/modules/ROOT/pages/jpa/stored-procedures.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
----
====

0 comments on commit b454c70

Please sign in to comment.