mybatisは@SelectProviderを使用して動的文を構築し、複数のパラメータ(@param注釈を使用しない場合)、3.4.2バージョン以降BindingException:Parameter'arg 0'
10176 ワード
最近コードがmybatisをアップグレードしたバージョン3.4.6,@SelectProviderを使用して複数のパラメータ(@param注記を使用しない場合)を構築する動的文が起動後に見つかり,クエリ時にエラーが報告される.
公式の例
そのためにわざわざmybatisの公式説明をめくって、次の例を見つけました.
テストの結果、公式コードに記述された最初の書き方(daoレイヤのbuildGetUserByNameメソッドは各パラメータの前に@Param注記を付け、内部クラスに対応するbuildGetUserByNameメソッドはパラメータ名、数をdaoレイヤの@Param注記と一致させるだけ)を付け、3.4.2バージョンまでは正常に使用できたが、3.4.2バージョンになるとエラーが報告される.
異常から,@param注釈を用いない場合,伝達パラメータには#{arg 0}-#{argn}または#{param 1}-#{paramn}を用いる必要があることがわかる.
エラー原因
プロジェクトが開始されると、mybatisはすべての@SelectProviderラベルを検索し、対応するtype属性に基づいて内部クラスを見つけ、providerSqlSourceオブジェクトを構築します.ここで、キーはproviderMethodArgumentName(メソッドパラメータ名配列)属性に割り当てられます.
3.4.2リリース前の割り当て方法
パラメータmは、公式の例のbuildGetUserByNameのような現在のメソッドmethodを表す.具体的な関数の実装:
コードから@Paramを先に検索していることが分かるが,場合によってはParamタグの値をパラメータ名とし,ない場合は「param 1」~「paramn」をパラメータ名として用いる.providerMethodArgumentNamesの属性は「param 1」~「paramn」である.
3.4.2リリース後の割り当て方法
パラメータconfigurationはmybatisの構成であり、パラメータmはmethodである
@Param注記を使用していないため、コードはシステムがuseActualParamName(java 8の反射を使用してメソッドパラメータ名を得る)を開いたかどうかを判断し、3.4.バージョン2以降のこの属性はデフォルトでtrueで、コードはParamName Utilを実行します.getParamNames()
エージェントのため、ここで取得した名前は「agr 0」~「agrn」であり、実際のコードで記述されたパラメータ名ではない.providerMethodArgumentNameの属性は「agr 0」~「agrn」である.
useActualParamNameをfalseに設定すると、取得したパラメータ名は「0」~「n」となります.providerMethodArgumentNameの属性は「0」~「n」である.
プロジェクトの開始後、コードクエリーを実行するとmybatisは次のコードを実行します.
肝心なのは最後の
ここでの実装は,以前にProviderSqlSourceを構築したときにproviderMethodArgumentNamesに与えられた値と同じであり,区別するときにここで得られたのはmapper内のメソッドとパラメータであり,mapperメソッドに@Param注釈が書かれているため,ここでパラメータの名前を正しく取得し,paramNameResolverに値を与えることができる.
execute()メソッドを実行する前にmybatisはmapperメソッドのパラメータ名配列とパラメータ値変換をmapに拡張します.
argsはパラメータ値の配列です
method.converArgsToSqlCommandParam()メソッドは、次の論理を実行します.
最終的にmybatisは次のコードを実行してmapperのメソッドとSelectProviderクラスのメソッドのパラメータの対応を完了します.
ここでパラメータparamsは,我々が得たmapperのmap−[「param 1」:「0」,「name」:「123」,「param 2」:「」,「orderByColumn」:「」];パラメータargumentNamesは、前のSelectProviderのproviderMethodArgumentNamesで3.4.2バージョン前は「param 1」~「paramn」で3.4.2バージョン以降は「arg 0」~「argn」または「0」~「1」である.だからプログラムは3.4です.2バージョン前は3.4で正常に走ることができます.2リリース後のレポート
解決策
解決策は簡単で,SelectProviderメソッドのパラメータ名対応にmapperメソッドと同様の@Param注釈を加える.
本文は等風de帆-29、ParamNameResolverパラメータ解析と彼岸の饅頭のmybatis伝の複数のパラメータ(@param注釈を使用しない場合)を参考し、3.4.バージョン2以降で#{0}-#{n}を使用して発生したパラメータバインド異常とsettingsプロパティのuseActualParamNameの役割.
org.apache.ibatis.binding.BindingException: Parameter 'arg0' not found.
公式の例
そのためにわざわざmybatisの公式説明をめくって、次の例を見つけました.
@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List getUsersByName(
@Param("name") String name, @Param("orderByColumn") String orderByColumn);
class UserSqlBuilder {
// If not use @Param, you should be define same arguments with mapper method
public static String buildGetUsersByName(
final String name, final String orderByColumn) {
return new SQL(){{
SELECT("*");
FROM("users");
WHERE("name like #{name} || '%'");
ORDER_BY(orderByColumn);
}}.toString();
}
// If use @Param, you can define only arguments to be used
public static String buildGetUsersByName(@Param("orderByColumn") final String orderByColumn) {
return new SQL(){{
SELECT("*");
FROM("users");
WHERE("name like #{name} || '%'");
ORDER_BY(orderByColumn);
}}.toString();
}
}
テストの結果、公式コードに記述された最初の書き方(daoレイヤのbuildGetUserByNameメソッドは各パラメータの前に@Param注記を付け、内部クラスに対応するbuildGetUserByNameメソッドはパラメータ名、数をdaoレイヤの@Param注記と一致させるだけ)を付け、3.4.2バージョンまでは正常に使用できたが、3.4.2バージョンになるとエラーが報告される.
org.apache.ibatis.binding.BindingException: Parameter 'arg0' not found.
異常から,@param注釈を用いない場合,伝達パラメータには#{arg 0}-#{argn}または#{param 1}-#{paramn}を用いる必要があることがわかる.
エラー原因
プロジェクトが開始されると、mybatisはすべての@SelectProviderラベルを検索し、対応するtype属性に基づいて内部クラスを見つけ、providerSqlSourceオブジェクトを構築します.ここで、キーはproviderMethodArgumentName(メソッドパラメータ名配列)属性に割り当てられます.
3.4.2リリース前の割り当て方法
this.providerMethodArgumentNames = extractProviderMethodArgumentNames(m);
パラメータmは、公式の例のbuildGetUserByNameのような現在のメソッドmethodを表す.具体的な関数の実装:
private String[] extractProviderMethodArgumentNames(Method providerMethod) {
String[] argumentNames = new String[providerMethod.getParameterTypes().length];
for (int i = 0; i < argumentNames.length; i++) {
Param param = findParamAnnotation(providerMethod, i);
argumentNames[i] = param != null ? param.value() : "param" + (i + 1);
}
return argumentNames;
}
private Param findParamAnnotation(Method providerMethod, int parameterIndex) {
final Object[] annotations = providerMethod.getParameterAnnotations()[parameterIndex];
Param param = null;
for (Object annotation : annotations) {
if (annotation instanceof Param) {
param = Param.class.cast(annotation);
break;
}
}
return param;
}
コードから@Paramを先に検索していることが分かるが,場合によってはParamタグの値をパラメータ名とし,ない場合は「param 1」~「paramn」をパラメータ名として用いる.providerMethodArgumentNamesの属性は「param 1」~「paramn」である.
3.4.2リリース後の割り当て方法
this.providerMethodArgumentNames = new ParamNameResolver(configuration, m).getNames();
パラメータconfigurationはmybatisの構成であり、パラメータmはmethodである
public ParamNameResolver(Configuration config, Method method) {
final Class>[] paramTypes = method.getParameterTypes();
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap map = new TreeMap();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// @Param was not specified.
if (config.isUseActualParamName()) {
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
private String getActualParamName(Method method, int paramIndex) {
if (Jdk.parameterExists) {
return ParamNameUtil.getParamNames(method).get(paramIndex);
}
return null;
}
@Param注記を使用していないため、コードはシステムがuseActualParamName(java 8の反射を使用してメソッドパラメータ名を得る)を開いたかどうかを判断し、3.4.バージョン2以降のこの属性はデフォルトでtrueで、コードはParamName Utilを実行します.getParamNames()
public class ParamNameUtil {
private static List getParameterNames(Executable executable) {
final List names = new ArrayList();
final Parameter[] params = executable.getParameters();
for (Parameter param : params) {
names.add(param.getName());
}
return names;
}
}
エージェントのため、ここで取得した名前は「agr 0」~「agrn」であり、実際のコードで記述されたパラメータ名ではない.providerMethodArgumentNameの属性は「agr 0」~「agrn」である.
useActualParamNameをfalseに設定すると、取得したパラメータ名は「0」~「n」となります.providerMethodArgumentNameの属性は「0」~「n」である.
プロジェクトの開始後、コードクエリーを実行するとmybatisは次のコードを実行します.
public MapperMethod(Class> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
public MethodSignature(Configuration configuration, Class> mapperInterface, Method method) {
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class>) {
this.returnType = (Class>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
肝心なのは最後の
this.paramNameResolver = new ParamNameResolver(configuration, method);
ここでの実装は,以前にProviderSqlSourceを構築したときにproviderMethodArgumentNamesに与えられた値と同じであり,区別するときにここで得られたのはmapper内のメソッドとパラメータであり,mapperメソッドに@Param注釈が書かれているため,ここでパラメータの名前を正しく取得し,paramNameResolverに値を与えることができる.
execute()メソッドを実行する前にmybatisはmapperメソッドのパラメータ名配列とパラメータ値変換をmapに拡張します.
Object param = method.convertArgsToSqlCommandParam(args);
argsはパラメータ値の配列です
method.converArgsToSqlCommandParam()メソッドは、次の論理を実行します.
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
/**
*
* A single non-special parameter is returned without a name.
* Multiple parameters are named using the naming rule.
* In addition to the default names, this method also adds the generic names (param1, param2,
* ...).
*
*/
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
} else {
final Map param = new ParamMap
で得られたmapは["param 1":"0","name":"123","param 2":","orderByColumn":""];最終的にmybatisは次のコードを実行してmapperのメソッドとSelectProviderクラスのメソッドのパラメータの対応を完了します.
private Object[] extractProviderMethodArguments(Map params, String[] argumentNames) {
Object[] args = new Object[argumentNames.length];
for (int i = 0; i < args.length; i++) {
if (providerContextIndex != null && providerContextIndex == i) {
args[i] = providerContext;
} else {
args[i] = params.get(argumentNames[i]);
}
}
return args;
}
ここでパラメータparamsは,我々が得たmapperのmap−[「param 1」:「0」,「name」:「123」,「param 2」:「」,「orderByColumn」:「」];パラメータargumentNamesは、前のSelectProviderのproviderMethodArgumentNamesで3.4.2バージョン前は「param 1」~「paramn」で3.4.2バージョン以降は「arg 0」~「argn」または「0」~「1」である.だからプログラムは3.4です.2バージョン前は3.4で正常に走ることができます.2リリース後のレポート
org.apache.ibatis.binding.BindingException: Parameter 'arg0' not found.
org.apache.ibatis.binding.BindingException: Parameter '0' not found.
解決策
解決策は簡単で,SelectProviderメソッドのパラメータ名対応にmapperメソッドと同様の@Param注釈を加える.
本文は等風de帆-29、ParamNameResolverパラメータ解析と彼岸の饅頭のmybatis伝の複数のパラメータ(@param注釈を使用しない場合)を参考し、3.4.バージョン2以降で#{0}-#{n}を使用して発生したパラメータバインド異常とsettingsプロパティのuseActualParamNameの役割.