mybatis运行原理04-mapper代理对象方法调用之Map

2021-03-08  本文已影响0人  布拉德老瓜

在上一篇文章中,讲解了由SqlSession获取到mapper代理对象的过程,在这个过程中,首先创建了一个与sqlSession关联的InvocationHandler: mapperProxy, 然后调Proxy.newInstance(loader, interfaces, ih)创建了指定mapper接口的代理对象proxyObj,这个代理对象实际上是通过mapperProxy.invoke(proxyObj, method, args)生效的。即当我们调用proxyObj.method(args)时,由mapperProxy.invoke(proxyObj, method, args)来进行处理。
本文就来讲述mapperProxy.invoke(proxyObj, method, args)是如何工作的:包括为方法创建mapperMethod对象和mapperMethod.execute()方法。至于如何创建连接、sql语句参数处理、执行sql语句、事务控制、返回指定结果集,放到下一篇文章再说。

对象关系

先上代码:

    //mapperProxy.invoke(proxy, method, args)
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }

上来先判断定义该方法的是不是Object类,如果是(比如hashCode()等方法),没什么好说的,让方法执行。如果不是,那就先执行cachedInvoker(method),给创建一个invoker,然后再调用invoker的invoke()方法。这里有两个问题:
1.该方法对象可能没有实现逻辑,除了方法的参数类型和返回值类型外,还缺少要执行的的sql语句。那么就需要为其进行sql语句的封装、参数化的处理和结果集的处理。

2.java8中接口可以有实现方法(接口中的default方法),也可以有抽象方法。该创建一个怎样的invoker呢?显然要判断一下,然后区别对待。cachedInvoker()方法把这件事情交给了this.methodCache.computeIfAbsent(method, mappingFunction)来完成。

    private MapperProxy.MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
        try {
            //  computeIfAbsent(method, mappingFunction):
            // method: 被调用的方法对象
            // mappingFunction: 对方法进行映射处理的函数
            return (MapperProxy.MapperMethodInvoker)this.methodCache.computeIfAbsent(method, (m) -> {
                if (m.isDefault()) {
                    try {
                        return privateLookupInMethod == null ? new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava8(method)) : new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava9(method));
                    } catch (InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException var4) {
                        throw new RuntimeException(var4);
                    }
                } else {
           //通常在mapper接口的中要被调用的方法并不是default类型,走这里的流程
                    return new MapperProxy.PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()));
                }
            });
        } catch (RuntimeException var4) {
            Throwable cause = var4.getCause();
            throw (Throwable)(cause == null ? var4 : cause);
        }
    }

看看this.methodCache.computeIfAbsent(method, mappingFunction)是如何做到的。
methodCache对象是一个HashMap,最初key:value都是空的。computeIfAbsent方法接收两个参数,一个是方法对象method,另一个是对方法对象进行映射处理的函数对象mappingFunction。computeIfAbsent所做的事情就是重新计算method对应的value应该取什么值,并将key:value存入到Map里,然后返回value。

    default V computeIfAbsent(K key,
            Function<? super K, ? extends V> mappingFunction) {
        Objects.requireNonNull(mappingFunction);
        V v;
        if ((v = get(key)) == null) {
            V newValue;
            if ((newValue = mappingFunction.apply(key)) != null) {
                put(key, newValue);
                return newValue;
            }
        }

        return v;
    }

现在我们知道了method,即methodCache里应该存放的key,那么value是谁呢?就是mappingFunction(method)计算出的值。mappingFunction的计算逻辑是什么呢?如下:如果方法在接口中被声明为default,即public且非抽象的实例方法,那么就创建MapperProxy.DefaultMethodInvoker;否则,创建MapperProxy.PlainMethodInvoker。。接口中的default方法:https://blog.csdn.net/wf13265/article/details/79363522

