美文网首页
Mybatis中Mapper接口如何和xml中sql关联(二)

Mybatis中Mapper接口如何和xml中sql关联(二)

作者: 萤火之森ss | 来源:发表于2017-08-01 19:44 被阅读188次

    继续上一篇,了解到,MyBatis中利用的JDK的动态代理,实现了接口管联xml中的sql,这里呢,只是知道了用什么方式,但是具体的怎么关联不清楚,需要再次学习其中的内容,go。

    知道了动态代理后,先看MapperProxy类,他实现了InvocationHandler,那么也重写了invoke方法,找个这个方法
    MapperProxy.class 中

     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if(Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else {
                MapperMethod mapperMethod = this.cachedMapperMethod(method);
                return mapperMethod.execute(this.sqlSession, args);
            }
        }
    

    都知道执行ActivityCzMapper 接口的方法,会调用这里MapperProxy的invoke方法,看到第一行代码if判断

    Object.class.equals(method.getDeclaringClass())
    

    这里的method肯定是我们调用ActivityCzMapper 接口的方法,getDeclaringClass()肯定返回null, 因为我们的Mapper接口就没有实现类,不可能走下去,所以执行else里的代码,else里面执行了cachedMapperMethod()方法,我们看一下

    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;
        }
    

    首先在methodCache的map中get传来的method(就是访问Mapper接口的方法)key,结果肯定是null,执行下面代码初始化MapperMethod三个参数的构造方法,第一个参数Mapper接口,第二个接口的方法,第三个是一个sqlSession的getConfiguration方法代表sqlsession的所有信息,在上一篇中传递的那个sqlsession,初始化MapperProxy类的构造方法时。

    现在跳到这个MapperMethod的构造方法查看
    MapperMethod.class中

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

    看到初始化两个成员变量,command,method,在继续看SqlComman()方法是干什么的,跳到MapperProxyFactory类中的静态内部类SqlCommand,看到代码

    public static class SqlCommand {
            private final String name;
            private final SqlCommandType type;
    
            public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) throws BindingException {
                String statementName = mapperInterface.getName() + "." + method.getName();
                MappedStatement ms = null;
                if(configuration.hasStatement(statementName)) {
                    ms = configuration.getMappedStatement(statementName);
                } else if(!mapperInterface.equals(method.getDeclaringClass().getName())) {
                    String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
                    if(configuration.hasStatement(parentStatementName)) {
                        ms = configuration.getMappedStatement(parentStatementName);
                    }
                }
    
                if(ms == null) {
                    throw new BindingException("Invalid bound statement (not found): " + statementName);
                } else {
                    this.name = ms.getId();
                    this.type = ms.getSqlCommandType();
                    if(this.type == SqlCommandType.UNKNOWN) {
                        throw new BindingException("Unknown execution method for: " + this.name);
                    }
                }
            }
    
            public String getName() {
                return this.name;
            }
    
            public SqlCommandType getType() {
                return this.type;
            }
        }
    

    哇,看这一条代码

    String statementName = mapperInterface.getName() + "." + method.getName();
    

    这个 mapperInterface.getName()就是Mapper接口的类名字,他必然和xml的
    <mapper namespace="com.car.service.dao.ActivityCzMapper" > 一致,method.getName()得到方法的名字,
    那就是mapper里的id,然后判断

    MappedStatement ms = null;
    if(configuration.hasStatement(statementName)) {
                    ms = configuration.getMappedStatement(statementName);
                }
    

    成立后,看下MappedStatement的意思,MappedStatement对象对应Mapper配置文件中的一个select/update/insert/delete节点,主要描述的是一条SQL语句。
    这样把他包装起来,在执行下面的代码,ms != null 后赋值,name 就是mapper xml 中sql 的id值,type就是sql的类型(sel,up,de)如果不知道,那就异常。

    OK现在我们返回到MapperMethod中,说白了,传来的sqlSession到SqlCommand,在到MapperMethod。
    现在在回到MapperProxy的invoke方法中走最后一步, return mapperMethod.execute(this.sqlSession, args);
    查看execute方法

    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()) {
                    throw new BindingException("Unknown execution method for: " + this.command.getName());
                }
    
                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);
                }
            }
    
            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;
            }
        }
    

    有点多,但是很明确,清楚看出来,首先判断sql的类型,up,sel,de的,在select中还有一层判断Mapper接口方法的返回值,我们查的是selectByPrimaryKey单个的,那就走selectOne()方法,例如我们的返回值List,那需要走executeForMany,可以看下这个源代码:

    private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
            Object param = this.method.convertArgsToSqlCommandParam(args);
            List result;
            if(this.method.hasRowBounds()) {
                RowBounds rowBounds = this.method.extractRowBounds(args);
                result = sqlSession.selectList(this.command.getName(), param, rowBounds);
            } else {
                result = sqlSession.selectList(this.command.getName(), param);
            }
    
            return !this.method.getReturnType().isAssignableFrom(result.getClass())?(this.method.getReturnType().isArray()?this.convertToArray(result):this.convertToDeclaredCollection(sqlSession.getConfiguration(), result)):result;
        }
    

    恩,很稳,贴上一句关键点

     result = sqlSession.selectList(this.command.getName(), param, rowBounds);
    

    这就不多说了,将结果返回。这样通过MapperProxy的 public Object invoke(Object proxy, Method method, Object[] args)方法
    返回结果。这样就是一个执行Mapper接口方法执行sql的过程。在整个过程中还有很多地方未探索的,就比如在mybatis配置文件加载的时候干了什么,例如那个addMapper()方法到底在MyBatis哪个加载过程中add的,需要再次探索。以上是我的整个学习过程。

    相关文章

      网友评论

          本文标题:Mybatis中Mapper接口如何和xml中sql关联(二)

          本文链接:https://www.haomeiwen.com/subject/xvqslxtx.html