1. 前言
在通过代理模式获取到mapper接口的代理对象后,就可以直接使用代理对象调用mappper方法进行增删改查操作了。
接下来我们分析MyBatis整个Mapper中的SQL执行流程。
List<User> dynamicUserList = userMapper.selectDynamicUserList(userQuery);
详细的执行流程如下图所示:
2. 源码分析
2.1 进入代理对象执行方法
代理对象执行时,会进入MapperProxy类invoke方法。
/**
* 执行代理 代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
*
* @param proxy 代理对象
* @param method 执行方法
* @param args 方法参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 代理执行
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
invoke方法判断不是Object中的方法后,进入cachedInvoker(method)方法对代理方法method进行处理,返回一个MapperMethodInvoker对象(可以理解为真实的执行方法对象)。因为当前method是JDK中的类,无法进行进行数据库复杂操作,需要进行进一步处理。
/**
* @param method 方法对象
* @return MapperMethodInvoker
* @throws Throwable
*/
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return MapUtil.computeIfAbsent(methodCache, method,
// 处理方法
m -> {
// 如果是接口中的default修饰的方法
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方法返回一个PlainMethodInvoker
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
2.2 Method 转为 MapperMethod
在上一步cachedInvoker方法中返回MapperMethodInvoker 时,首先创建了一个MapperMethod对象。MapperMethod是对Mapper接口中的方法结合当前sqlSession中的Configuration对象,进一步处理为Mybatis中能执行操作的方法对象。
// 非default方法返回一个PlainMethodInvoker
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
MapperMethod的有参构造,创建了SqlCommand和MethodSignature两个对象并赋值给MapperMethod的成员变量。
/***
* 构造方法
* @param mapperInterface mapper接口
* @param method 执行方法对象
* @param config Configuration对象
*/
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
2.3 创建 SqlCommand 对象
SqlCommand对象有两个重要的属性name和type,name存放MappedStatement的ID,type存放当前的增删改查类型。
public static class SqlCommand {
private final String name; // org.pearl.mybatis.demo.dao.UserMapper.selectDynamicUserList
private final SqlCommandType type; // UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
/**
* SqlCommand 对象
*/
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
// 方法名 selectDynamicUserList
final String methodName = method.getName();
// 接口 interface org.pearl.mybatis.demo.dao.UserMapper
final Class<?> declaringClass = method.getDeclaringClass();
// 获取MappedStatement
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
// MappedStatement为null
if (ms == null) {
// 判断方法上是否有Flush注解
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
// 设置当前SqlCommand 的name 和 type属性
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
2.4 获取 MappedStatement
通过之前构建SqlSessionFactory源码分析中,我们了解到每个方法对应的MappedStatement都存放到了Configuration对象中名为mappedStatements的Map中,每个键值对存放了当前方法的ID及方法其他属性。
SqlCommand对象中的name就对应MappedStatement的ID:
MapperMethod类中的resolveMappedStatement会从mappedStatements中获取改执行方法对应的MappedStatement。
/**
* 获取MappedStatement
*/
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
// ID org.pearl.mybatis.demo.dao.UserMapper.selectDynamicUserList
String statementId = mapperInterface.getName() + "." + methodName;
// 判断configuration 是否有该MappedStatement
if (configuration.hasStatement(statementId)) {
// 有直接获取并返回
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
// 没有MappedStatement 递归创建
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
MapperMethod的构造方法中,通过2.3、2.4两个步骤,就创建了SqlCommand对象,此对象主要记录了执行方法的MappedStatement的ID及操作类型。
2.5 创建 MethodSignature对 象
MapperMethod的构造方法,接下来会创建MethodSignature对象。
MethodSignature方法签名类,主要提供存放当前方法的返回值类型,处理参数等功能。
/**
* 方法签名,静态内部类
*/
public static class MethodSignature {
// 是否多值查询
private final boolean returnsMany;
// 是否map查询
private final boolean returnsMap;
// 是否void查询
private final boolean returnsVoid;
// 是否游标查询
private final boolean returnsCursor;
// 是否Optional
private final boolean returnsOptional;
// 返回类型
private final Class<?> returnType;
// 获取mapKey的值
private final String mapKey;
// ResultHandler类型在method参数的序号
private final Integer resultHandlerIndex;
// 分页参数在method参数的序号
private final Integer rowBoundsIndex;
// 参数名称解析器
private final ParamNameResolver paramNameResolver;
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
// 解析返回的类型 interface java.util.List
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
// 设置返回类型到 方法签名中
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
// 设置方法签名相关属性
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.returnsOptional = Optional.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
// Mybatis默认的分页是通过RowBounds参数来实现的,并且是在内存里面进行的
// 如果方法参数中包含RowBounds类型或其子类型的参数,找出这个参数在方法参数列表中的下标值
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
// 如果方法参数中包含ResultHandler类型或其子类型的参数,找出这个参数在方法参数列表中的下标值
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
// 参数名称解析器
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
2.6 MethodSignature 参数名称解析器
在MethodSignature创建了一个ParamNameResolver参数名解析器。主要用来处理接口形式的参数,最后会把参数处放在一个map中。
在配置文件中有一个useActualParamName配置,参数名称解析器有用到,这个配置的意思是是否使用真实的参数名,比如(User user), 就会获取到user这个名称。但是javac编译后后,会优化,再通过反射获取,其名称就不是user了。
<setting name="useActualParamName" value="true" />
可以看到实际运行时,获取到的参数名是arg0:
ParamNameResolver成员属性:
// Param 注解前缀
public static final String GENERIC_NAME_PREFIX = "param";
// 是否使用实际传入的参数名
private final boolean useActualParamName;
// 存放参数的位置和对应的参数名
private final SortedMap<Integer, String> names;
// 是否使用@param注解
private boolean hasParamAnnotation;
ParamNameResolver构造方法解析参数逻辑:
/**
* 参数名解析器
* 主要用来处理接口形式的参数,最后会把参数处放在一个map中
*
* @param config Configuration
* @param method Method
*/
public ParamNameResolver(Configuration config, Method method) {
// 获取Configuration中的useActualParamName属性 true
this.useActualParamName = config.isUseActualParamName();
// 参数的类型数组
final Class<?>[] paramTypes = method.getParameterTypes();
// 方法注解的二维数组,每一个方法的参数包含一个注解数组。
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
// 创建一个有序的Map
final SortedMap<Integer, String> map = new TreeMap<>();
// 方法注解的集合的长度
int paramCount = paramAnnotations.length;
// get names from @Param annotations
// 循环注解,没有注解时,paramCount为1
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
// 如果为特殊参数(RowBounds(分页对象) 和 ResultHandler(结果处理)),则不会记入mapper的实际参数
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
//判断参数是否由@Param注解修饰,如果有Param注解 hasParamAnnotation = true,参数的名称为Param定义的值
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
// 没有Param注解
if (name == null) {
// @Param was not specified.
//如果配置了useActualParamName=true的话,则取实际参数的名称 arg0
if (useActualParamName) {
//
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
//否则,该参数的名称为0,1....n
name = String.valueOf(map.size());
}
}
// 将参数序列号 参数名放入有序Map中 { 0 ,arg0}
map.put(paramIndex, name);
}
// 将Map 赋值给names
names = Collections.unmodifiableSortedMap(map);
}
构建完参数解析器后,MethodSignature方法签名对象就构建完成了:
2.7 构建 PlainMethodInvoker 对象
SqlCommand及MethodSignature对象构建完成后,MapperMethod对象也就完成了初始化,第一步中的cachedInvoker(method),就只有就有一步了,构建PlainMethodInvoker对象。
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
通过对mapper方法的重重处理,最后获取PlainMethodInvoker对象。构造方法很简单,就是把处理后的MapperMethod方赋值给成员属性。
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
2.8 第二个 invoke
PlainMethodInvoker创建成功后,拥有了MapperMethod对象,最终回到第一步代码中,调用PlainMethodInvoker的 invoke方法,开始执行数据库操作。
// 代理执行
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);