Mybatis 源码剖析,执行 select 语句全过程

进行源码剖析,首先要先了解 Mybatis 的执行过程(或者说原理),其实就是如图的四个步骤:
Mybatis 源码剖析,执行 select 语句全过程
根据思路写代码,再次查看代码逻辑发现:
Mybatis 源码剖析,执行 select 语句全过程

创建 SqlSessionFactory

其实就是根据逻辑一行行写的代码,接下来关注源码:按住 ctrl 点进去 build 方法:
Mybatis 源码剖析,执行 select 语句全过程
返回另一个 build 方法,再次点击
Mybatis 源码剖析,执行 select 语句全过程
先不关注怎么解析 xml 的,首先看如果创建工厂,其实就是 传递解析 xml 的 config,然后 new 了一个默认的工厂,直接返回,此时工厂建立完毕

Mybatis 源码剖析,执行 select 语句全过程
接下来过一下如何解析 xml 文件:

点击 parse() 方法,进入
再次深入

Mybatis 源码剖析,执行 select 语句全过程
Mybatis 源码剖析,执行 select 语句全过程
然后发现这个代码,很长,不过一行行看能够大概知道什么意思,打个断点测试一下:

Mybatis 源码剖析,执行 select 语句全过程
进入 else 里面,点击 new XMLMapperBuilder 方法进入内部

private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            Iterator var2 = parent.getChildren().iterator();

            while(true) {
                while(var2.hasNext()) {
                    XNode child = (XNode)var2.next();
                    String resource;
                    if ("package".equals(child.getName())) {
                        resource = child.getStringAttribute("name");
                        this.configuration.addMappers(resource);
                    } else {
                        resource = child.getStringAttribute("resource");
                        String url = child.getStringAttribute("url");
                        String mapperClass = child.getStringAttribute("class");
                        XMLMapperBuilder mapperParser;
                        InputStream inputStream;
                        if (resource != null && url == null && mapperClass == null) {
                            ErrorContext.instance().resource(resource);
                            inputStream = Resources.getResourceAsStream(resource);
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                            mapperParser.parse();
                        } else if (resource == null && url != null && mapperClass == null) {
                            ErrorContext.instance().resource(url);
                            inputStream = Resources.getUrlAsStream(url);
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
                            mapperParser.parse();
                        } else {
                            if (resource != null || url != null || mapperClass == null) {
                                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                            }

                            Class<?> mapperInterface = Resources.classForName(mapperClass);
                            this.configuration.addMapper(mapperInterface);
                        }
                    }
                }

                return;
            }
        }
    }

最终发现,解析 xml 的实质就是将什么 返回值,入参,sql 等创建成 statement
Mybatis 源码剖析,执行 select 语句全过程
此时完成 SqlSessionFactory 的创建,得到一个与 xml 文件有关的 DefaultSqlSessionFactory,然后开始创建 SqlSession
Mybatis 源码剖析,执行 select 语句全过程

创建 SqlSession

然后创建 SqlSession,通过打断点测试,进入方法内部,发现其实 DefaultSqlSessionFactory 是 SqlSessionFactory 的实现类
Mybatis 源码剖析,执行 select 语句全过程
再次进入 openSessionFromDataSource 方法
Mybatis 源码剖析,执行 select 语句全过程
创建 executor 然后通过配置,创建一个对应的 DefaultSqlSession,也是一个 SqlSession 的实现类

获取 Mapper 对象

继续打断点测试,查看 mapper 层是如何获取的,一直进入方法内部,可以发现,又有一个 Mapper 的工厂
Mybatis 源码剖析,执行 select 语句全过程
根据断点一路深究,
Mybatis 源码剖析,执行 select 语句全过程
很明显看出,是一个动态代理,通过 工厂,创建代理对象,执行 invoke 方法,完成 mapper 对象的创建
Mybatis 源码剖析,执行 select 语句全过程
根据创建 DefaultSqlSessionFactory 得到的信息,创建出一个 mapper 对象

根据 Mapper 对象,得到返回结果

然后看执行 mapper 的方法,如何运行 sql 优势怎么得到结果的,进入方法内部,如下:

public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        switch(this.command.getType()) {
        case INSERT:
            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:
            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 if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                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;
        }
    }

Mybatis 源码剖析,执行 select 语句全过程
执行返回多个结果的方法:
Mybatis 源码剖析,执行 select 语句全过程
发现,其实就是根据之前得到的 statement 对象,获取当前标签的唯一标识符,然后开始执行执行里面的 sql

Mybatis 源码剖析,执行 select 语句全过程

Mybatis 源码剖析,执行 select 语句全过程
Mybatis 源码剖析,执行 select 语句全过程
这里给实体类赋值,是否可赋值,就是得出的结果是否与 resultType 相符合,(如果实体类,判断是否相对应
Mybatis 源码剖析,执行 select 语句全过程

Mybatis 源码剖析,执行 select 语句全过程

然后执行完结果,此时完成一个查找的全部流程

上一篇:一个基于RBAC0的通用权限设计清单


下一篇:MetadataReader