Retain stored procedure parameter position.

Store the position of each output parameter in ProcedureParameter so that it can be used when extracting them from the result set.

Resolves: #3460
Original Pull Request: #3463
This commit is contained in:
Thorben Janssen
2024-05-04 11:23:45 +02:00
committed by Christoph Strobl
parent 3eb9c4b096
commit c66214540e
7 changed files with 77 additions and 34 deletions

View File

@@ -28,16 +28,19 @@ import org.springframework.lang.Nullable;
*
* @author Gabriel Basilio
* @author Greg Turnquist
* @author Thorben Janssen
*/
class ProcedureParameter {
private final String name;
private final int position;
private final ParameterMode mode;
private final Class<?> type;
ProcedureParameter(@Nullable String name, ParameterMode mode, Class<?> type) {
ProcedureParameter(@Nullable String name, int position, ParameterMode mode, Class<?> type) {
this.name = name;
this.position = position;
this.mode = mode;
this.type = type;
}
@@ -46,6 +49,10 @@ class ProcedureParameter {
return name;
}
public int getPosition() {
return position;
}
public ParameterMode getMode() {
return mode;
}
@@ -76,6 +83,6 @@ class ProcedureParameter {
@Override
public String toString() {
return "ProcedureParameter{" + "name='" + name + '\'' + ", mode=" + mode + ", type=" + type + '}';
return "ProcedureParameter{" + "name='" + name + '\'' + ", position=" + position + ", mode=" + mode + ", type=" + type + '}';
}
}

View File

@@ -15,17 +15,16 @@
*/
package org.springframework.data.jpa.repository.query;
import jakarta.persistence.NamedStoredProcedureQueries;
import jakarta.persistence.NamedStoredProcedureQuery;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.StoredProcedureParameter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import jakarta.persistence.NamedStoredProcedureQueries;
import jakarta.persistence.NamedStoredProcedureQuery;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.StoredProcedureParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.Nullable;
@@ -44,6 +43,7 @@ import org.springframework.util.StringUtils;
* @author Jeff Sheets
* @author Gabriel Basilio
* @author Greg Turnquist
* @author Thorben Janssen
* @since 1.6
*/
enum StoredProcedureAttributeSource {
@@ -119,10 +119,7 @@ enum StoredProcedureAttributeSource {
} else {
// try to discover the output parameter
outputParameters = extractOutputParametersFrom(namedStoredProc).stream() //
.map(namedParameter -> new ProcedureParameter(namedParameter.name(), namedParameter.mode(),
namedParameter.type())) //
.collect(Collectors.toList());
outputParameters = extractOutputParametersFrom(namedStoredProc);
}
return new StoredProcedureAttributes(namedStoredProc.name(), outputParameters, true);
@@ -137,33 +134,35 @@ enum StoredProcedureAttributeSource {
*/
private ProcedureParameter createOutputProcedureParameterFrom(Method method, Procedure procedure) {
return new ProcedureParameter(procedure.outputParameterName(),
return new ProcedureParameter(procedure.outputParameterName(), 1,
procedure.refCursor() ? ParameterMode.REF_CURSOR : ParameterMode.OUT, method.getReturnType());
}
/**
* Translate all the {@Link NamedStoredProcedureQuery} parameters into a {@link List} of
* {@link StoredProcedureParameter}s.
* Translate all the {@Link NamedStoredProcedureQuery} parameters into a {@link List} of {@link ProcedureParameter}s.
*
* @param namedStoredProc
* @return
*/
private List<StoredProcedureParameter> extractOutputParametersFrom(NamedStoredProcedureQuery namedStoredProc) {
private List<ProcedureParameter> extractOutputParametersFrom(NamedStoredProcedureQuery namedStoredProc) {
List<StoredProcedureParameter> outputParameters = new ArrayList<>();
List<ProcedureParameter> outputParameters = new ArrayList<>();
int position = 1;
for (StoredProcedureParameter param : namedStoredProc.parameters()) {
switch (param.mode()) {
case OUT:
case INOUT:
case REF_CURSOR:
outputParameters.add(param);
outputParameters.add(new ProcedureParameter(param.name(), position, param.mode(), param.type()));
break;
case IN:
default:
continue;
// not an output parameter
}
position++;
}
return outputParameters;

View File

@@ -34,6 +34,7 @@ import org.springframework.util.StringUtils;
* @author Jeff Sheets
* @author Jens Schauder
* @author Gabriel Basilio
* @author Thorben Janssen
* @since 1.6
*/
class StoredProcedureAttributes {
@@ -87,7 +88,7 @@ class StoredProcedureAttributes {
private ProcedureParameter getParameterWithCompletedName(ProcedureParameter parameter, int i) {
return new ProcedureParameter(completeOutputParameterName(i, parameter.getName()), parameter.getMode(),
return new ProcedureParameter(completeOutputParameterName(i, parameter.getName()), parameter.getPosition(), parameter.getMode(),
parameter.getType());
}

View File

@@ -43,6 +43,7 @@ import org.springframework.util.StringUtils;
* @author Mark Paluch
* @author Jeff Sheets
* @author JyotirmoyVS
* @author Thorben Janssen
* @since 1.6
*/
class StoredProcedureJpaQuery extends AbstractJpaQuery {
@@ -119,15 +120,15 @@ class StoredProcedureJpaQuery extends AbstractJpaQuery {
List<ProcedureParameter> outputParameters = procedureAttributes.getOutputProcedureParameters();
if (outputParameters.size() == 1) {
return extractOutputParameterValue(outputParameters.get(0), 0, storedProcedureQuery);
return extractOutputParameterValue(outputParameters.get(0), storedProcedureQuery);
}
Map<String, Object> outputValues = new HashMap<>();
for (int i = 0; i < outputParameters.size(); i++) {
ProcedureParameter outputParameter = outputParameters.get(i);
outputValues.put(outputParameter.getName(),
extractOutputParameterValue(outputParameter, i, storedProcedureQuery));
for (ProcedureParameter outputParameter : outputParameters) {
outputValues.put(
!outputParameter.getName().isEmpty() ? outputParameter.getName() : outputParameter.getPosition() + "",
extractOutputParameterValue(outputParameter, storedProcedureQuery));
}
return outputValues;
@@ -136,14 +137,12 @@ class StoredProcedureJpaQuery extends AbstractJpaQuery {
/**
* @return The value of an output parameter either by name or by index.
*/
private Object extractOutputParameterValue(ProcedureParameter outputParameter, Integer index,
private Object extractOutputParameterValue(ProcedureParameter outputParameter,
StoredProcedureQuery storedProcedureQuery) {
JpaParameters methodParameters = getQueryMethod().getParameters();
return useNamedParameters && StringUtils.hasText(outputParameter.getName())
? storedProcedureQuery.getOutputParameterValue(outputParameter.getName())
: storedProcedureQuery.getOutputParameterValue(methodParameters.getNumberOfParameters() + index + 1);
: storedProcedureQuery.getOutputParameterValue(outputParameter.getPosition());
}
/**
@@ -236,7 +235,6 @@ class StoredProcedureJpaQuery extends AbstractJpaQuery {
}
/**
*
* @return true if the stored procedure will use a ResultSet to return data and not output parameters
*/
private boolean isResultSetProcedure() {

View File

@@ -16,7 +16,7 @@
package org.springframework.data.jpa.repository.procedures;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assertions.assertThat;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityManagerFactory;
@@ -28,6 +28,7 @@ import jakarta.persistence.StoredProcedureParameter;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
@@ -45,7 +46,6 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.jpa.repository.query.Procedure;
import org.springframework.data.jpa.util.DisabledOnHibernate61;
import org.springframework.data.jpa.util.DisabledOnHibernate62;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
@@ -66,8 +66,8 @@ import org.testcontainers.containers.PostgreSQLContainer;
* @author Gabriel Basilio
* @author Greg Turnquist
* @author Yanming Zhou
* @author Thorben Janssen
*/
@DisabledOnHibernate61 // GH-2903
@Transactional
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = PostgresStoredProcedureIntegrationTests.Config.class)
@@ -150,12 +150,28 @@ class PostgresStoredProcedureIntegrationTests {
new Employee(4, "Gabriel"));
}
@Test // 3460
void testPositionalInOutParameter() {
Map results = repository.positionalInOut(1, 2);
assertThat(results.get("2")).isEqualTo(2);
assertThat(results.get("3")).isEqualTo(3);
}
@Entity
@NamedStoredProcedureQuery( //
name = "get_employees_postgres", //
procedureName = "get_employees", //
parameters = { @StoredProcedureParameter(mode = ParameterMode.REF_CURSOR, type = void.class) }, //
resultClasses = Employee.class)
@NamedStoredProcedureQuery( //
name = "positional_inout", //
procedureName = "positional_inout_parameter_issue3460", //
parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, type = Integer.class),
@StoredProcedureParameter(mode = ParameterMode.INOUT, type = Integer.class),
@StoredProcedureParameter(mode = ParameterMode.OUT, type = Integer.class) }, //
resultClasses = Employee.class)
public static class Employee {
@Id
@@ -234,6 +250,9 @@ class PostgresStoredProcedureIntegrationTests {
@Procedure(name = "get_employees_postgres", refCursor = true)
List<Employee> entityListFromNamedProcedure();
@Procedure(name = "positional_inout")
Map positionalInOut(Integer in, Integer inout);
}
@EnableJpaRepositories(considerNestedRepositories = true,

View File

@@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test;
*
* @author Oliver Gierke
* @author Jens Schauder
* @author Thorben Janssen
*/
class StoredProcedureAttributesUnitTests {
@@ -34,7 +35,7 @@ class StoredProcedureAttributesUnitTests {
void usesSyntheticOutputParameterNameForAdhocProcedureWithoutOutputName() {
StoredProcedureAttributes attributes = new StoredProcedureAttributes("procedure",
new ProcedureParameter(null, ParameterMode.OUT, Long.class));
new ProcedureParameter(null, 1, ParameterMode.OUT, Long.class));
assertThat(attributes.getOutputProcedureParameters().get(0).getName()).isEqualTo(SYNTHETIC_OUTPUT_PARAMETER_NAME);
}
}

View File

@@ -34,3 +34,21 @@ BEGIN
OPEN ref FOR SELECT * FROM employee WHERE employee.ID = 3;
END;
$BODY$;;
CREATE OR REPLACE PROCEDURE get_employees_count(OUT results integer)
LANGUAGE 'plpgsql'
AS
$BODY$
BEGIN
results = (SELECT COUNT(*) FROM employee);
END;
$BODY$;;
CREATE OR REPLACE PROCEDURE positional_inout_parameter_issue3460(IN inParam integer, INOUT inoutParam integer, OUT outParam integer)
LANGUAGE 'plpgsql'
AS
$BODY$
BEGIN
outParam = 3;
END;
$BODY$;;