a default method
所以对于非default方法,将method: PlainMethodInvoker对象以key:value的形式存放到了methodCache中并返回了PlainMethodInvoker对象
  //mappingFunction
    (m) -> {
            if (m.isDefault()) {
        // Default methods are public non-abstract instance methods
        // declared in an interface.
                try {
                    return privateLookupInMethod == null ? new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava8(method)) : new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava9(method));
                } catch (InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException var4) {
                    throw new RuntimeException(var4);
                }
            } else {
                return new MapperProxy.PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()));
            }
        }
经过了this.methodCache.computeIfAbsent(method, mappingFunction)之后,methodCache内就存入了method: methodInvoker,同时将methodInvoker返回给了调用方。如图: computeIfAbsent之后的methodCache 调用关系

现在我们重新回到文章的第一段代码mapperProxy.invoke(...)内了。到此时,创建了一个PlainMethodInvoker,并将其与被调用的方法存到了methodCache内。

//mapperProxy.invoke()
return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
//刚完成了this.cachedInvoker(method),得到了PlainMethodInvoker对象invoker。
//现在需要做的是invoker.invoke(proxy, method, args, this.sqlSession)

前面创建PlainMethodInvoker的时候还没有看到底是怎么new出来的,现在过一遍,构造方法很简单,复杂的地方在于mapperMethod的创建。

    private static class PlainMethodInvoker implements MapperProxy.MapperMethodInvoker {
        private final MapperMethod mapperMethod;

        public PlainMethodInvoker(MapperMethod mapperMethod) {
            this.mapperMethod = mapperMethod;
        }

        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
            return this.mapperMethod.execute(sqlSession, args);
        }
    }

当时创建是通过下面的这段代码完成PlainMethodInvoker创建的:先创建一个MapperMethod,将它作为invoker构造方法的参数。

    return new MapperProxy.PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()));

mapperMethod作为PlainMethodInvoker的唯一属性,显然非常关键,因为invoker.invoke()的功能就是通过mapperMethod.execute()来完成的。其构造方法如下:

    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
        this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
    }

在这个构造方法内,为该方法对象创建了sql语句的查询类型和name,为方法设置方法签名。

  1. 创建sql语句
    public static class SqlCommand {
        private final String name;
        private final SqlCommandType type;
       
        public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
            //获取方法名
            String methodName = method.getName();
            //获取方法所在的类
            Class<?> declaringClass = method.getDeclaringClass();
            //获取映射语句信息
            MappedStatement ms = this.resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
            ///若ms是NULL,则查看该方法上是否标注了FLUSH标签,如果存在,则设置查询类型为FLUSH,否则抛出异常表示接口找不到定义的方法。
            if (ms == null) {
                if (method.getAnnotation(Flush.class) == null) {
                    throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName);
                }

                this.name = null;
                this.type = SqlCommandType.FLUSH;
            } else {
                // 找到了,为sqlCommand对象设置查询类型
                this.name = ms.getId();
                this.type = ms.getSqlCommandType();
                if (this.type == SqlCommandType.UNKNOWN) {
                    throw new BindingException("Unknown execution method for: " + this.name);
                }
            }
        }
        //......
}

获取方法名,然后根据类名和方法名去对应的mappedStatement.之前在解析mapper的时候,将mapper的信息都存放到了configuration中,并有部分已经解析完了。现在要做的事情就是利用configuration.getStatement(statementId)将剩余的部分resultMap、sql语句解析出来。

private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration) {
            //statementID = 类名 + 方法名
            String statementId = mapperInterface.getName() + "." + methodName;
            // 如果有还没解析完的statement,那么接着解析完并返回是否包含当前statementId
            if (configuration.hasStatement(statementId)) {
                // 解析出对应的mappedStatement
                return configuration.getMappedStatement(statementId);
            } else if (mapperInterface.equals(declaringClass)) {
                return null;
            } else {
                // 如果在当前接口解析不到,且方法并不是在当前接口定义的,则去父接口中解析该statement
                Class[] var6 = mapperInterface.getInterfaces();
                int var7 = var6.length;

                for(int var8 = 0; var8 < var7; ++var8) {
                    Class<?> superInterface = var6[var8];
                    if (declaringClass.isAssignableFrom(superInterface)) {
                        MappedStatement ms = this.resolveMappedStatement(superInterface, methodName, declaringClass, configuration);
                        if (ms != null) {
                            return ms;
                        }
                    }
                }

                return null;
            }
        }
    public MappedStatement getMappedStatement(String id) {
        return this.getMappedStatement(id, true);
    }

    public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
        if (validateIncompleteStatements) {
            this.buildAllStatements();
        }

        return (MappedStatement)this.mappedStatements.get(id);
    }

