Mybatis源码(七):SQL执行流程

  SQL执行通过接口代理对象调用接口方法完成的。在Mybatis中所有Mapper接口代理对象都是JDK动态代理生成的,最终都会调用MapperProxy中的invoke方法。Mapper接口代理对象执行接口方法,MapperProxy#invoke() 核心代码:

 1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 2   try {
 3     // 如果目标方法为Object中的方法(toString hashCode equals getClass...),则直接调用目标方法
 4     if (Object.class.equals(method.getDeclaringClass())) {
 5       return method.invoke(this, args);
 6     // 接口中默认实现的方法
 7     } else if (isDefaultMethod(method)) {
 8       return invokeDefaultMethod(proxy, method, args);
 9     }
10   } catch (Throwable t) {
11     throw ExceptionUtil.unwrapThrowable(t);
12   }
13   // 普通方法的处理
14   // 获取缓存中的MapperMethod -> 获取映射方法详情:1、目标方法全限定名;2、目标方法sql类型;3、目标方法的参数及返回值处理
15   final MapperMethod mapperMethod = cachedMapperMethod(method);
16   // 根据sql命令类型,执行sql
17   return mapperMethod.execute(sqlSession, args);
18 }

  若要执行的方法为Object中的方法(toString hashCode equals getClass...), 直接执行目标方法;若要执行的方法为接口实现的方法,通过反射获取对象实例,绑定代理对象,执行目标方法。Mybatis中Mapper接口定义的预数据库相关的方法都是普通方法,无法满足上述条件,我们接着往下看普通方法的处理:

1、MapperProxy#cachedMapperMethod()

  获取映射方法,MapperProxy#cachedMapperMethod() 核心代码:
1 // 用于缓存MapperMethod对象,key是mapper接口中方法对应的Method对象,value是对应的MapperMethod对象,MapperMethod对象会完成参数转换
2 // 以及SQL语句的执行功能,MapperMethod中未记录状态相关的信息,可以在多个代理对象之间共享
3 private final Map<Method, MapperMethod> methodCache;
4 
5 // 获取MapperMethod对象
6 private MapperMethod cachedMapperMethod(Method method) {
7   return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
8 }

  MapperMethod中有两个关键属性,SqlCommand和MethodSignature。SqlCommand存储SQL语句相关信息;MethodSignature存储Mapper接口中方法相关信息。MapperMethod的构造函数详情:

 1 // Mapper接口方法
 2 public class MapperMethod {
 3 
 4   // 记录了SQL语句的名称和类型
 5   private final SqlCommand command;
 6   // mapper接口中对应方法的相关信息
 7   private final MethodSignature method;
 8 
 9   public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
10     this.command = new SqlCommand(config, mapperInterface, method); // 获取唯一标识(类限定名+方法名称)、获取sql命令类型
11     this.method = new MethodSignature(config, mapperInterface, method);
12   }
13   // ...
14 }

1、SqlCommand

  SqlCommand为MapperMethod的静态内部类,主要包含了目标执行方法的全限定名、目标执行方法对应sql的类型(增/删/改/查),持有两个关键属性,name和type。name为 全限定接口 + 方法名;type为 当前SQL语句类型,用于辨别增删改查语句。

// 方法的全限定名 -> 全限定类名 + 方法名
private final String name;
// SQL语句类型 insert|update|delete|select
private final SqlCommandType type;

  下面来看看SqlCommand的构造函数,这两个属性是在哪获取的,构造函数详情:

 1 public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
 2   // 获取执行方法名称
 3   final String methodName = method.getName(); 
 4   // 获取接口的Class
 5   final Class<?> declaringClass = method.getDeclaringClass();
 6   // 获取MappedStatement对象,包含xml配置文件、全限定名+方法名、返回结果类型、sqlCommand
 7   MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
 8       configuration);
 9   // ...
