1
1
/**
2
- * Copyright 2009-2020 the original author or authors.
2
+ * Copyright 2009-2021 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
16
16
package org .apache .ibatis .executor .resultset ;
17
17
18
18
import java .lang .reflect .Constructor ;
19
+ import java .lang .reflect .Parameter ;
19
20
import java .sql .CallableStatement ;
20
21
import java .sql .ResultSet ;
21
22
import java .sql .SQLException ;
22
23
import java .sql .Statement ;
24
+ import java .text .MessageFormat ;
23
25
import java .util .ArrayList ;
26
+ import java .util .Arrays ;
24
27
import java .util .HashMap ;
25
28
import java .util .HashSet ;
26
29
import java .util .List ;
27
30
import java .util .Locale ;
28
31
import java .util .Map ;
32
+ import java .util .Optional ;
29
33
import java .util .Set ;
30
34
31
35
import org .apache .ibatis .annotations .AutomapConstructor ;
36
+ import org .apache .ibatis .annotations .Param ;
32
37
import org .apache .ibatis .binding .MapperMethod .ParamMap ;
33
38
import org .apache .ibatis .cache .CacheKey ;
34
39
import org .apache .ibatis .cursor .Cursor ;
@@ -686,49 +691,35 @@ Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType
686
691
return foundValues ? objectFactory .create (resultType , constructorArgTypes , constructorArgs ) : null ;
687
692
}
688
693
689
- private Object createByConstructorSignature (ResultSetWrapper rsw , Class <?> resultType , List <Class <?>> constructorArgTypes , List <Object > constructorArgs ) throws SQLException {
690
- final Constructor <?>[] constructors = resultType .getDeclaredConstructors ();
691
- final Constructor <?> defaultConstructor = findDefaultConstructor (constructors );
692
- if (defaultConstructor != null ) {
693
- return createUsingConstructor (rsw , resultType , constructorArgTypes , constructorArgs , defaultConstructor );
694
- } else {
695
- for (Constructor <?> constructor : constructors ) {
696
- if (allowedConstructorUsingTypeHandlers (constructor , rsw .getJdbcTypes ())) {
697
- return createUsingConstructor (rsw , resultType , constructorArgTypes , constructorArgs , constructor );
698
- }
699
- }
700
- }
701
- throw new ExecutorException ("No constructor found in " + resultType .getName () + " matching " + rsw .getClassNames ());
694
+ private Object createByConstructorSignature (ResultSetWrapper rsw , Class <?> resultType ,
695
+ List <Class <?>> constructorArgTypes , List <Object > constructorArgs ) throws SQLException {
696
+ return applyConstructorAutomapping (rsw , resultType , constructorArgTypes , constructorArgs ,
697
+ findConstructorForAutomapping (resultType , rsw ).orElseThrow (() -> new ExecutorException (
698
+ "No constructor found in " + resultType .getName () + " matching " + rsw .getClassNames ())));
702
699
}
703
700
704
- private Object createUsingConstructor (ResultSetWrapper rsw , Class <?> resultType , List <Class <?>> constructorArgTypes , List <Object > constructorArgs , Constructor <?> constructor ) throws SQLException {
705
- boolean foundValues = false ;
706
- for (int i = 0 ; i < constructor .getParameterTypes ().length ; i ++) {
707
- Class <?> parameterType = constructor .getParameterTypes ()[i ];
708
- String columnName = rsw .getColumnNames ().get (i );
709
- TypeHandler <?> typeHandler = rsw .getTypeHandler (parameterType , columnName );
710
- Object value = typeHandler .getResult (rsw .getResultSet (), columnName );
711
- constructorArgTypes .add (parameterType );
712
- constructorArgs .add (value );
713
- foundValues = value != null || foundValues ;
714
- }
715
- return foundValues ? objectFactory .create (resultType , constructorArgTypes , constructorArgs ) : null ;
716
- }
717
-
718
- private Constructor <?> findDefaultConstructor (final Constructor <?>[] constructors ) {
701
+ private Optional <Constructor <?>> findConstructorForAutomapping (final Class <?> resultType , ResultSetWrapper rsw ) {
702
+ Constructor <?>[] constructors = resultType .getDeclaredConstructors ();
719
703
if (constructors .length == 1 ) {
720
- return constructors [0 ];
704
+ return Optional . of ( constructors [0 ]) ;
721
705
}
722
-
723
706
for (final Constructor <?> constructor : constructors ) {
724
707
if (constructor .isAnnotationPresent (AutomapConstructor .class )) {
725
- return constructor ;
708
+ return Optional . of ( constructor ) ;
726
709
}
727
710
}
728
- return null ;
711
+ if (configuration .isArgNameBasedConstructorAutoMapping ()) {
712
+ // Finding-best-match type implementation is possible,
713
+ // but using @AutomapConstructor seems sufficient.
714
+ throw new ExecutorException (MessageFormat .format (
715
+ "'argNameBasedConstructorAutoMapping' is enabled and the class ''{0}'' has multiple constructors, so @AutomapConstructor must be added to one of the constructors." ,
716
+ resultType .getName ()));
717
+ } else {
718
+ return Arrays .stream (constructors ).filter (x -> findUsableConstructorByArgTypes (x , rsw .getJdbcTypes ())).findAny ();
719
+ }
729
720
}
730
721
731
- private boolean allowedConstructorUsingTypeHandlers (final Constructor <?> constructor , final List <JdbcType > jdbcTypes ) {
722
+ private boolean findUsableConstructorByArgTypes (final Constructor <?> constructor , final List <JdbcType > jdbcTypes ) {
732
723
final Class <?>[] parameterTypes = constructor .getParameterTypes ();
733
724
if (parameterTypes .length != jdbcTypes .size ()) {
734
725
return false ;
@@ -741,6 +732,69 @@ private boolean allowedConstructorUsingTypeHandlers(final Constructor<?> constru
741
732
return true ;
742
733
}
743
734
735
+ private Object applyConstructorAutomapping (ResultSetWrapper rsw , Class <?> resultType , List <Class <?>> constructorArgTypes , List <Object > constructorArgs , Constructor <?> constructor ) throws SQLException {
736
+ boolean foundValues = false ;
737
+ if (configuration .isArgNameBasedConstructorAutoMapping ()) {
738
+ foundValues = applyArgNameBasedConstructorAutoMapping (rsw , resultType , constructorArgTypes , constructorArgs ,
739
+ constructor , foundValues );
740
+ } else {
741
+ foundValues = applyColumnOrderBasedConstructorAutomapping (rsw , constructorArgTypes , constructorArgs , constructor ,
742
+ foundValues );
743
+ }
744
+ return foundValues ? objectFactory .create (resultType , constructorArgTypes , constructorArgs ) : null ;
745
+ }
746
+
747
+ private boolean applyColumnOrderBasedConstructorAutomapping (ResultSetWrapper rsw , List <Class <?>> constructorArgTypes ,
748
+ List <Object > constructorArgs , Constructor <?> constructor , boolean foundValues ) throws SQLException {
749
+ for (int i = 0 ; i < constructor .getParameterTypes ().length ; i ++) {
750
+ Class <?> parameterType = constructor .getParameterTypes ()[i ];
751
+ String columnName = rsw .getColumnNames ().get (i );
752
+ TypeHandler <?> typeHandler = rsw .getTypeHandler (parameterType , columnName );
753
+ Object value = typeHandler .getResult (rsw .getResultSet (), columnName );
754
+ constructorArgTypes .add (parameterType );
755
+ constructorArgs .add (value );
756
+ foundValues = value != null || foundValues ;
757
+ }
758
+ return foundValues ;
759
+ }
760
+
761
+ private boolean applyArgNameBasedConstructorAutoMapping (ResultSetWrapper rsw , Class <?> resultType ,
762
+ List <Class <?>> constructorArgTypes , List <Object > constructorArgs , Constructor <?> constructor , boolean foundValues )
763
+ throws SQLException {
764
+ List <String > missingArgs = null ;
765
+ Parameter [] params = constructor .getParameters ();
766
+ for (Parameter param : params ) {
767
+ boolean columnNotFound = true ;
768
+ Param paramAnno = param .getAnnotation (Param .class );
769
+ String paramName = paramAnno == null ? param .getName () : paramAnno .value ();
770
+ for (String columnName : rsw .getColumnNames ()) {
771
+ if (paramName .equalsIgnoreCase (
772
+ configuration .isMapUnderscoreToCamelCase () ? columnName .replace ("_" , "" ) : columnName )) {
773
+ Class <?> paramType = param .getType ();
774
+ TypeHandler <?> typeHandler = rsw .getTypeHandler (paramType , columnName );
775
+ Object value = typeHandler .getResult (rsw .getResultSet (), columnName );
776
+ constructorArgTypes .add (paramType );
777
+ constructorArgs .add (value );
778
+ columnNotFound = false ;
779
+ foundValues = value != null || foundValues ;
780
+ }
781
+ }
782
+ if (columnNotFound ) {
783
+ if (missingArgs == null ) {
784
+ missingArgs = new ArrayList <>();
785
+ }
786
+ missingArgs .add (paramName );
787
+ }
788
+ }
789
+ if (foundValues && constructorArgs .size () < params .length ) {
790
+ throw new ExecutorException (MessageFormat .format ("Constructor auto-mapping of ''{1}'' failed "
791
+ + "because ''{0}'' were not found in the result set; "
792
+ + "Available columns are ''{2}'' and mapUnderscoreToCamelCase is ''{3}''." ,
793
+ missingArgs , constructor , rsw .getColumnNames (), configuration .isMapUnderscoreToCamelCase ()));
794
+ }
795
+ return foundValues ;
796
+ }
797
+
744
798
private Object createPrimitiveResultObject (ResultSetWrapper rsw , ResultMap resultMap , String columnPrefix ) throws SQLException {
745
799
final Class <?> resultType = resultMap .getType ();
746
800
final String columnName ;
0 commit comments