接着我们上一次分享的mybatis源码阅读,这次我将通过阅读去了解我们在调用自己写的mapper接口的时候,mybatis具体是怎样与数据库交互的。
大家都知道我们通过@mapper注解声明了mybatis的mapper接口,然后通过调用接口方法就能查询数据库。通过之前mybatis源码阅读文章,我们知道其实我们获取到的接口实现类是动态代理MapperProxy对象,而且是通过jdk的动态代理进行实现,所以当我们调用接口的时候,方法必然会执行到MapperProxy这个类中的invoke方法,方法实现如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
//调用invocke方法去调用MapperMethod的execute方法进行sql语句执行
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
cachedInvoker方法
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
//如果缓存中不存在调用方法,则缓存该方法
return methodCache.computeIfAbsent(method, m -> {
//mapper中的接口如果是default定义的默认方法,则使用 DefaultMethodInvoker ,否则使用 PlainMethodInvoker
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
//我们一般编写的接口都不是用default关键字定义的,所以返回的是这个类
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
由上可知,执行接口的时候构造了PlainMethodInvoker类的一个对象实例,然后调用这个对象的invoke方法。最终调用到了MapperMethod类的execute方法,如下:
//TODO args就是在调用mapper接口方法时候传递进来的参数
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
//封装参数
Object param = method.convertArgsToSqlCommandParam(args);
//TODO 返回影响的行数
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
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);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
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;
}
这个方法中增删改都调用了method.convertArgsToSqlCommandParam(args)进行参数的封装,其最终实现是调用了ParamNameResolver类的getNamedParams方法解析,具体如下:
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
//TODO 不存在参数的情况
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
//TODO 一个参数的情况
return args[names.firstKey()];
} else {
//TODO 多个参数的情况
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
//TODO key: @param注解的name属性值 value: 传入参数
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
//TODO key: parm1 parm2 ... value:传入的参数值
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}
以上方法实现其实很简单,Object[] args是调用接口传递进来的参数,例如mapper.findByUsernameAndAge(“李四”,25)调用,args就是[“李四”,25] 。
而names实际上就是一个map,实在解析xml的时候封装的,以下例子解释:
接口:findByUsernameAndAge(@Param("username")String username ,@Param("age")Integer age );
names中是这样的{ {0:username},{1:age}}
接口:findByUsernameAndAge(String username ,Integer age );
names中是这样的{ {0:arg0},{1:arg1}}
经过参数的封装,然后交给sqlSession 中持有的Executor进行数据库调用处理。