mybatis源码解析(四)-Mapper方法调用过程

mybatis源码解析(一)-开篇
mybatis源码解析(二)-加载过程
mybatis源码解析(三)-SqlSession.selectOne类似方法调用过程
mybatis源码解析(四)-Mapper方法调用过程
mybatis源码解析(五)-mybatis如何实现的事务控制
mybatis源码解析(六)-配合spring-tx实现事务的原理
mybatis源码解析(七)-当mybatis一级缓存遇上spring

转载请标明出处:
http://blog.csdn.net/bingospunky/article/details/79220894
本文出自马彬彬的博客

上一篇博客描述了SqlSession的执行过程。但是我们使用mybatis时一般使用Mapper进行增删改查,这篇文章展示一下Mapper的方法是如何执行的。
使用Mapper进行增删改查分为两个步骤,第一步先获取Mapper对象,第二部调用Mapper的方法。
我们调用的方法对应的xxxMapper.xml的内容如下:

Code 1

    <select id="selectById" resultMap="baseResultMap">
        select
        "baseColumnList"/>
        from
        table1
        where id = #{id}
    select>

获取Mapper

获取Mapper的主要代码如下:

Code 2
org.apache.ibatis.binding.MapperProxyFactory

    public T newInstance(SqlSession sqlSession) {
        MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy); 
    }

这段代码是MapperProxyFactory里的,MapperProxyFactory的作用就是来生成Mapper,一个Mapper对应一个MapperProxyFactory。
从代码里可以看到:我们获取的对象是通过JdbcProxy生成的,代理对象需要的三个参数:第一个不解释;第二个就是我们要获取的Mappe接口;第三个参数mapperProxy就比较关键了,它就是一个回调,当我们调用Mapper接口的方法时,会回调mapperProxy里的方法,我们需要在mapperProxy里实现回调方法,真正去操作数据库。关于mapperProxy,会在执行查询过程详细叙述。

调用Mapper的方法

由于我们获取的Mapper对象是JdbcProxy对象,在我们调用方法时,回调接口的方法会被调用,回调接口里的代码如下:

Code 3
org.apache.ibatis.binding.MapperProxy

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            try {
                return method.invoke(this, args);
            } catch (Throwable var5) {
                throw ExceptionUtil.unwrapThrowable(var5);
            }
        } else {
            MapperMethod mapperMethod = this.cachedMapperMethod(method);
            return mapperMethod.execute(this.sqlSession, args);
        }
    }
    private MapperMethod cachedMapperMethod(Method method) {
        MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
        if (mapperMethod == null) {
            mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
            this.methodCache.put(method, mapperMethod);
        }
        return mapperMethod;
    }

第9行通过Method获取MapperMethod,第10行调用MapperMethod.execute方法执行后面真正的sql。当我们获取MapperMethod时,如果是第一个获取的话,需要构造一个MapperMethod。下面我们从构造MapperMethod,和执行MapperMethod.execute两部分描述。

构造MapperMethod

构造MapperMethod的代码如下:

Code 4
org.apache.ibatis.binding.MapperMethod

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

MapperMethod.SqlCommand比较简单,就包含这个Mapper方法对应的MappedStatement的Id和该方法对应的SqlCommandType,SqlCommandType有UNKNOWN,INSERT,UPDATE,DELETE,SELECT,FLUSH这几个值。这里可以注意一点,当这个Mapper方法没有对应的MappedStatement且该方法没有被org.apache.ibatis.annotations.Flush注解标记时会抛异常。这很好理解,在xxxMapper.xml里没有对应的配置,所有这个方法没法执行。

MapperMethod.MethodSignature稍微麻烦一点,构造MapperMethod.MethodSignature的代码如下:

Code 5
org.apache.ibatis.binding.MapperMethod.MethodSignature

    public MethodSignature(Configuration configuration, Method method) {
        this.returnType = method.getReturnType();
        this.returnsVoid = Void.TYPE.equals(this.returnType);
        this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
        this.mapKey = this.getMapKey(method);
        this.returnsMap = this.mapKey != null;
        this.hasNamedParameters = this.hasNamedParams(method);
        this.rowBoundsIndex = this.getUniqueParamIndex(method, RowBounds.class);
        this.resultHandlerIndex = this.getUniqueParamIndex(method, ResultHandler.class);
        this.params = Collections.unmodifiableSortedMap(this.getParams(method, this.hasNamedParameters));
    }

