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()
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标识、返回结果是否为集合标识、返回结果是否为游标标识等;
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、核心流程图
以查询为例,核心流程图如下:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)