mybatis使⽤@SelectProvider构建动态语句,多个参数(不使
⽤@param。。。
最近代码升级mybatis了的版本3.4.6,启动后发现 使⽤@SelectProvider构建多个参数(不使⽤@param注解情况下)的动态语句,在查询时会报错。
org.apache.ibatis.binding.BindingException: Parameter 'arg0' not found.
官⽅例⼦
为此特意翻阅了mybatis的官⽅说明,到下⾯的例⼦
@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> 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.
从异常可以看出在没有使⽤@pa ra m注解的情况下,传递参数需要使⽤#{a r g0}-#{a r gn}或者#
m注解的情况下,传递参数需要使⽤#{a
{pa r a m1}-#{pa r a mn}
报错原因
在项⽬启动的过程中,mybatis会查所有的@SelectProvider标签,根据对应的type属性到内部类,构建ProviderSqlSource对象,其中关键点在 providerMethodArgumentNames (⽅法参数名数组)属性的赋值。
3.4.2版本前的赋值⽅式
this.providerMethodArgumentNames = extractProviderMethodArgumentNames(m);
参数m代表当前的⽅法method,如官⽅例⼦中的 buildGetUserByName ;具体函数的实现:
private String[] extractProviderMethodArgumentNames(Method providerMethod) {
String[] argumentNames = new ParameterTypes().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 = ParameterAnnotations()[parameterIndex];
Param param = null;
for (Object annotation : annotations) {
if (annotation instanceof Param) {
param = Param.class.cast(annotation);
break;
}
}
return param;
}
根据代码可以知道是先查@Param,有的情况下则把Param标签的值作为参数名,没有则⽤"param1"~"paramn"作为参数名。providerMethodArgumentNames的属性为"param1"~"paramn";
3.4.2版本后的赋值⽅式
this.providerMethodArgumentNames = new ParamNameResolver(configuration, m).getNames();
参数configuration是mybatis的配置,参数m是method
public ParamNameResolver(Configuration config, Method method) {
final Class<?>[] paramTypes = ParameterTypes();
final Annotation[][] paramAnnotations = ParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
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) {
ParamNames(method).get(paramIndex);
}
return null;
}
由于没有使⽤@Param注解,代码会判断系统是否开启了 useActualParamName(使⽤java8的反射得到⽅法参数名),3.4.2版本后此属性默认为true,代码会执⾏ParamNames()
public class ParamNameUtil {
private static List<String> getParameterNames(Executable executable) {
final List<String> names = new ArrayList<String>();
final Parameter[] params = Parameters();
for (Parameter param : params) {
names.Name());
}
return names;
}
}
因为代理的原因,这⾥获取到的名字就是"agr0"~"agrn",⽽不是实际代码编写的参数名;providerMethodArgumentNames的属性为"agr0"~"agrn";
假如把useActualParamName设置为false,获取到的参数名则为"0"~"n";providerMethodArgumentNames的属性为"0"~"n";
项⽬启动完毕后,当执⾏代码查询时,mybatis会执⾏下列代码
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
thismand = new SqlCommand(config, mapperInterface, method);
}
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
Type resolvedReturnType = solveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
} else if (resolvedReturnType instanceof ParameterizedType) {
} else {
}
this.mapKey = getMapKey(method);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
关键在于最后的
this.paramNameResolver = new ParamNameResolver(configuration, method);
param name
这⾥的实现跟之前构建ProviderSqlSource时给providerMethodArgumentNames赋值是⼀样的,区别时这⾥获取的是mapper⾥⾯的⽅法和参数,由于mapper⽅法上有写@Param注解,这⾥就能正确获取到参数的名称,并赋值到paramNameResolver 中。
当执⾏execute()⽅法前,mybatis会把mapper⽅法的参数名数组和参数值转换扩充为map。
Object param = vertArgsToSqlCommandParam(args);
args就是参数值的数组
public Object convertArgsToSqlCommandParam(Object[] args) {
NamedParams(args);
}
/**
* <p>
* A single non-special parameter is returned without a name.<br />
* Multiple parameters are named using the naming rule.<br />
* In addition to the default names, this method also adds the generic names (param1, param2,
* ...).
* </p>
*/
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<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : Set()) {
param.Value(), Key()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!ainsValue(genericParamName)) {
param.put(genericParamName, Key()]);
}
i++;
}
return param;
}
}
得到的map为["param1":"0","name":"123","param2":"","orderByColumn":""];
最终mybatis会执⾏下⾯的代码完成mapper中⽅法和SelectProvider类⽅法的参数对应
private Object[] extractProviderMethodArguments(Map<String, Object> 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] = (argumentNames[i]);
}
}
return args;
}
其中参数params就是我们上⾯获得到的mapper的map--["param1":"0","name":"123","param2":"","orderByColumn":""];参数argumentNames则为前⾯SelectProvider中的providerMethodArgumentNames,在3.4.2版本前为"param1"~"paramn",在3.4.2
版本后为"arg0"~"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.

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。