第2行获取Mapper的方法返回值的类型。第4行获取返回类型是不是集合,比如数组或Collection接口都是集合,这个值后面分析会用到。第7行判断该方法的参数中是否包含了org.apache.ibatis.annotations.Param注解,这个值会影响实参的处理。第8行获取参数中RowBounds类型的位置,如果参数中包含多个RowBounds类型参数,也会抛异常。第9行与第8行类似,获取ResultHandler类型参数的位置。第10行就厉害了,它会生成一个SortedMap

执行MapperMethod.execute

MapperMethod.execute的代码如下:

Code 6
org.apache.ibatis.binding.MapperMethod

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object param;
        Object result;
        if (SqlCommandType.INSERT == this.command.getType()) {
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
        } else if (SqlCommandType.UPDATE == this.command.getType()) {
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
        } else if (SqlCommandType.DELETE == this.command.getType()) {
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
        } else if (SqlCommandType.SELECT == this.command.getType()) {
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
            }
        } else {
            if (SqlCommandType.FLUSH != this.command.getType()) {
                throw new BindingException("Unknown execution method for: " + this.command.getName());
            }
            result = sqlSession.flushStatements();
        }
        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;
        }
    }

代码对不同的情况进行不了不同的处理,把所有的情况分为下面这些类型:insert、delete、update、select、flust,在select中,又根据如何对返回值进行处理进行的划分,分别为:使用自定义ResultHandler处理、返回对象集合、返回Map、返回单个对象。下面我们不讨论flust的情况。
第23行可以看出来,调用了org.apache.ibatis.session.SqlSession.selectOne方法去操作数据库。关于org.apache.ibatis.session.SqlSession.selectOne方法类似方法是如何执行的可以参考上一篇博客。对于其他的情况,他们都是调用了org.apache.ibatis.session.SqlSession的方法,它们要做的有两点:1、选择适当的org.apache.ibatis.session.SqlSession方法进行调用;2.把Mapper方法传递进来的参数转化org.apache.ibatis.session.SqlSession对应方法支持的参数。我们知道org.apache.ibatis.session.SqlSession的参数就一个,要么是单个对象,要么集合,要么Map,二我们在调用Mapper方法时传递进来的是多个参数,我们JdbcProxy里以数组形式保存他们,所以要进行参数的转化。参数转化调用的都是一个方法,该方法的代码如下:

Code 7
org.apache.ibatis.binding.MapperMethod

    public Object convertArgsToSqlCommandParam(Object[] args) {
        int paramCount = this.params.size();
        if (args != null && paramCount != 0) {
            if (!this.hasNamedParameters && paramCount == 1) {
                return args[((Integer)this.params.keySet().iterator().next()).intValue()];
            } else {
                Map<String, Object> param = new MapperMethod.ParamMap();
                int i = 0;
                for(Iterator i$ = this.params.entrySet().iterator(); i$.hasNext(); ++i) {
                    EntryString> entry = (Entry)i$.next();
                    param.put(entry.getValue(), args[((Integer)entry.getKey()).intValue()]);
                    String genericParamName = "param" + String.valueOf(i + 1);
                    if (!param.containsKey(genericParamName)) {
                        param.put(genericParamName, args[((Integer)entry.getKey()).intValue()]);
                    }
                }
                return param;
            }
        } else {
            return null;
        }
    }

第5行,当参数只有一个且参数没有org.apache.ibatis.annotations.Param注解时,就返回数组的数组的第一个值,也是调用Mapper方法的第一个值。其他情况是都返回Map。在构造Map时,使用到了本篇博客中前面提到的SortedMap。对于SortedMap每一个位置,这个位置的key为序号,value为org.apache.ibatis.annotations.Param注解的值(如果没有就是序号的字符串形式),我们需要把它转化成键值对的形式,键值对的key应该是xxxMapper.xml取值的key,那应该就是org.apache.ibatis.annotations.Param注解的值(如果没有就是序号的字符串形式),所以正好是SortedMap的value;键值对的value应该是我们调用Mapper方法传递进来的参数的对应位置的值。第11行代码就是这么做的。

最后

浓缩一下:

Mapper方法的执行过程:先获取Mapper对象,该对象是JdbcProxy代理对象。代理对象回调接口里,会根据Method,执行org.apache.ibatis.session.SqlSession对应的方法,同时需要完成参数的转化。

你可能感兴趣的:(♚java♚,mybatis源码解析)