10   // 初始化name和type
11   name = ms.getId();
12   // sql命令类型, SqlCommandType
13   type = ms.getSqlCommandType(); 
14   if (type == SqlCommandType.UNKNOWN) {
15     throw new BindingException("Unknown execution method for: " + name);
16   }
17   // ...
18 }

  解析接口、方法名称获取MappedStatement,MapperMethod#resolveMappedStatement() 核心伪代码:

 1 // 解析接口信息获取MappedStatement对象
 2 private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
 3       Class<?> declaringClass, Configuration configuration) {
 4     // sql标识id, SQL语句的名称是由Mapper接口的全限定名 + 目标方法名称
 5     String statementId = mapperInterface.getName() + "." + methodName;
 6     // 全局配置类是否包含该sql标识di
 7     if (configuration.hasStatement(statementId)) {
 8       // 从configuration.mappedStatements集合中查找对应的MappedStatement对象,MappedStatement对象中封装了SQL语句相关的信息,在mybatis初始化时创建
 9       return configuration.getMappedStatement(statementId);
10     } 
11     // ...
12 }

  组装sql标识id,sql标识id为 Mapper接口的全限定名 + 目标方法名称,判断此sql标识id在全局配置对象configuration中mappedStatements属性中是否存在,存在则从configuration中获取在解析阶段创建好的mappedStatement对象。sqlCommand中的name对应mappedStatement对象的id,sqlCommand中的type对应mappedStatement中的getSqlCommandType。

  有关sql标识id与mappedStatement的映射关系添加至mappedStatements属性,详见:Mybatis 源码(四):Mapper的解析工作

  sqlCommand中包含了目标执行方法的全限定名、目标执行方法对应的SQL类型。详情如下:

 

2、MethodSignature

  MethodSignature为MapperMethod的静态内部类,主要包含了方法返回值信息、参数名称解析器。构造函数详情如下:

 1 // 返回值类型是否为Collection类型或者数组类型
 2 private final boolean returnsMany;
 3 // 返回值类型是否为map类型
 4 private final boolean returnsMap;
 5 // 返回值类型是否为void
 6 private final boolean returnsVoid;
 7 // 返回值是否为Cursor类型
 8 private final boolean returnsCursor;
 9 private final boolean returnsOptional;
10 // 返回值类型
11 private final Class<?> returnType;
12 // 如果返回值类型是map,则该字段记录了作为key的列名
13 private final String mapKey;
14 // 用来标记该方法参数列表中ResultHandler类型参数的位置
15 private final Integer resultHandlerIndex;
16 // 用来标记该方法参数列表中RowBounds类型参数的位置
17 private final Integer rowBoundsIndex;
18 // 该方法对应的ParamNameResolver对象
19 private final ParamNameResolver paramNameResolver;
20 
21 // 构造函数
22 public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
23   // 解析方法的返回值类型
24   Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface); 
25   if (resolvedReturnType instanceof Class<?>) {
26     this.returnType = (Class<?>) resolvedReturnType;
27   } else if (resolvedReturnType instanceof ParameterizedType) {
28     this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
29   } else {
30     this.returnType = method.getReturnType();
31   }
32   // 初始化returnsVoid、returnsMany、returnsCursor、returnsOptional等参数
33   this.returnsVoid = void.class.equals(this.returnType);
34   this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
35   this.returnsCursor = Cursor.class.equals(this.returnType);
36   this.returnsOptional = Optional.class.equals(this.returnType);
37   // 若MethodSignature对应方法的返回值是Map且制定了@MapKey注解,则使用getMapKey方法处理
38   this.mapKey = getMapKey(method);
39   this.returnsMap = this.mapKey != null;
40   // 初始化rowBoundsIndex、resultHandlerIndex字段
41   this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
42   this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
43   // 创建ParamNameResolver对象
44   this.paramNameResolver = new ParamNameResolver(configuration, method);
45 }

  MethodSignature包含了方法返回值信息:返回值类型、返回值是否为void标识、返回结果是否为集合标识、返回结果是否为游标标识等;

  参数解析器ParamNameResolver对象的初始化。

