一、思路
- 分析MapperMethod的初始化过程,以及其下的成员变量。
- 分析MapperMethod的命令的执行过程。
二、MapperMethod的初始化过程的分析
我们在Mybatis系列之MapperProxy(揭示Mapper类背后的执行逻辑)一文中已经可以了解到MapperProxy是怎么处理mapper接口的方法请求的,其关键方法正是private MapperMethod cachedMapperMethod(Method method)。在这个方法中实例化了MapperMethod对象,在MapperMethod中实现了用户的请求的处理逻辑。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
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;
}
那么我们现在就从MapperMethod的构造方法开始分析。
先观察一下MapperMethod的源代码,它的成员变量一共俩,一个是SqlCommand命令类型,还有一个是MethodSignature方法签名。
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, method);
}
// ... 省略部分代码
}
我们再看看new SqlCommand(config, mapperInterface, method);具体做了什么。
其实在new SqlCommand中只做了一件事,就是在Configuration的mappedStatements中找到对应的方法(mappedStatements是从mapper.xml中解析出来的方法,即平常我们定义的sql)。如果找到了,也就知道了这个方法对应的所要执行的sql的具体细节了。如果没有找到,那么说明对应mapper和xml的对应关系存在了问题,那么就是时候改bug咯。
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) throws BindingException {
String statementName = mapperInterface.getName() + "." + method.getName();
MappedStatement ms = null;
if (configuration.hasStatement(statementName)) {
ms = configuration.getMappedStatement(statementName);
} else if (!mapperInterface.equals(method.getDeclaringClass().getName())) { // issue #35
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
ms = configuration.getMappedStatement(parentStatementName);
}
}
if (ms == null) {
throw new BindingException("Invalid bound statement (not found): " + statementName);
}
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
讲完SqlCommand,我们来看看在this.method = new MethodSignature(config, method);中都做了啥。
其实看类型MethodSignature的类名就大致能猜测出来,它就是封装了一些method相关的信息,主要包括两方面,一个是返回类型,第二个是参数类型。
public MethodSignature(Configuration configuration, Method method) throws BindingException {
this.returnType = method.getReturnType(); // 返回值类型
this.returnsVoid = void.class.equals(this.returnType); // 返回值是否为void
this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray()); // 是否返回多个结果
this.mapKey = getMapKey(method); // method是否使用了mapKey注解
this.returnsMap = (this.mapKey != null); // 返回值是否是Map类型
this.hasNamedParameters = hasNamedParams(method); // 参数是否自定义了别名
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class); // 是否使用mybatis的物理分页
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class); // 是否有resutlHandler
this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters)); // 请求参数解析
}
三、MapperMethod的命令执行过程的分析
讲完了MapperMethod的初始化过程,我们就来看看MapperMethod是怎么执行命令的。
主要就是分析MapperMethod下的execute方法了。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
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 {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
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;
}
大家都知道DML基本上分为增删改查,我们这里增删改作为一类来分析,查单独作为一类来分析。
a. 先看看execute中的增删改,它们很相似。首先都是调用了public Object convertArgsToSqlCommandParam(Object[] args)方法解析用户传递的参数,然后就是调用SqlSession对用的增删改方法。
b. 我们再来关注查,可以看到根据返回类型的不同,会执行不同的查询操作。
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 {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
- 我们先看看返回类型是void并且有ResultHandler的方法。它是调用 executeWithResultHandler(sqlSession, args);方法来处理请求。从下面的实现代码中看到其实最终还是调用了SqlSession的包含ResultHandler的select方法。
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
// ... 省略部分内容
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) { // 分页信息
RowBounds rowBounds = method.extractRowBounds(args);
sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
} else {
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
}
}
- 当返回参数是一个List时,它其实也是调用了SqlSession的selectList方法,然后最后根据method上定义的返回类型进行数据封装。
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
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
// 对各种类型的list兼容,包括数组和Collections
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
- 当返回类型是一个Map时,也只是调用了SqlSession的selectMap方法。
private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
Map<K, V> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
} else {
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
}
return result;
}
- 最后默认情况下,调用SqlSession的selectOne方法只返回一个对象。
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 {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
四、小小的总结
MapperMethod执行命令的本质上就是三步,一是处理请求参数,二是调用SqlSession执行真正的sql,三是根据方法的返回类型封装结果。
ok,今天就先到这儿了,good night ~~~
网友评论