系列
開(kāi)篇
- 本篇文章主要基于mybatis-3.3.0版本進(jìn)行源碼分析,目標(biāo)在于記錄mybatis在參數(shù)解析過(guò)程。
- 了解參數(shù)解析過(guò)程是為了更好的寫mybaits的xml預(yù)發(fā)以及更好的適應(yīng)mybatis的參數(shù)攔截插件。
- 文章會(huì)講解清楚兩個(gè)問(wèn)題點(diǎn),包括mybatis的執(zhí)行過(guò)程中參數(shù)的傳遞形式和對(duì)List的特殊處理。
參數(shù)解析過(guò)程
- mybatis的參數(shù)解析過(guò)程主要包含三個(gè)步驟,包括:解析方法參數(shù)的別名,構(gòu)建方法參數(shù)對(duì)象,特殊處理集合對(duì)象。
-
解析方法參數(shù)的別名:負(fù)責(zé)解析方法的參數(shù)和參數(shù)注解,構(gòu)建參數(shù)下標(biāo)及對(duì)應(yīng)的別名的映射關(guān)系。
-
構(gòu)建方法參數(shù)對(duì)象:結(jié)合參數(shù)別名映射關(guān)系,構(gòu)建參數(shù)別名和參數(shù)對(duì)象的映射關(guān)系。
-
特殊處理集合對(duì)象:針對(duì)參數(shù)對(duì)象為單一集合的場(chǎng)景人工生成list/collection的映射關(guān)系。
解析方法參數(shù)的別名
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
// mapperInterface會(huì)對(duì)應(yīng)的mapper的 interface 接口
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
public static class MethodSignature {
// 省略其他不強(qiáng)相關(guān)的代碼
private final SortedMap<Integer, String> params;
private final boolean hasNamedParameters;
// 解析方法簽名
public MethodSignature(Configuration configuration, Method method) {
// 判斷是否有@Param的注解
this.hasNamedParameters = hasNamedParams(method);
// 通過(guò)getParams來(lái)獲取對(duì)應(yīng)的參數(shù)值
this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
}
// 解析方法的參數(shù)
private SortedMap<Integer, String> getParams(Method method, boolean hasNamedParameters) {
final SortedMap<Integer, String> params = new TreeMap<Integer, String>();
// 1、獲取方法的參數(shù)類型
final Class<?>[] argTypes = method.getParameterTypes();
// 2、遍歷方法的參數(shù)類型構(gòu)建參數(shù)下標(biāo)和別名的映射關(guān)系
for (int i = 0; i < argTypes.length; i++) {
if (!RowBounds.class.isAssignableFrom(argTypes[i]) && !ResultHandler.class.isAssignableFrom(argTypes[i])) {
// 默認(rèn)以參數(shù)的下標(biāo)值作為value值
String paramName = String.valueOf(params.size());
// 如果有注解就遍歷該參數(shù)對(duì)應(yīng)的注解名字
if (hasNamedParameters) {
paramName = getParamNameFromAnnotation(method, i, paramName);
}
params.put(i, paramName);
}
}
// 返回的params以參數(shù)下標(biāo)作為key,以下標(biāo)或者別名作為value
return params;
}
// 從@Param注解中獲取對(duì)應(yīng)的別名
private String getParamNameFromAnnotation(Method method, int i, String paramName) {
// 遍歷方法的所有參數(shù)注解,getParameterAnnotations返回的是一個(gè)二維數(shù)組
// 第一維是參數(shù)的下標(biāo),第二維是改參數(shù)對(duì)應(yīng)的多個(gè)注解
final Object[] paramAnnos = method.getParameterAnnotations()[i];
// 如果存在@Param注解就獲取注解對(duì)應(yīng)的別名
for (Object paramAnno : paramAnnos) {
if (paramAnno instanceof Param) {
paramName = ((Param) paramAnno).value();
break;
}
}
return paramName;
}
}
- 通過(guò)method.getParameterTypes()獲取方法的所有參數(shù)對(duì)象。
- 遍歷方法的參數(shù),解析該參數(shù)對(duì)應(yīng)的注解@Param對(duì)應(yīng)的別名,如果沒(méi)有注解用參數(shù)下標(biāo)作為別名。
- 構(gòu)建方法參數(shù)的別名Map對(duì)象,其中key為參數(shù)順序,value為別名或參數(shù)的下標(biāo)順序。
構(gòu)建方法參數(shù)對(duì)象
public class MapperMethod {
public static class MethodSignature {
// 解析入?yún)⒎祷豰ybatis內(nèi)部處理的參數(shù)
public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
// 1、參數(shù)為空的場(chǎng)景直接返回
if (args == null || paramCount == 0) {
return null;
// 2、沒(méi)有@Param注解修飾且只有一個(gè)參數(shù)的場(chǎng)景
} else if (!hasNamedParameters && paramCount == 1) {
return args[params.keySet().iterator().next().intValue()];
// 3、其他的場(chǎng)景,包含@Param注解或參數(shù)個(gè)數(shù)大于1
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
// 遍歷所有的方法參數(shù),以別名或者下標(biāo)作為key,value未
for (Map.Entry<Integer, String> entry : params.entrySet()) {
// entry.getValue()返回的是@Param對(duì)應(yīng)的別名 或 參數(shù)的順序
param.put(entry.getValue(), args[entry.getKey().intValue()]);
// 額外添加param0、param1作為key,value為對(duì)應(yīng)的參數(shù)值
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}
- 針對(duì)不同場(chǎng)景返回不同的參數(shù)映射map,1、如果參數(shù)為空的場(chǎng)景直接返回空對(duì)象;2、如果沒(méi)有@Param注解修飾且只有一個(gè)參數(shù)的場(chǎng)景直接返回參數(shù)對(duì)象;3、其他場(chǎng)景構(gòu)建參數(shù)的Map對(duì)象進(jìn)行返回。
- 所以在mybatis的執(zhí)行過(guò)程中的參數(shù)存在三種形態(tài),分別是空對(duì)象、Bean/java數(shù)據(jù)原生對(duì)象,Map對(duì)象。
- 針對(duì)其他場(chǎng)景構(gòu)建的參數(shù),依據(jù)解析方法參數(shù)的別名過(guò)程的結(jié)果構(gòu)建參數(shù)Map對(duì)象,我們以別名或參數(shù)下標(biāo)作為key,以參數(shù)對(duì)象作為value的Map對(duì)象。因?yàn)橄蚝蠹嫒莸男枰狹ap中會(huì)保存字符串param1(參數(shù)下標(biāo)位置)作為key,參數(shù)對(duì)象作為Value。
解析方法參數(shù)的過(guò)程
public class MapperProxy<T> implements InvocationHandler, Serializable {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 針對(duì)被調(diào)用的方法 method 創(chuàng)建對(duì)應(yīng)的 MapperMethod對(duì)象。
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 通過(guò)mapperMethod執(zhí)行 execute 動(dòng)作
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
- MapperProxy的invoke的過(guò)程會(huì)針對(duì)調(diào)用的方法創(chuàng)建MapperMethod對(duì)象。
- MethodSignature的創(chuàng)建會(huì)構(gòu)建被調(diào)用的方法的簽名信息,構(gòu)建了參數(shù)的別名信息。
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
// 執(zhí)行前會(huì)進(jìn)行參數(shù)轉(zhuǎn)換
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
// 執(zhí)行前會(huì)進(jìn)行參數(shù)轉(zhuǎn)換
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
// 執(zhí)行前會(huì)進(jìn)行參數(shù)轉(zhuǎn)換
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
// 執(zhí)行前會(huì)進(jìn)行參數(shù)轉(zhuǎn)換
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
Cursor<T> result;
// 執(zhí)行前會(huì)進(jìn)行參數(shù)轉(zhuǎn)換
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<T>selectCursor(command.getName(), param, rowBounds);
} else {
result = sqlSession.<T>selectCursor(command.getName(), param);
}
return result;
}
}
- MapperMethod執(zhí)行SQL 操作的時(shí)候會(huì)通過(guò)convertArgsToSqlCommandParam解析參數(shù),后續(xù)以解析后的參數(shù)執(zhí)行 SQL 語(yǔ)句。
特殊處理集合對(duì)象
public class DefaultSqlSession implements SqlSession {
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("array", object);
return map;
}
return object;
}
}
- DefaultSqlSession#wrapCollection的過(guò)程在MethodSignature#convertArgsToSqlCommandParam之后,針對(duì)只有一個(gè)參數(shù)對(duì)象且沒(méi)有@Param標(biāo)注別名的列表對(duì)象會(huì)構(gòu)建一個(gè)Map進(jìn)行返回。
- 回憶下mybatis的xml定義中經(jīng)常會(huì)遍歷列表對(duì)象,存在<foreach collection="list">的語(yǔ)句,這里的list就是在這個(gè)過(guò)程中生成的。
參數(shù)案例
場(chǎng)景一:
List<Object> batchSelectByXxx(@Param("xxx") List<String> xxxList)
參數(shù)映射:構(gòu)建以xxx為key,xxxList為value的Map對(duì)象。
場(chǎng)景二:
int batchInsert(List<Object> xxxList)
參數(shù)映射:構(gòu)建以list/collection為key,xxxList為value的Map對(duì)象
場(chǎng)景三:
int update(Object record)
參數(shù)映射:構(gòu)建以O(shè)bject為參數(shù)對(duì)象,沒(méi)有Map。
場(chǎng)景四:
List<Object> listByParam(@Param("paramA") String strA, @Param("paramB") String strB)
參數(shù)映射:構(gòu)建以paramA為key,strA為value;以paramB為key,strB為value的Map對(duì)象。
場(chǎng)景五:
List<Object> listByParam(String strA, String strB)
參數(shù)映射,構(gòu)建以0為key,strA為value;以1為key,strB未value的Map對(duì)象。