23
23
import jakarta .persistence .TupleElement ;
24
24
import jakarta .persistence .TypedQuery ;
25
25
26
+ import java .lang .reflect .Constructor ;
26
27
import java .util .Arrays ;
27
28
import java .util .Collection ;
28
29
import java .util .HashMap ;
54
55
import org .springframework .jdbc .support .JdbcUtils ;
55
56
import org .springframework .lang .Nullable ;
56
57
import org .springframework .util .Assert ;
58
+ import org .springframework .util .ClassUtils ;
57
59
import org .springframework .util .ReflectionUtils ;
58
60
59
61
/**
@@ -345,7 +347,7 @@ public TupleConverter(ReturnedType type, boolean nativeQuery) {
345
347
&& !type .getInputProperties ().isEmpty ();
346
348
347
349
if (this .dtoProjection ) {
348
- this .preferredConstructor = PreferredConstructorDiscoverer .discover (String . class );
350
+ this .preferredConstructor = PreferredConstructorDiscoverer .discover (type . getReturnedType () );
349
351
} else {
350
352
this .preferredConstructor = null ;
351
353
}
@@ -373,18 +375,35 @@ public Object convert(Object source) {
373
375
374
376
Object [] ctorArgs = new Object [type .getInputProperties ().size ()];
375
377
378
+ boolean containsNullValue = false ;
376
379
for (int i = 0 ; i < type .getInputProperties ().size (); i ++) {
377
- ctorArgs [i ] = tuple .get (i );
380
+ Object value = tuple .get (i );
381
+ ctorArgs [i ] = value ;
382
+ if (!containsNullValue && value == null ) {
383
+ containsNullValue = true ;
384
+ }
378
385
}
379
386
380
387
try {
381
388
382
- if (preferredConstructor .getParameterCount () == ctorArgs .length ) {
389
+ if (preferredConstructor != null && preferredConstructor .getParameterCount () == ctorArgs .length ) {
383
390
return BeanUtils .instantiateClass (preferredConstructor .getConstructor (), ctorArgs );
384
391
}
385
392
386
- return BeanUtils .instantiateClass (type .getReturnedType ()
387
- .getConstructor (Arrays .stream (ctorArgs ).map (Object ::getClass ).toArray (Class <?>[]::new )), ctorArgs );
393
+ Constructor <?> ctor = null ;
394
+
395
+ if (!containsNullValue ) { // let's seem if we have an argument type match
396
+ ctor = type .getReturnedType ()
397
+ .getConstructor (Arrays .stream (ctorArgs ).map (Object ::getClass ).toArray (Class <?>[]::new ));
398
+ }
399
+
400
+ if (ctor == null ) { // let's see if there's more general constructor we could use that accepts our args
401
+ ctor = findFirstMatchingConstructor (ctorArgs );
402
+ }
403
+
404
+ if (ctor != null ) {
405
+ return BeanUtils .instantiateClass (ctor , ctorArgs );
406
+ }
388
407
} catch (ReflectiveOperationException e ) {
389
408
ReflectionUtils .handleReflectionException (e );
390
409
}
@@ -393,6 +412,30 @@ public Object convert(Object source) {
393
412
return new TupleBackedMap (tupleWrapper .apply (tuple ));
394
413
}
395
414
415
+ @ Nullable
416
+ private Constructor <?> findFirstMatchingConstructor (Object [] ctorArgs ) {
417
+
418
+ for (Constructor <?> ctor : type .getReturnedType ().getDeclaredConstructors ()) {
419
+
420
+ if (ctor .getParameterCount () == ctorArgs .length ) {
421
+ boolean itsAMatch = true ;
422
+ for (int i = 0 ; i < ctor .getParameterCount (); i ++) {
423
+ if (ctorArgs [i ] == null ) {
424
+ continue ;
425
+ }
426
+ if (!ClassUtils .isAssignable (ctor .getParameterTypes ()[i ], ctorArgs [i ].getClass ())) {
427
+ itsAMatch = false ;
428
+ break ;
429
+ }
430
+ }
431
+ if (itsAMatch ) {
432
+ return ctor ;
433
+ }
434
+ }
435
+ }
436
+ return null ;
437
+ }
438
+
396
439
/**
397
440
* A {@link Map} implementation which delegates all calls to a {@link Tuple}. Depending on the provided
398
441
* {@link Tuple} implementation it might return the same value for various keys of which only one will appear in the
0 commit comments