前言
Mybatis的mapper方法传递参数可以通过@param,对象object,map和list等方式。本文从源码层次分析每种参数传递的流程。
一、参数解析过程源码
参数解析的过程比较复杂,本文就重点代码进行分析。
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
//通过@param和实体object传递参数都是在此处解析的,parameterMappings对应xml中的参数
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
//当boundSql含有额外参数时,mapper参数为list时
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
//传递参数使用@param时,parameterObject对应ParamMap(基于@param参数和默认key生成),使用map.get(prop.getName())取值,保证xml中变量名与@param中的名一致,或者使用arg0,arg1,param0
//传递参数使用实体object时,parameterObject对应的时实体object,使用BeanWrapper基于属性反射获取,保证xml变量名与属性名一致
MetaObject metaObject = configuration.newMetaObject(parameterObject);
//获取参数对应的值
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
除了使用list传递的参数,其余参数都是在metaObject.getValue(propertyName)方法解析的,下面是该方法的源码,其中最重要的方法是objectWrapper.get(prop),根据objectWrapper的不同字类调用不同的实现方法,其中@param和map方式使用的是MapWrapper,实体对象参数使用的是BeanWrapper,下面就两种ObjectWrapper的get方法进行分析。
public Object getValue(String name) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
return null;
} else {
return metaValue.getValue(prop.getChildren());
}
} else {
//根据不同的objectWrapper获取属性
return objectWrapper.get(prop);
}
}
二、BeanWrapper和MapWrapper的get方法
1.BeanWrapper的get方法
beanWrapper通过属性的get方法获取参数的值,需要保证xml中的属性名称与对象中的属性名称一致。
public Object get(PropertyTokenizer prop) {
if (prop.getIndex() != null) {
Object collection = resolveCollection(prop, object);
return getCollectionValue(prop, collection);
} else {
//通过反射获取,要保证xml中的变量名与对象属性名一致
return getBeanProperty(prop, object);
}
}
private Object getBeanProperty(PropertyTokenizer prop, Object object) {
try {
//获取属性的get方法
Invoker method = metaClass.getGetInvoker(prop.getName());
try {
//反射调用get方法
return method.invoke(object, NO_ARGUMENTS);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
} catch (RuntimeException e) {
throw e;
} catch (Throwable t) {
throw new ReflectionException("Could not get property '" + prop.getName() + "' from " + object.getClass() + ". Cause: " + t.toString(), t);
}
}
当传入一个对象并且不使用@param注解的时候会使用这种方法,在开发中遇到了一个问题,Caused by: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'bids' in 'class java.lang.String',在该方法中将string类型的bids传入,mybatis将参数bids作为对象中的一个属性,所以使用getter方法获取,报没有getter方法异常.
@Select("select TOP 1 * from Info with(nolock) where UserID in ${bids} and Status != 0 and Status != 6")
Info getbids(String bids);
2.MapWrapper的get方法
MapWrapper的get方法直接调用了map的get方法,需要保证xml中的变量名与map中的key一致。
public Object get(PropertyTokenizer prop) {
if (prop.getIndex() != null) {
Object collection = resolveCollection(prop, map);
return getCollectionValue(prop, collection);
} else {
//直接调用map的get方法获取
return map.get(prop.getName());
}
}
@param传递的参数也是通过MapWrapper的get方法解析,下面主要分析@Param传递参数时,parameterObject的生成过程。
三、@Param对应的ParamMap的生成过程
对应paraMap生成的过程比较复杂,本文摘取其中的两段重要代码,首先是ParamNameResolver中的names的生成过程,如果使用了@param注解,其中的key使用@param注解中的值,否则使用arg0,arg1.....作为默认的key。
public ParamNameResolver(Configuration config, Method method) {
this.useActualParamName = config.isUseActualParamName();
final Class<?>[] paramTypes = method.getParameterTypes();
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> 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;
//使用@param中的值作为map的key
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// @Param was not specified.
if (useActualParamName) {
//当@param没有使用时,使用arg0,arg1作为默认key
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);
}
接下来getNameParams方法,创建了一个新的map,将上述names中的值加入到新的map中,而且额外添加了param1,param2.....为key的键值对,所以在xml文件中也可以通过param1,param2使用参数传递的值。
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
Object value = args[names.firstKey()];
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
//获取原有names中的值
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param
//加入默认参数名param1,param2
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
四、参数为list时参数解析过程
参数为list时解析过程相对比较复杂,在上面参数解析源码中,通过boundSql.getAdditionalParameter方法获取值,该方法源码如下,其实与上面的方法一致,最终是通过MapWrapper的get方法获取值。
public Object getAdditionalParameter(String name) {
return metaParameters.getValue(name);
}
接下来主要分析additionalParameter的赋值过程,主要时通过context.bind生成相关参数
private void applyItem(DynamicContext context, Object o, int i) {
if (item != null) {
context.bind(item, o);
//绑定参数,生成参数名
context.bind(itemizeItem(item, i), o);
}
}
private static String itemizeItem(String item, int i) {
return ITEM_PREFIX + item + "_" + i; //__frch_item_0
}
public void bind(String name, Object value) {
bindings.put(name, value);
}
设置additionalParameter
context.getBindings().forEach(boundSql::setAdditionalParameter);
总结
本文对mybatis中的mapper方法的各种参数传递方法进行了原理上的分析,并且在代码书写上给出建议,新手上路,解释不当之处请多谅解。