1515 */
1616package 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
2128import java .lang .annotation .Retention ;
2229import java .lang .annotation .RetentionPolicy ;
2330import java .lang .reflect .Method ;
31+ import java .util .ArrayList ;
32+ import java .util .Arrays ;
2433import java .util .List ;
2534import java .util .Map ;
2635
27- import jakarta .persistence .EntityManager ;
28- import jakarta .persistence .ParameterMode ;
29-
3036import org .junit .jupiter .api .BeforeEach ;
3137import org .junit .jupiter .api .Test ;
3238import org .junit .jupiter .api .extension .ExtendWith ;
39+ import org .junit .jupiter .params .ParameterizedTest ;
40+ import org .junit .jupiter .params .provider .ValueSource ;
3341import org .mockito .Mock ;
3442import org .mockito .junit .jupiter .MockitoExtension ;
3543import org .mockito .junit .jupiter .MockitoSettings ;
5866class 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