• MyBatis是如何执行一条SQL语句的


    背景

    在前两天的一次面试中,面试官问了一个和标题一样的问题,由于一直认为MyBatis只是一个ORM框架,所以并没有对他有过深入的了解,于是被问到了,那么这一篇文章来从源码探究一下MyBatis是如何执行一条SQL语句的。

    阅读环境

    源码环境,github直接下载的main分支代码,版本号为 3.5.11-SNAPSHOT
    github点击跳转

    Ide Jetbrain Idea 2021.2.1 社区版
    Maven 3.8.2
    JDK 17

    配置文件

    1. <?xml version="1.0" encoding="UTF-8" ?>
    2. <!DOCTYPE configuration
    3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
    5. <configuration>
    6. <environments default="development">
    7. <environment id="development">
    8. <transactionManager type="JDBC"/>
    9. <dataSource type="POOLED">
    10. <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
    11. <property name="url" value="jdbc:mysql://******/**"/>
    12. <property name="username" value="****"/>
    13. <property name="password" value="******"/>
    14. </dataSource>
    15. </environment>
    16. </environments>
    17. <mappers>
    18. <mapper resource="LogChartMapper.xml"/>
    19. </mappers>
    20. </configuration>

    测试主方法

    1. public class Main {
    2. public static void main(String[] args) throws IOException {
    3. InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
    4. SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
    5. SqlSession sqlSession = build.openSession();
    6. LogChartMapper mapper = sqlSession.getMapper(LogChartMapper.class);
    7. List<LogChart> logCharts = mapper.selectList();
    8. System.out.println(JSONUtil.toJsonStr(logCharts));
    9. }
    10. }

    mapper

    1. public interface LogChartMapper {
    2. @Select(value = "select * from log_chart")
    3. List<LogChart> selectList();
    4. }

    mapper.xml

    1. <?xml version="1.0" encoding="UTF-8" ?>
    2. <!DOCTYPE mapper
    3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    5. <mapper namespace="org.mybatis.test.mapper.LogChartMapper">
    6. </mapper>

    阅读过程

    加载XML的过程

    从上方的测试主方法可以看到,比较重要的是 SqlSessionFactory,第一步需要看的就是他是被如何构建出来的,点击build方法,跟进去。

    Build方法追到最下面,构建了一个XMLConfigBuilder,那么这个Parser方法,有必要进去看一下。

    这段代码比较简短,可以看到两个信息点。第一个返回的类型是Configuration类,调用了一个parseConfiguration方法,并且传入的configuration的xml节点。
    说明方法进入是对的,后续我们继续跟一下 parseConfiguration方法

    针对这个方法,看到几个眼熟的东西。分别是解析 plugins、objectFactory、mappers 这些标签的。其他的我们这里不关心,我们主要看一下处理 Mappers 标签的这个方法。

    这里通过遍历我们写的每个mapper标签,会得到resource、url、mapperClass这三个变量。他们在配置时是互斥的。 这里的三个if,也对应了每个不为空的情况。

    具体的每个属性代表什么意思,可以参考 > 跳转链接

    我们的XML使用的是Resources属性,可以回到上面翻XML,我们这里看Resource标签不为空的 if 判断即可。

    这里注意看,Myabtis去读取了Mapper指向的xml,然后new了一个XmlMapperBuilder,随后调用了parse方法,这里的逻辑和刚刚开头的地方很相似。值得注意的是在创建XmlMapperBuilder的时候吧Configuration传了进去,也就是说在parse中是会改变这个configuration里面的东西的。到这里接着往里面跟代码。

    看名字有两个比较重要的方法,ConfigurationElement、binMapperForNameScpace。
    configurationElement 看这个方法的入参,大概率是对XML做一些处理,像ResultMap、sql、select这些标签做处理,我这里没有用到就不说了,感兴趣的可以自己阅读一下。

    后面主要看一下BindMapperForNameSpace方法。

    创建Mapper

    上面的代码中,看到了AddMapper这个方法,传入的值,是刚刚xml中配置的nameSpace,也就是Mapper类的完整类名,追进去看一下。

    看到这里,知道了一个知识点,为什么Mapper类一定要是 Interface,如果不是interface,这个方法就进不来了。

    注意看了,有一个细节,向knownMappers中,以这个class为键,放入了一个MapperProxyFactory。到达这里,就可以得知。所有的Mapper方法,都是由代理类去调用的。

    接下来往下,又看到一个相似的,parse方法,看他的类名 MapperAnnotationBuilder 是用来处理注解的,往里追看一下。

    这里的圈出来的 IF 判断,判断方法上有Select 或者 SelectProvider注解,并且没有ResultMap注解的。会去解析ResultMap,这里大概率是根据返回值,去遍历属性这里就跳过了,进入下一个方法。

    parseStatement方法像是去解析方法了,代码有点多,这里就不粘贴了,感兴趣可以自己看一下,知道这里是去解析方法就可以了。

    到这里结束,最后返回了SqlSessionFactory类。

    获得一个Mapper

    1. SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
    2. SqlSession sqlSession = build.openSession();
    3. LogChartMapper mapper = sqlSession.getMapper(LogChartMapper.class);

    直接追进去,看看代码。

    knownMapper 这个对象很眼熟是不是,这个在上面有提到过,以Class为Key,value时MapperProxyFactory,所以这里可以Get出来一个工厂,然后点用了newInstance方法,追进去看一下。

    这里看到是Java原生的动态代理,这里返回的是一个代理对象。

    执行一个Mapper的方法

    因为看到这里时Java的动态代理,所以也很显而易见的得知。MapperProxy实现了InvocationHandler接口。

    实现这个接口,会重写Invoke方法。也就是说,我们虽然代码上写的是调用的我们Mapper中指向的方法,但是其实jvm会去调用这个invoke方法,接下来直接去追Invoke方法。

    由于我们mapper是接口,所以上面的if是为false的,也就是else是我们执行的代码,接着往里追。

    这里可以看到new了一个PlainMethodInvoker,然后调用他的invoke方法,追进来。

    接下来这个Execute就是他的具体执行Select语句的地方了。

    由于后面的代码有点长,我这里就摘取了一小段关键的地方。看一下即可。

    结论

    好了,到这里就结束了,下次再遇到这个问题就知道怎么回答了,我总结一个完整的。

    MyBatis先读取配置文件生成一个configuration对象,然后会生成对应的XmlParseBuilder,在对应的XmlParseBuilder会向configuration中的knowMapper中,放入对应的MapperProxyFactory。然后要执行mapper方法的时候,会通过mapperProxyFactory拿到一个Mapper的动态代理对象。随后会动态代理对象会根据当前的方法去判断调用SqlSession的不同的方法来执行Sql语句。

     

     

  • 相关阅读:
    Linux从入门到精通(十)——进程管理
    代码随想录笔记_动态规划_392判断子序列
    一文读懂 Shiro 登录认证全流程
    新能源汽车展厅用哪些种类的显示屏比较好?
    浅谈Zk和Optimistic Rollups:原理、区别和前景
    thinkphp5 URL和路由的功能详解与实例
    1024程序员节——我是猿,我为自己带盐
    VM装Windows虚拟机扩容
    vue2.x封装svg组件并使用
    大数据(一)背景和概念
  • 原文地址:https://blog.csdn.net/m0_71777195/article/details/125600326