MyBatis执行一条sql语句的流程(源码解析)

MyBatis执行sql语句的流程

ini 复制代码
    //<1> 加载配置文件
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    //<2> 创建sessionFactory对象
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
    //<3> 获取sqlSession对象信息
    SqlSession session = sessionFactory.openSession();
    //<4> 构建映射器的代理对象
    SysUserMapper mapper = session.getMapper(SysUserMapper.class);
    //<5>调用相关方法信息
    SysUserPO sysUserPO = mapper.selectById(1L);

加载配置文件

我们进入<1>处的方法的: 我们发现一个陌生的对象classLoaderWrapper,看着像一个类加载器,我们进入这个类中查看: 发现这个对象包含两个类加载器,那么classLoaderWrapper也是关于进行类加载的对象。 我们进入getResourceAsStream方法中查看: 注意这里传入了一个空的类加载器对象,进入最底层: 我们发现这里传入了一个类加载器的数组,并且遍历了这个类加载器数组,然后尝试让类加载加载这个资源,加载成功后就立即返回。 但是这个类加载器数组是什么呢?我们返回上一层方法查看: 进入这个返回类加载器数组的方法:

kotlin 复制代码
    ClassLoader[] getClassLoaders(ClassLoader classLoader) {
        return new ClassLoader[]{
        //null,传入的时候为null
         classLoader,
         //classLoaderWrapper对象的类加载器变量
         this.defaultClassLoader, 
         //关于当前线程的类加载器
         Thread.currentThread().getContextClassLoader(), 
         //当前类的类加载器
         this.getClass().getClassLoader(), 
         //classLoaderWrapper对象的另一个类加载器变量
         this.systemClassLoader};
    }

加载配置文件的流程

通过以上的代码追踪,<1>的步骤如下:通过classLoaderWrapper对象的类加载器变量对mybatis的配置文件进行加载,返回了一个输入流。

创建sqlsessionFactory对象

步骤<2>中我们发现创建啦一个SqlSessionFactoryBuilder对象并且通过build方法返回了sqlsessionFactory对象,我们进入方法查看: 该方法内首先通过步骤<1>获取的输入流创建了一个XmlConfigBuilder对象,然后调用了其parse方法,我们先查看parse方法: 这个方法首先是对mybatis配置文件标签进行了解析,并且返回了configuration对象,那么这个解析配置的方法应该是对configration对象的属性进行了填充,那么这个configuration对象是什么呢?我们进入这个类查看: 可以看到Configuration类中含有许多变量,包括我们很熟悉的缓存、请求映射和返回结果映射,我们其实就可以想到整个解析配置的方法中应该是对配置文件中的每个标签进行了解析然后填充到Configuration对象中,我们查看这个解析方法:

解析Mapper

进入这个方法中,我们可以发现确实如此,对每个标签进行了解析,我们查看一个很关键的方法mapperElement:

我们发现生成了一个迭代器,应该是对mappers的多个子标签mapper进行了遍历,然后获取mapper标签的多个属性,可以看到mapper存在三个属性,分别是resource、url、class,由下方的非空判断可知这三者是互斥的,进行判断后,由通过属性对每一个mapper进行加载获取输入流,然后又创建了XmlMapperBuilder,调用其parse方法对每一个mapper进行解析,进入方法: 我们发现,第一个方法对mapper标签进行了解析,我们进入第一个方法: 可以看到,这个方法对mapper标签下的每一条sql进行了解析,包括缓存、请求映射、响应结果映射、查询类型等,我们注意上方我框起来的方法:获取mapper的命名空间,并且存储在了一个对象中。 我们接着查看第二个方法: 还记得上面讲的吗,我们获取对mapper进行解析时存储的mapper的命名空间,然后通过命名空间进行加载获取了mapper的类型,然后这个addMapper方法看起来好像是对configuration对象的属性进行了填充,并且传入了mapper的类型,我们点进去查看: 这里又出现了MapperRegistry对象,是Configuration对象的变量,继续查看: 这里创建了一个MapperProxyFactory对象,似乎是创建mapper代理工厂,并且把mapper和代理工厂放入一个map集合中。 我们再回到sqlsessionFactory的创建: 发现我们之前通过XmlConfigBuider解析的配置文件的标签以及通过XmlMapperBuilder解析的每一个mapper的标签得到的Configuration对象,通过这个Configuration对象创建了DefalutSqlsessionFactory。

创建sqlsessionFactory流程

首先创建了XmlConfigBuilder对象,解析mybatis配置文件,来填充Configuration,在解析<mappers标签过程中,创建了XmlMapperBuilder对象解析每一个mapper中的标签,并且利用了Configuration对象的mapperRegistry的map变量,存储了键为mapper类型,值为mapper代理工厂。

获取sqlsession

进入方法最底层:

java 复制代码
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
        // 获取sqlsessionFactory的configuration变量的环境
            Environment environment = this.configuration.getEnvironment();
        // 通过环境获取事务工厂
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
        // 创建新的事务
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 通过configuration创建Executor
            Executor executor = this.configuration.newExecutor(tx, execType);
        // 通过Configuration和Executor创建sqlSession
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var12) {
            this.closeTransaction(tx);
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
        } finally {
            ErrorContext.instance().reset();
        }

        return var8;
    }

创建mapper代理

第<4>步返回了一个mapper对象,但不是实际的mapper,而是mapper的代理对象,我们进入方法最底层: 我们发现会从关于mapper代理工厂的map集合中,通过mapper类型获取代理工厂,并且通过代理工厂创建代理对象,点进去查看: mapper代理对象的创建是基于JDK动态代理实现的,通过Proxy的newProxyInstance方法创建: 而创建代理的方法第三个参数是InvocationHandler,那么mapperProxy一定实现了InvocationHandler这个类,并且当我们调用代理对象的方法时,代理类的方法会中转到InvocationHandler对象的invoke方法,然后就可以在这个方法中对原方法做出一些增强,具体可参考:代理模式:静态代理和动态代理(JDK动态代理原理)

执行mapper方法

我们来查看使用代理模式做了哪些增强: 在这个方法中,首先判断这个类是不是接口,由于JDK动态代理生成的代理类需要继承Proxy类,又需要实现或继承被代理类来获取方法信息,但是java是多实现单继承,因此这个类必须是接口,然后判断是不是默认方法。 查看第一个方法: 发现是从缓存中获取一个MapperMethod对象,如果不存在进行创建,创建时可以发现闯入了mapper接口、调用的方法以及sqlsession的configuration对象,并且执行方法时也调用了其execute方法,我们看一下MapperMethod到底是什么:

其存在两个对象,分别是sql命令和方法签名,sql命令包括sql的类型和statement的id,方法签名包括方法的返回类型以及一些信息。 查看execute方法 首先会根据sql的类型,来选择执行方法,然后将参数装换为sql可以识别的参数,然后再次交给sqlsession来执行方法,我们随机进入一个执行方法查看:

可以发现会通过Configuration创建MappedStatement,然后由Executor来执行相关方法

总结:MyBatis执行一条sql语句的流程

加载配置文件:通过类加载器ClassLoaderWrapper以及一些其他的类加载器加载mybatis配置文件获得输入流。

SqlsessionFactory的创建:通过XmlConfigBuilder通过输入流解析配置文件的标签,来填充Configuration属性,在解析的mappers标签的过程中,遍历每一个mapper标签,通过mapper标签的属性解析相应的mapper,并通过XmlMapperBuilder对相应的mapper类的标签进行解析,在此期间,通过mapper的命名空间获取mapper类,并且创建mapper代理工厂,以mapper类为键,mapper代理工厂为值放入mapperRegistry的map集合中。

获取sqlsession:通过SqlsessionFactory的Configuration对象获取环境变量,通过环境变量获取事务工厂并且创建事务,然后通过事务和环境创建Executor对象,最后通过创建的Executor对象和事务来创建DefaultSqlsession并返回

获取代理对象:根据mapper类从关于mapper代理工厂的键值对中取出mapper代理工厂并且创建代理对象返回

执行相关方法:由于创建代理对象时传入了MapperProxy对象,其实现了InvocationHandler接口,因此调用代理相关方式时会进入MapperProxy的invoke方法,在该方法内首先会从缓存中获取MapperMethod对象,然后调用其Execute方法,根据sql的类型然后由Executor执行。

相关推荐
庞传奇17 分钟前
【LC】191. 位1的个数
java·数据结构·算法·leetcode
禁默1 小时前
深入浅出:Java 抽象类与接口
java·开发语言
小万编程2 小时前
【2025最新计算机毕业设计】基于SSM的医院挂号住院系统(高质量源码,提供文档,免费部署到本地)【提供源码+答辩PPT+文档+项目部署】
java·spring boot·毕业设计·计算机毕业设计·项目源码·毕设源码·java毕业设计
白宇横流学长2 小时前
基于Java的银行排号系统的设计与实现【源码+文档+部署讲解】
java·开发语言·数据库
123yhy传奇2 小时前
【学习总结|DAY027】JAVA操作数据库
java·数据库·spring boot·学习·mybatis
想要打 Acm 的小周同学呀2 小时前
亚信科技Java后端外包一面
java·求职·java后端
lishiming03086 小时前
TestEngine with ID ‘junit-jupiter‘ failed to discover tests 解决方法
java·junit·intellij-idea
HEU_firejef6 小时前
设计模式——工厂模式
java·开发语言·设计模式
Kobebryant-Manba6 小时前
单元测试学习2.0+修改私有属性
java·单元测试·log4j
fajianchen6 小时前
应用架构模式
java·开发语言