buildAllStatements完成剩下的CacheRefs、Statements和methods的解析。重点关注parseStatementNode是如何创建出sql语句的:如果还没有解析完成,那么就对其解析并从未解析的map中移除。

protected void buildAllStatements() {
        this.parsePendingResultMaps();
        if (!this.C.isEmpty()) {
            synchronized(this.incompleteCacheRefs) {
                this.incompleteCacheRefs.removeIf((x) -> {
                    return x.resolveCacheRef() != null;
                });
            }
        }

        if (!this.incompleteStatements.isEmpty()) {
            synchronized(this.incompleteStatements) {
                this.incompleteStatements.removeIf((x) -> {
                    x.parseStatementNode();
                    return true;
                });
            }
        }

        if (!this.incompleteMethods.isEmpty()) {
            synchronized(this.incompleteMethods) {
                this.incompleteMethods.removeIf((x) -> {
                    x.resolve();
                    return true;
                });
            }
        }

    }

对于需要解析的,就像之前在构建SqlSessionFactory中做的那样,使用parseStatementNode()再解析一遍statement.
至此,sqlCommand所需的mappedStatement就有了(还是放在Configuration中),然后就是为其命名(id|方法名)和指定类型(SELECT/UPDATE...)。

  1. 创建方法签名
    解析方法的返回值类型,参数名解析,为MapperMethod指定resultHandlerIndex(用于找到resultHandler)和paramNameResolver(用于参数名解析)。

至此,mapperMethod创建完成,执行就交给了mapperMethod.execute().

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        switch(this.command.getType()) {
        case INSERT:
            //将方法的参数解析为sql语句的参数
            param = this.method.convertArgsToSqlCommandParam(args);
            //解析返回结果
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        //对于增删改而言,方法的返回结果比较固定,就是影响的行数。
        //但对于查找来说,返回结果则复杂一点
        case SELECT:        
            //如果返回void类型且定义了ResultHandler,那么需要resultHandler去处理查找结果并返回void。
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            //返回结果为多条记录
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            //返回结果为Map     
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            //返回游标位置
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
            //常规查找, 先解析参数, 若结果为null或返回结果类型与方法的返回类型不一致,则返回Optional.ofNullable.(看似非空,实则为空,避免空指针异常)
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
                if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }

到这里,我们不难理解sqlCommand的作用了,那就是指定sql语句的类型。一共有5种,crud和Flush(flush cache)。而方法签名则定义了参数名解析器和返回值类型。

本节的重点在于,mapper代理对象的方法在被调用时,发生了什么。了解一下动态代理,就知道实际上是与该代理对象关联的invocationHandler去invoke了该代理对象的方法。invoke的时候,创建了mapperMethod对象,该对象与mapper接口的方法、mapper.xml文件中的sql语句的<id>相对应。内部封装了mapper接口方法的签名、参数名解析器和与sql对应的sqlCommand对象。通过mapperMethod对象,可以完成方法参数解析,返回值类型判断和sql语句类型判断,最终将sql语句id和参数交给sqlSession去完成对数据的操作。
所以现在问题又来了,参数是如何被解析到sql中的?这个execute()过程中mybatis是怎样与数据库交互的获取到结果集的?Object类型的结果集又是怎么被封装成了指定的类型的?ResultHandler是个什么东西,它又是怎样处理返回结果的?
下篇文章再看吧。

上一篇下一篇

猜你喜欢

热点阅读