Skip to content

Commit b5ec8cb

Browse files
Polishing.
Additional tests for stored procedure out parameter detection. Update Nullability annotations and Javadoc. Reduce method visibility. See: #3460
1 parent c662145 commit b5ec8cb

File tree

3 files changed

+135
-20
lines changed

3 files changed

+135
-20
lines changed

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

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616

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

19-
import java.util.Objects;
20-
2119
import jakarta.persistence.ParameterMode;
2220

21+
import java.util.Objects;
22+
2323
import org.springframework.lang.Nullable;
2424

2525
/**
@@ -32,7 +32,7 @@
3232
*/
3333
class ProcedureParameter {
3434

35-
private final String name;
35+
@Nullable private final String name;
3636
private final int position;
3737
private final ParameterMode mode;
3838
private final Class<?> type;
@@ -45,19 +45,28 @@ class ProcedureParameter {
4545
this.type = type;
4646
}
4747

48-
public String getName() {
48+
/**
49+
* @return the parameter name. Can be {@literal null}.
50+
*/
51+
@Nullable
52+
String getName() {
4953
return name;
5054
}
5155

52-
public int getPosition() {
56+
/**
57+
* @return the {@code one} based parameter position as listed in
58+
* {@link jakarta.persistence.NamedStoredProcedureQuery#parameters()}
59+
* @since 3.2.6
60+
*/
61+
int getPosition() {
5362
return position;
5463
}
5564

56-
public ParameterMode getMode() {
65+
ParameterMode getMode() {
5766
return mode;
5867
}
5968

60-
public Class<?> getType() {
69+
Class<?> getType() {
6170
return type;
6271
}
6372

@@ -83,6 +92,7 @@ public int hashCode() {
8392

8493
@Override
8594
public String toString() {
86-
return "ProcedureParameter{" + "name='" + name + '\'' + ", position=" + position + ", mode=" + mode + ", type=" + type + '}';
95+
return "ProcedureParameter{" + "name='" + name + '\'' + ", position=" + position + ", mode=" + mode + ", type="
96+
+ type + '}';
8797
}
8898
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,12 @@ Object extractOutputValue(StoredProcedureQuery storedProcedureQuery) {
123123
return extractOutputParameterValue(outputParameters.get(0), storedProcedureQuery);
124124
}
125125

126-
Map<String, Object> outputValues = new HashMap<>();
127-
126+
Map<String, Object> outputValues = new HashMap<>(outputParameters.size());
128127
for (ProcedureParameter outputParameter : outputParameters) {
129-
outputValues.put(
130-
!outputParameter.getName().isEmpty() ? outputParameter.getName() : outputParameter.getPosition() + "",
131-
extractOutputParameterValue(outputParameter, storedProcedureQuery));
128+
129+
String param = StringUtils.hasText(outputParameter.getName()) ? outputParameter.getName()
130+
: Integer.toString(outputParameter.getPosition());
131+
outputValues.put(param, extractOutputParameterValue(outputParameter, storedProcedureQuery));
132132
}
133133

134134
return outputValues;

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

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

18-
import static org.assertj.core.api.Assertions.*;
19-
import static org.mockito.Mockito.*;
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.mockito.Mockito.doReturn;
20+
import static org.mockito.Mockito.when;
21+
22+
import jakarta.persistence.EntityManager;
23+
import jakarta.persistence.Id;
24+
import jakarta.persistence.NamedStoredProcedureQuery;
25+
import jakarta.persistence.ParameterMode;
26+
import jakarta.persistence.StoredProcedureParameter;
2027

2128
import java.lang.annotation.Retention;
2229
import java.lang.annotation.RetentionPolicy;
2330
import java.lang.reflect.Method;
31+
import java.util.ArrayList;
32+
import java.util.Arrays;
2433
import java.util.List;
2534
import java.util.Map;
2635

27-
import jakarta.persistence.EntityManager;
28-
import jakarta.persistence.ParameterMode;
29-
3036
import org.junit.jupiter.api.BeforeEach;
3137
import org.junit.jupiter.api.Test;
3238
import org.junit.jupiter.api.extension.ExtendWith;
39+
import org.junit.jupiter.params.ParameterizedTest;
40+
import org.junit.jupiter.params.provider.ValueSource;
3341
import org.mockito.Mock;
3442
import org.mockito.junit.jupiter.MockitoExtension;
3543
import org.mockito.junit.jupiter.MockitoSettings;
@@ -58,14 +66,14 @@
5866
class StoredProcedureAttributeSourceUnitTests {
5967

6068
private StoredProcedureAttributeSource creator;
61-
@Mock JpaEntityMetadata<User> entityMetadata;
69+
@Mock JpaEntityMetadata<?> entityMetadata;
6270

6371
@BeforeEach
6472
void setup() {
6573

6674
creator = StoredProcedureAttributeSource.INSTANCE;
6775

68-
when(entityMetadata.getJavaType()).thenReturn(User.class);
76+
doReturn(User.class).when(entityMetadata).getJavaType();
6977
when(entityMetadata.getEntityName()).thenReturn("User");
7078
}
7179

@@ -325,6 +333,34 @@ public void testEntityListFromResultSetWithInputAndNamedOutputAndCursor() {
325333
assertThat(outputParameter.getName()).isEqualTo("dummies");
326334
}
327335

336+
@ParameterizedTest // GH-3463
337+
@ValueSource(
338+
strings = { "inInOut", "inoutInOut", "inoutOut", "outInIn", "inInInoutOut", "inInoutInOut", "inInoutInoutOut" })
339+
void storedProcedureParameterInoutAndOutParameterPositionDetection(String methodName) {
340+
341+
String[] paramPattern = methodName.split("(?=[A-Z])");
342+
Class<?>[] methodArgs = new Class<?>[paramPattern.length - 1];
343+
Arrays.fill(methodArgs, Integer.class);
344+
345+
List<Integer> expectedOut = new ArrayList<>(2);
346+
int position = 0;
347+
for (String s : paramPattern) {
348+
position++;
349+
switch (s.toLowerCase()) {
350+
case "inout", "out" -> expectedOut.add(position);
351+
}
352+
}
353+
354+
doReturn(InOut.class).when(entityMetadata).getJavaType();
355+
when(entityMetadata.getEntityName()).thenReturn("InOut");
356+
357+
StoredProcedureAttributes attr = creator.createFrom(method(methodName, methodArgs), entityMetadata);
358+
assertThat(attr.getOutputProcedureParameters()).extracting(ProcedureParameter::getPosition) //
359+
.withFailMessage("Expecting method %s to have %s out parameters at positions %s but was %s.", methodName,
360+
expectedOut.size(), expectedOut, attr.getOutputProcedureParameters()) //
361+
.isEqualTo(expectedOut);
362+
}
363+
328364
private static Method method(String name, Class<?>... paramTypes) {
329365
return ReflectionUtils.findMethod(DummyRepository.class, name, paramTypes);
330366
}
@@ -410,6 +446,27 @@ private interface DummyRepository {
410446

411447
@Procedure(value = "1_input_1_resultset", outputParameterName = "dummies", refCursor = true) // DATAJPA-1657
412448
List<Dummy> entityListFromResultSetWithInputAndNamedOutputAndCursor(Integer arg);
449+
450+
@Procedure(name = "InOut.in_in_out")
451+
Map<Object, Object> inInOut(Integer in1, Integer in2);
452+
453+
@Procedure(name = "InOut.inout_in_out")
454+
Map<Object, Object> inoutInOut(Integer inout1, Integer in2);
455+
456+
@Procedure(name = "InOut.inout_out")
457+
Map<Object, Object> inoutOut(Integer inout1);
458+
459+
@Procedure(name = "InOut.out_in_in")
460+
Map<Object, Object> outInIn(Integer in1, Integer in2);
461+
462+
@Procedure(name = "InOut.in_in_inout_out")
463+
Map<Object, Object> inInInoutOut(Integer in1, Integer in2, Integer inout);
464+
465+
@Procedure(name = "InOut.in_inout_in_out")
466+
Map<Object, Object> inInoutInOut(Integer in1, Integer inout, Integer in2);
467+
468+
@Procedure(name = "InOut.in_inout_inout_out")
469+
Map<Object, Object> inInoutInoutOut(Integer in1, Integer inout1, Integer inout2);
413470
}
414471

415472
@SuppressWarnings("unused")
@@ -429,4 +486,52 @@ private interface DummyRepository {
429486
@AliasFor(annotation = Procedure.class, attribute = "outputParameterName")
430487
String outParamName() default "";
431488
}
489+
490+
@NamedStoredProcedureQuery( //
491+
name = "InOut.in_in_out", //
492+
procedureName = "positional_in_in_out", //
493+
parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, type = Integer.class),
494+
@StoredProcedureParameter(mode = ParameterMode.IN, type = Integer.class),
495+
@StoredProcedureParameter(mode = ParameterMode.OUT, type = Integer.class) })
496+
@NamedStoredProcedureQuery( //
497+
name = "InOut.inout_in_out", //
498+
procedureName = "positional_inout_in_out", //
499+
parameters = { @StoredProcedureParameter(mode = ParameterMode.INOUT, type = Integer.class),
500+
@StoredProcedureParameter(mode = ParameterMode.IN, type = Integer.class),
501+
@StoredProcedureParameter(mode = ParameterMode.OUT, type = Integer.class) })
502+
@NamedStoredProcedureQuery( //
503+
name = "InOut.inout_out", //
504+
procedureName = "positional_inout_out", //
505+
parameters = { @StoredProcedureParameter(mode = ParameterMode.INOUT, type = Integer.class),
506+
@StoredProcedureParameter(mode = ParameterMode.OUT, type = Integer.class) })
507+
@NamedStoredProcedureQuery( //
508+
name = "InOut.out_in_in", //
509+
procedureName = "positional_out_in_in", //
510+
parameters = { @StoredProcedureParameter(mode = ParameterMode.OUT, type = Integer.class),
511+
@StoredProcedureParameter(mode = ParameterMode.IN, type = Integer.class),
512+
@StoredProcedureParameter(mode = ParameterMode.IN, type = Integer.class) })
513+
@NamedStoredProcedureQuery( //
514+
name = "InOut.in_in_inout_out", //
515+
procedureName = "positional_in_in_inout_out", //
516+
parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, type = Integer.class),
517+
@StoredProcedureParameter(mode = ParameterMode.IN, type = Integer.class),
518+
@StoredProcedureParameter(mode = ParameterMode.INOUT, type = Integer.class),
519+
@StoredProcedureParameter(mode = ParameterMode.OUT, type = Integer.class) })
520+
@NamedStoredProcedureQuery( //
521+
name = "InOut.in_inout_in_out", //
522+
procedureName = "positional_in_inout_in_out", //
523+
parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, type = Integer.class),
524+
@StoredProcedureParameter(mode = ParameterMode.INOUT, type = Integer.class),
525+
@StoredProcedureParameter(mode = ParameterMode.IN, type = Integer.class),
526+
@StoredProcedureParameter(mode = ParameterMode.OUT, type = Integer.class) })
527+
@NamedStoredProcedureQuery( //
528+
name = "InOut.in_inout_inout_out", //
529+
procedureName = "positional_in_inout_inout_out", //
530+
parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, type = Integer.class),
531+
@StoredProcedureParameter(mode = ParameterMode.INOUT, type = Integer.class),
532+
@StoredProcedureParameter(mode = ParameterMode.INOUT, type = Integer.class),
533+
@StoredProcedureParameter(mode = ParameterMode.OUT, type = Integer.class) })
534+
private static class InOut {
535+
@Id private Long id;
536+
}
432537
}

0 commit comments

Comments
 (0)