Skip to content

Commit d8d6591

Browse files
thjanssenchristophstrobl
authored andcommitted
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
1 parent 8c1174f commit d8d6591

File tree

7 files changed

+77
-34
lines changed

7 files changed

+77
-34
lines changed

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

+9-2
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,19 @@
2828
*
2929
* @author Gabriel Basilio
3030
* @author Greg Turnquist
31+
* @author Thorben Janssen
3132
*/
3233
class ProcedureParameter {
3334

3435
private final String name;
36+
private final int position;
3537
private final ParameterMode mode;
3638
private final Class<?> type;
3739

38-
ProcedureParameter(@Nullable String name, ParameterMode mode, Class<?> type) {
40+
ProcedureParameter(@Nullable String name, int position, ParameterMode mode, Class<?> type) {
3941

4042
this.name = name;
43+
this.position = position;
4144
this.mode = mode;
4245
this.type = type;
4346
}
@@ -46,6 +49,10 @@ public String getName() {
4649
return name;
4750
}
4851

52+
public int getPosition() {
53+
return position;
54+
}
55+
4956
public ParameterMode getMode() {
5057
return mode;
5158
}
@@ -76,6 +83,6 @@ public int hashCode() {
7683

7784
@Override
7885
public String toString() {
79-
return "ProcedureParameter{" + "name='" + name + '\'' + ", mode=" + mode + ", type=" + type + '}';
86+
return "ProcedureParameter{" + "name='" + name + '\'' + ", position=" + position + ", mode=" + mode + ", type=" + type + '}';
8087
}
8188
}

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

+16-17
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,16 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18+
import jakarta.persistence.NamedStoredProcedureQueries;
19+
import jakarta.persistence.NamedStoredProcedureQuery;
20+
import jakarta.persistence.ParameterMode;
21+
import jakarta.persistence.StoredProcedureParameter;
22+
1823
import java.lang.reflect.Method;
1924
import java.util.ArrayList;
2025
import java.util.Arrays;
2126
import java.util.Collections;
2227
import java.util.List;
23-
import java.util.stream.Collectors;
24-
25-
import jakarta.persistence.NamedStoredProcedureQueries;
26-
import jakarta.persistence.NamedStoredProcedureQuery;
27-
import jakarta.persistence.ParameterMode;
28-
import jakarta.persistence.StoredProcedureParameter;
2928

3029
import org.springframework.core.annotation.AnnotatedElementUtils;
3130
import org.springframework.lang.Nullable;
@@ -44,6 +43,7 @@
4443
* @author Jeff Sheets
4544
* @author Gabriel Basilio
4645
* @author Greg Turnquist
46+
* @author Thorben Janssen
4747
* @since 1.6
4848
*/
4949
enum StoredProcedureAttributeSource {
@@ -119,10 +119,7 @@ private StoredProcedureAttributes newProcedureAttributesFrom(Method method, Name
119119
} else {
120120

121121
// try to discover the output parameter
122-
outputParameters = extractOutputParametersFrom(namedStoredProc).stream() //
123-
.map(namedParameter -> new ProcedureParameter(namedParameter.name(), namedParameter.mode(),
124-
namedParameter.type())) //
125-
.collect(Collectors.toList());
122+
outputParameters = extractOutputParametersFrom(namedStoredProc);
126123
}
127124

128125
return new StoredProcedureAttributes(namedStoredProc.name(), outputParameters, true);
@@ -137,33 +134,35 @@ private StoredProcedureAttributes newProcedureAttributesFrom(Method method, Name
137134
*/
138135
private ProcedureParameter createOutputProcedureParameterFrom(Method method, Procedure procedure) {
139136

140-
return new ProcedureParameter(procedure.outputParameterName(),
137+
return new ProcedureParameter(procedure.outputParameterName(), 1,
141138
procedure.refCursor() ? ParameterMode.REF_CURSOR : ParameterMode.OUT, method.getReturnType());
142139
}
143140

144141
/**
145-
* Translate all the {@Link NamedStoredProcedureQuery} parameters into a {@link List} of
146-
* {@link StoredProcedureParameter}s.
142+
* Translate all the {@Link NamedStoredProcedureQuery} parameters into a {@link List} of {@link ProcedureParameter}s.
147143
*
148144
* @param namedStoredProc
149145
* @return
150146
*/
151-
private List<StoredProcedureParameter> extractOutputParametersFrom(NamedStoredProcedureQuery namedStoredProc) {
147+
private List<ProcedureParameter> extractOutputParametersFrom(NamedStoredProcedureQuery namedStoredProc) {
152148

153-
List<StoredProcedureParameter> outputParameters = new ArrayList<>();
149+
List<ProcedureParameter> outputParameters = new ArrayList<>();
154150

151+
int position = 1;
155152
for (StoredProcedureParameter param : namedStoredProc.parameters()) {
156153

157154
switch (param.mode()) {
158155
case OUT:
159156
case INOUT:
160157
case REF_CURSOR:
161-
outputParameters.add(param);
158+
outputParameters.add(new ProcedureParameter(param.name(), position, param.mode(), param.type()));
162159
break;
163160
case IN:
164161
default:
165-
continue;
162+
// not an output parameter
166163
}
164+
165+
position++;
167166
}
168167

169168
return outputParameters;

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
* @author Jeff Sheets
3535
* @author Jens Schauder
3636
* @author Gabriel Basilio
37+
* @author Thorben Janssen
3738
* @since 1.6
3839
*/
3940
class StoredProcedureAttributes {
@@ -87,7 +88,7 @@ private List<ProcedureParameter> getParametersWithCompletedNames(List<ProcedureP
8788

8889
private ProcedureParameter getParameterWithCompletedName(ProcedureParameter parameter, int i) {
8990

90-
return new ProcedureParameter(completeOutputParameterName(i, parameter.getName()), parameter.getMode(),
91+
return new ProcedureParameter(completeOutputParameterName(i, parameter.getName()), parameter.getPosition(), parameter.getMode(),
9192
parameter.getType());
9293
}
9394

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

+8-10
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
* @author Mark Paluch
4444
* @author Jeff Sheets
4545
* @author JyotirmoyVS
46+
* @author Thorben Janssen
4647
* @since 1.6
4748
*/
4849
class StoredProcedureJpaQuery extends AbstractJpaQuery {
@@ -119,15 +120,15 @@ Object extractOutputValue(StoredProcedureQuery storedProcedureQuery) {
119120
List<ProcedureParameter> outputParameters = procedureAttributes.getOutputProcedureParameters();
120121

121122
if (outputParameters.size() == 1) {
122-
return extractOutputParameterValue(outputParameters.get(0), 0, storedProcedureQuery);
123+
return extractOutputParameterValue(outputParameters.get(0), storedProcedureQuery);
123124
}
124125

125126
Map<String, Object> outputValues = new HashMap<>();
126127

127-
for (int i = 0; i < outputParameters.size(); i++) {
128-
ProcedureParameter outputParameter = outputParameters.get(i);
129-
outputValues.put(outputParameter.getName(),
130-
extractOutputParameterValue(outputParameter, i, storedProcedureQuery));
128+
for (ProcedureParameter outputParameter : outputParameters) {
129+
outputValues.put(
130+
!outputParameter.getName().isEmpty() ? outputParameter.getName() : outputParameter.getPosition() + "",
131+
extractOutputParameterValue(outputParameter, storedProcedureQuery));
131132
}
132133

133134
return outputValues;
@@ -136,14 +137,12 @@ Object extractOutputValue(StoredProcedureQuery storedProcedureQuery) {
136137
/**
137138
* @return The value of an output parameter either by name or by index.
138139
*/
139-
private Object extractOutputParameterValue(ProcedureParameter outputParameter, Integer index,
140+
private Object extractOutputParameterValue(ProcedureParameter outputParameter,
140141
StoredProcedureQuery storedProcedureQuery) {
141142

142-
JpaParameters methodParameters = getQueryMethod().getParameters();
143-
144143
return useNamedParameters && StringUtils.hasText(outputParameter.getName())
145144
? storedProcedureQuery.getOutputParameterValue(outputParameter.getName())
146-
: storedProcedureQuery.getOutputParameterValue(methodParameters.getNumberOfParameters() + index + 1);
145+
: storedProcedureQuery.getOutputParameterValue(outputParameter.getPosition());
147146
}
148147

149148
/**
@@ -236,7 +235,6 @@ private StoredProcedureQuery createAdhocStoredProcedureQuery() {
236235
}
237236

238237
/**
239-
*
240238
* @return true if the stored procedure will use a ResultSet to return data and not output parameters
241239
*/
242240
private boolean isResultSetProcedure() {

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

+22-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package org.springframework.data.jpa.repository.procedures;
1818

19-
import static org.assertj.core.api.Assertions.*;
19+
import static org.assertj.core.api.Assertions.assertThat;
2020

2121
import jakarta.persistence.Entity;
2222
import jakarta.persistence.EntityManagerFactory;
@@ -28,6 +28,7 @@
2828

2929
import java.math.BigDecimal;
3030
import java.util.List;
31+
import java.util.Map;
3132
import java.util.Objects;
3233
import java.util.Properties;
3334

@@ -45,7 +46,6 @@
4546
import org.springframework.data.jpa.repository.JpaRepository;
4647
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
4748
import org.springframework.data.jpa.repository.query.Procedure;
48-
import org.springframework.data.jpa.util.DisabledOnHibernate61;
4949
import org.springframework.data.jpa.util.DisabledOnHibernate62;
5050
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
5151
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
@@ -66,8 +66,8 @@
6666
* @author Gabriel Basilio
6767
* @author Greg Turnquist
6868
* @author Yanming Zhou
69+
* @author Thorben Janssen
6970
*/
70-
@DisabledOnHibernate61 // GH-2903
7171
@Transactional
7272
@ExtendWith(SpringExtension.class)
7373
@ContextConfiguration(classes = PostgresStoredProcedureIntegrationTests.Config.class)
@@ -150,12 +150,28 @@ void testEntityListFromNamedProcedure() {
150150
new Employee(4, "Gabriel"));
151151
}
152152

153+
@Test // 3460
154+
void testPositionalInOutParameter() {
155+
156+
Map results = repository.positionalInOut(1, 2);
157+
158+
assertThat(results.get("2")).isEqualTo(2);
159+
assertThat(results.get("3")).isEqualTo(3);
160+
}
161+
153162
@Entity
154163
@NamedStoredProcedureQuery( //
155164
name = "get_employees_postgres", //
156165
procedureName = "get_employees", //
157166
parameters = { @StoredProcedureParameter(mode = ParameterMode.REF_CURSOR, type = void.class) }, //
158167
resultClasses = Employee.class)
168+
@NamedStoredProcedureQuery( //
169+
name = "positional_inout", //
170+
procedureName = "positional_inout_parameter_issue3460", //
171+
parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, type = Integer.class),
172+
@StoredProcedureParameter(mode = ParameterMode.INOUT, type = Integer.class),
173+
@StoredProcedureParameter(mode = ParameterMode.OUT, type = Integer.class) }, //
174+
resultClasses = Employee.class)
159175
public static class Employee {
160176

161177
@Id
@@ -234,6 +250,9 @@ public interface EmployeeRepositoryWithRefCursor extends JpaRepository<Employee,
234250

235251
@Procedure(name = "get_employees_postgres", refCursor = true)
236252
List<Employee> entityListFromNamedProcedure();
253+
254+
@Procedure(name = "positional_inout")
255+
Map positionalInOut(Integer in, Integer inout);
237256
}
238257

239258
@EnableJpaRepositories(considerNestedRepositories = true,

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributesUnitTests.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,15 @@
2727
*
2828
* @author Oliver Gierke
2929
* @author Jens Schauder
30+
* @author Thorben Janssen
3031
*/
3132
class StoredProcedureAttributesUnitTests {
3233

3334
@Test // DATAJPA-681
3435
void usesSyntheticOutputParameterNameForAdhocProcedureWithoutOutputName() {
3536

3637
StoredProcedureAttributes attributes = new StoredProcedureAttributes("procedure",
37-
new ProcedureParameter(null, ParameterMode.OUT, Long.class));
38+
new ProcedureParameter(null, 1, ParameterMode.OUT, Long.class));
3839
assertThat(attributes.getOutputProcedureParameters().get(0).getName()).isEqualTo(SYNTHETIC_OUTPUT_PARAMETER_NAME);
3940
}
4041
}

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

+18
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,21 @@ BEGIN
3434
OPEN ref FOR SELECT * FROM employee WHERE employee.ID = 3;
3535
END;
3636
$BODY$;;
37+
38+
CREATE OR REPLACE PROCEDURE get_employees_count(OUT results integer)
39+
LANGUAGE 'plpgsql'
40+
AS
41+
$BODY$
42+
BEGIN
43+
results = (SELECT COUNT(*) FROM employee);
44+
END;
45+
$BODY$;;
46+
47+
CREATE OR REPLACE PROCEDURE positional_inout_parameter_issue3460(IN inParam integer, INOUT inoutParam integer, OUT outParam integer)
48+
LANGUAGE 'plpgsql'
49+
AS
50+
$BODY$
51+
BEGIN
52+
outParam = 3;
53+
END;
54+
$BODY$;;

0 commit comments

Comments
 (0)