3、总结

  sqlCommand主要用于锁定目标执行方法及目标执行方法要做的数据操作(增/删/改/查);MethodSignature主要用于对方法的参数、返回值的信息处理。用创建好的sqlCommand、MethodSignature创建MapperMethod对象。

2、MapperMethod#execute()

  根据不同的SQL类型执行不同的sqlSession方法,MapperMethod#execute() 核心代码:

 1 public Object execute(SqlSession sqlSession, Object[] args) {
 2   Object result;
 3   // 根据SQL语句的类型调用SqlSession对应的方法
 4   switch (command.getType()) {
 5     // 新增
 6     case INSERT: {
 7       // 使用ParamNameResolver处理args数组,将用户传入的实参与指定参数名称关联起来
 8       Object param = method.convertArgsToSqlCommandParam(args);
 9       // 调用sqlSession.insert方法,rowCountResult方法会根据method字段中记录的方法的返回值类型对结果进行转换
10       result = rowCountResult(sqlSession.insert(command.getName(), param));
11       break;
12     }
13     // 更新
14     case UPDATE: {
15       Object param = method.convertArgsToSqlCommandParam(args);
16       result = rowCountResult(sqlSession.update(command.getName(), param));
17       break;
18     }
19     // 删除
20     case DELETE: {
21       Object param = method.convertArgsToSqlCommandParam(args);
22       result = rowCountResult(sqlSession.delete(command.getName(), param));
23       break;
24     }
25     // 查询
26     case SELECT:
27       // 处理返回值为void是ResultSet通过ResultHandler处理的方法
28       if (method.returnsVoid() && method.hasResultHandler()) {
29         // 如果有结果处理器
30         executeWithResultHandler(sqlSession, args);
31         result = null;
32       // 如果结果有多条记录
33       } else if (method.returnsMany()) {
34         result = executeForMany(sqlSession, args);
35       // 处理返回值为map的方法
36       } else if (method.returnsMap()) {
37         result = executeForMap(sqlSession, args);
38       // 处理返回值为cursor的方法
39       } else if (method.returnsCursor()) {
40         result = executeForCursor(sqlSession, args);
41       } else {
42         // 处理返回值为单一对象的方法
43         Object param = method.convertArgsToSqlCommandParam(args); // 获取方法参数值
44         result = sqlSession.selectOne(command.getName(), param);  // 根据 类全限定名 + 方法名、方法参数,执行sql
45         if (method.returnsOptional() &&
46             (result == null || !method.getReturnType().equals(result.getClass()))) {
47           result = Optional.ofNullable(result);
48         }
49       }
50       break;
51     case FLUSH:
52       result = sqlSession.flushStatements();
53       break;
54     default:
55       throw new BindingException("Unknown execution method for: " + command.getName());
56   }
57   if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
58     throw new BindingException("Mapper method '" + command.getName()
59         + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
60   }
61   return result;
62 }

  根据不同的sqlCommandType和返回类型执行不同sqlSession方法。以查询为例,进行SQL执行流程分析:

UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 查询流程
User user02 = mapper.selectUserById("101");

2.1、方法参数解析成SQL参数

  方法参数args数组转换成SQL语句对应的参数列表,MapperMethod#convertArgsToSqlCommandParam() 核心方法:

public Object convertArgsToSqlCommandParam(Object[] args) {
  return paramNameResolver.getNamedParams(args);
}

  通过参数名称解析器paramNameResolver解析方法参数,paramNameResolver对象在创建MethodSignature时被初始化赋值的。

2.2、执行SQL

1、查询

  后续查询流程,详见Mybatis源码(八):查询执行流程

2、新增|更新|删除

  后续增删改流程,详见:Mybtais源码(九):增删改执行流程

3、核心流程图

  以查询为例,核心流程图如下:

 

 

 

 

posted @ 2023-03-20 22:08  无虑的小猪  阅读(234)  评论(0编辑  收藏  举报