Mybatis执行SQL流程(五)之MapperProxy与MapperMethod
文章目录
- MapperProxy
- MapperMethod
- Insert
- Select
MapperProxy
实现 InvocationHandler
接口,作为代理的具体实现。主要功能就是执行invoke 方法。
invoke源码如下:
// mapperProxy 的 invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args); // 此处对Object的方法做了判断,例如toString,hashCode等} else {return cachedInvoker(method).invoke(proxy, method, args, sqlSession);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}
}private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {try {return MapUtil.computeIfAbsent(methodCache, method, m -> {// 判断是否为默认方法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 {// 普通 Mapper 方法。// PlainMethodInvoker 包装 MapperMethodreturn new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));}});} catch (RuntimeException re) {Throwable cause = re.getCause();throw cause == null ? re : cause;}
}
private static class PlainMethodInvoker implements MapperMethodInvoker {private final MapperMethod mapperMethod;public PlainMethodInvoker(MapperMethod mapperMethod) {super();this.mapperMethod = mapperMethod;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {return mapperMethod.execute(sqlSession, args); // 接下来就是核心处理类MapperMethod如何处理SQL了。}
}
MapperMethod
MapperMethod是核心执行类,包含:
-
SQL 命令类型(select/insert等)
-
参数处理逻辑
-
结果映射规则
-
组件 职责 MapperProxy
代理入口,路由方法调用 MapperMethodInvoker
方法调用器抽象接口 DefaultMethodInvoker
处理接口默认方法 PlainMethodInvoker
处理普通 Mapper 方法 MapperMethod
执行实际 SQL 逻辑
// MapperMethod.javapublic Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) {case INSERT: {Object param = method.convertArgsToSqlCommandParam(args); // 参数转换, 若是单个数据,则直接返回参数值,若是多个,则返回Mapresult = rowCountResult(sqlSession.insert(command.getName(), param));// 执行sql,并返回执行结果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()) { // 方法返回类型为 collection 或 数组result = executeForMany(sqlSession, args);} else if (method.returnsMap()) { // 方法返回类型为 Mapresult = executeForMap(sqlSession, args);} else if (method.returnsCursor()) { // 方法返回类型为 游标result = executeForCursor(sqlSession, args);} else {// 其他,返回单个对象 例如返回类型为 UserObject 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;}
接下来了解insert 与select语句。
Insert
- 转换参数
// Object param = method.convertArgsToSqlCommandParam(args);
// 内部调用 getNamedParams 方法
// 返回一个不带名称的非特殊参数。使用命名规则命名多个参数。除了默认名称外,此方法还添加通用名称(param1、param2 等)。public Object getNamedParams(Object[] args) {final int paramCount = names.size(); // SortedMap<Integer, String> names; key为下标,value为参数名,从@Param中获取,若没有该注解,则使用下标。例如// aMethod(@Param("M") int a, @Param("N") int b) -> {{0, "M"}, {1, "N"}}// aMethod(int a, int b) -> {{0, "0"}, {1, "1"}}if (args == null || paramCount == 0) {return null;} else if (!hasParamAnnotation && paramCount == 1) { // 若没有使用Param注解,且参数个数仅为1个,则直接返回对象。例如参数为user对象,则返回的Object也是User对象Object value = args[names.firstKey()];return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);} else {// 包含@Param注解,或参数个数不止一个final Map<String, Object> param = new ParamMap<>();int i = 0;for (Map.Entry<Integer, String> entry : names.entrySet()) {param.put(entry.getValue(), args[entry.getKey()]); // 将参数存入param map中,key为参数名value为参数值// add generic param names (param1, param2, ...) // todo 为什么要添加通用参数名,暂时不知道在哪里用到,后续为sql的?占位符赋上对应的数据时,也是根据参数名获取的final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);// ensure not to overwrite parameter named with @Paramif (!names.containsValue(genericParamName)) { // 避免覆盖@Param中的参数名param.put(genericParamName, args[entry.getKey()]);}i++;}return param;}}
以包含@Param注解为例:
其中param注解转换参数例如:
@Insert("insert into user(id, username) values (#{id}, #{username})")
int insert2User(Long id, @Param("username") String name);
其中names的值为:
(0, "id"), (1, "username");
// 将 参数下标与Param的值对应。若不存在Param注解,则使用参数名
传参args中包含两个入参值:10、testparam。经过转换后param包含了4个kv键值对。两个key是mapper接口中的入参名(如果有Param注解,以Parma注解中的为准;另外两个key是根据规则拼接的,即param中既包含了@Param注解中的参数名键值对,也包含了根据规则拼接好的参数键值对。
- sqlSession.insert执行流程:
// result = rowCountResult(sqlSession.insert(command.getName(), param));
// 1. sqlSession.insert
sqlSession.insert-> DefaultSqlSession.insert-> DefaultSqlSession.update-> ParamNameResolver.wrapToMapIfCollection(object, null)-> Executor.update // 此处的执行内容见 Executor 环节
// 2. rowCountResult :返回sql的执行结果,
// 将 Collection 或 数组类型的参数转化为 Map
public static Object wrapToMapIfCollection(Object object, String actualParamName) {if (object instanceof Collection) {ParamMap<Object> map = new ParamMap<>();map.put("collection", object);if (object instanceof List) {map.put("list", object);}Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));return map;} else if (object != null && object.getClass().isArray()) {ParamMap<Object> map = new ParamMap<>();map.put("array", object);Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));return map;}return object;
}
接下来就是要执行Executor.update。此处的执行内容见 Executor 环节
Select
参数转换
sqlSession.selectOne-> DefaultSqlSession.selectOne-> DefaultSqlSession.selectList-> Executor.query // 依然是交给 执行器 去执行sql
查询步骤大致如下:其中涉及到缓存问题,后续单独补充
接下来就是要执行Executor.query。此处的执行内容见 Executor 环节
总结:MapperProxy作为JDK代理的实际执行内容,内部将执行方法转化为MapperMethod,为增删查改操作进行了区分,不同操作后续的处理内容不同。在MapperMethod中进行了参数转换,具体的执行交给了SqlSession,在Sqlsession中将操作交给了Executor执行器。