mybatis源码分析

mybatis执行步骤

  如果你认证看完这个文章,那么你一定会有些收获

mybatis的三大操作

Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,加载驱动、创建连接、创建statement等繁杂的过程,开发者开发时只需要关注如何编写SQL语句,可以严格控制sql执行性能,灵活度高。

作为一个半ORM框架,MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。

称Mybatis是半自动ORM映射工具,是因为在查询关联对象或关联集合对象时,需要手动编写sql来完成。不像Hibernate这种全自动ORM映射工具,Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取。

通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。
由于MyBatis专注于SQL本身,灵活度高,所以比较适合对性能的要求很高,或者需求变化较多的项目,如互联网项目。

前景分析

本次源码分析共用到了如下文件
首先是数据库层在这里插入图片描述
这是我之前使用消息队列的时候构建的一个存储信息的实体
下面是这个实体对应的entity类

package com.entity;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;
import lombok.Data;
import java.io.Serializable;
@TableName("record")
@Data
public class RecordEntity implements Serializable {
    private static final long serialVersionUID = 1L
    @TableId(type = IdType.AUTO)
    private  long id ;
    private  String Record;
}

上述代码是我在做springcloud的时候创建的一个entity类,里面与数据库表的映射已经对应好,在这里用来存储mybatis的resulttype进行返回查询的结果

下面是mybatis配置文件mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/school_blog?useUnicode=true&amp;serverTimezone=UTC" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="Mapper/RecordMapper.xml"></mapper>
    </mappers>
</configuration>

在这里我们指定了我自己的映射关系Mapper/RecordMapper.xml,对应的执行操作,文件如下

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.entity.RecordEntity">
    <!-- 映射 selectOne 方法,查询一条记录 -->
    <select id="selectOne" resultType="com.entity.RecordEntity">
        SELECT id, record
        FROM record
        WHERE id = #{id}
    </select>
</mapper>

这这里我们指定了与recordentity的位置与映射关系,并且定义了一个方法,selectone方法,并写出了对应的语句
接下来就是测试文件了test.java,如下所示

package clg;
import com.entity.RecordEntity;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class test {
    public static  void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession();
        RecordEntity record = session.selectOne("com.entity.RecordEntity.selectOne", 12925);
        System.out.println(record);
    }
}

接下来我们按照这个test文件分析mybatis源码运行的全过程

第一步,获取数据源

对程序进行调试,运行调试程序
在这里插入图片描述对SqlSessionFactory添加断点,可以看到跳转到sqlsessionfactory的构造方法

进入到builder方法之后,可以看到如下代码
在这里插入图片描述
这是第一个比较核心的代码如下,开始读取xml配置文件,继续跳转到这段代码下面

 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
 return this.bulid(parser.parse());

接下来我们观看这个parse()方法
在这里插入图片描述
可以看到,当程序开始时,parsed的值为false,只有当被加载的时候,才会变成true,这样做检查是否已经解析过配置,如果没有,则解析MyBatis配置文件并构建Configuration对象,

随着代码的步入,接下来进入到下面的parse configuration()方法

在这里插入图片描述
可以看到这里将root(也就是上文parser代表的XML文件中的 < configuration> 元素,然后将这个元素作为根元素传递给 parseConfiguration() 方法),我们在这里可以如下图所示执行一下这个root,看看会发生什么
在这里插入图片描述经过执行之后,图片如下在这里插入图片描述我们把result复制一下,可以得到如下xml文件

<configuration>
    <settings>
        <setting name="logImpl" value="SLF4J"/>        
    </settings>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>            
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>                
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/school_blog?useUnicode=true&serverTimezone=UTC"/>                
                <property name="username" value="root"/>                
                <property name="password" value="root"/>                
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="Mapper/RecordMapper.xml"/>        
    </mappers>
</configuration>

由此,这就是mybatis-config文件,说明mybatis-config文件已经被读入到
XMLConfigBuilder并且返回了
接下来就是对全局配置进行解析了,对解析全局配置的方法添加断点,图片如下,运行代码在这里插入图片描述进入如下代码,同样运行context内容
在这里插入图片描述
在这里插入图片描述可以得到如下xml

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>        
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.cj.jdbc.Driver"/>            
            <property name="url" value="jdbc:mysql://127.0.0.1:3306/school_blog?useUnicode=true&serverTimezone=UTC"/>            
            <property name="username" value="root"/>            
            <property name="password" value="root"/>            
        </dataSource>
    </environment>
</environments>

可以看到,已经获取到核心配置内容了
会解析datasource,对datasource添加断点,图片如下所示
在这里插入图片描述点击运行,跳转到datasourceelement方法
在这里插入图片描述同样进入到resolveclass方法添加断点,进入到resolveclass的resolvealias方法
在这里插入图片描述
在这里插入图片描述
进入到resolvealias方法
在这里插入图片描述
可以看到第一步,把大写替换为小写
通过key=pooled,找到sessionfactory,并且作为value返回

在这里插入图片描述我们可以看到,DataSourceFactory将返回的进行instance,如下图所示
在这里插入图片描述最后返回这个factory
在这里插入图片描述在下面通过返回的datasource获取数据库源,通过getdatasource获取数据库源
在这里插入图片描述

我们观察datasource
在这里插入图片描述可以发现,至此,数据库四大元素已经全部被加载进来
总结
按照从上到下的顺序分为一下几步

org.apache.ibatis.session.SqlSessionFactoryBuilder.build()
org.apache.ibatis.builder.xml.XMLConfigBuilder.parse()
org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration()
org.apache.ibatis.builder.xml.XMLConfigBuilder.environmentsElement()
org.apache.ibatis.builder.xml.XMLConfigBuilder.dataSourceElement()
org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory.getDataSource()

在得到datasource之后,会调用setencironment标签,如下图所示在这里插入图片描述
在这里执行setenvironment方法,这是最最最重要的方法
在这里插入图片描述

点击到environment里面,直接跳转到mybitatis下
在这里插入图片描述可以看到,分别把获取到的datasource存储到mybatis的核心配置类
至此,所有获取数据库源的步骤已经完毕

第二步,获取语句

当拿到数据库源之后,会解析执行语句,拿到执行语句
由上面的xml解析可知,只要谁拿到解析select的标签,谁就拿到了sql语句
在这里插入图片描述这个文件是在mybatis-config文件下< mappers>标签包含,所以要找到解析这个标签的代码
在这里插入图片描述在xmlconfigbuilder下,我们可以看到这行代码在读取root节点代表的mybatis-config。xml下的< mappers>标签,下面继续运行,同样利用xmlconfigbuilder的pares方法去解析标签(在获取sessionfactory时,解析的是keys是pooled,在这里解析的顺序与上面的顺序一样),我们在这里添加断点
在这里插入图片描述我们直接看mapperelement方法,看看传递的与执行的结果
在这里插入图片描述
进入到mapperelement,我们首先看看这个parent到底是什么
在这里插入图片描述我们把他的value复制过来

<mappers>
    <mapper resource="Mapper/RecordMapper.xml"/>    
</mappers>

接下来,继续进行
在这里插入图片描述可以看到,mybatis解析包的时候的顺序是package->name->resource->url
但是这里可以吐槽一下
在这里插入图片描述用了太多的ifelse,可以用策略模式指定代码
下面继续运行,如下图所示
在这里插入图片描述在这里插入图片描述之后运行到这里,同样的,我们看看这个context到底有什么

在这里插入图片描述
查看value,可以看到

<mapper namespace="clg.entity.RecordEntity">
    <select resultType="clg.entity.RecordEntity" id="selectOne">
        SELECT id, record
        FROM record
        WHERE id = #{id}
    </select>
</mapper>

可以看到,到这里,mybatis-config下指定的一个mapper已经被加载进来

接下来我们可以看到,mybatis这个类对自定义mapper下的每个标签都进行了解析
在这里插入图片描述在这里我们直接看对select|insert|update|delete的解析,添加断点并运行
在这里插入图片描述接着下一步运行,我们可以看到这里用了list集合存储了XNode,(因为可能不止一条语句)在这里插入图片描述语句接下来执行,String第二个参数以null的形式传入到如下方法
在这里插入图片描述我们观看这个try里面的方法,添加断点运行(这是获取语句的核心方法)
在这里插入图片描述我们看到,这个语句全是赋值给局部变量的过程, 其值全是从conterxt中读取,这表示将这个mapper进行解析,赋值(后面的attritube都是在标签里一一对应的,按照attritube进行获取)
我们看到这个函数最后一行,有一个addmapperstatement方法,这个就是将获取到的所有局部变量返回
在这里插入图片描述我们仔细观看他

this.builderAssistant.addMappedStatement(id, 
sqlSource, statementType,
 sqlCommandType, fetchSize,
  timeout, parameterMap, 
  parameterTypeClass,
   resultMap, resultTypeClass,
    resultSetTypeEnum, flushCache,
     useCache, resultOrdered,
      (KeyGenerator)keyGenerator,
       keyProperty, keyColumn, 
       databaseId, langDriver, resultSets);
      

好家伙,这直接将所有东西都传递过去了

最后将这个类给到全局配置文件config
在这里插入图片描述
由此,所有的语句,状态等等信息,都给了mybatis核心上下文文件
主要的步骤为

1. apache.ibatis.session.SqlSessionFactoryBuilder
2. org.apache.ibatis.builder.xml.XMLConfigBuilder
3. org.apache.ibatis.builder.xml.XMLConfigBuilder.mapperElement
4. org.apache.ibatis.builder.xml.XMLMapperBuilder
5. org.apache.ibatis.builder.xml.XMLMapperBuilder.buildStatementFromContext
6. org.apache.ibatis.builder.xml.XMLStatementBuilder
7. org.apache.ibatis.builder.MapperBuilderAssistant
8. org.apache.ibatis.session.Configuration
9. org.apache.ibatis.session.Configuration.addMappedStatement

第三步,执行操作

准备操作已经全部做完了,我们回到主测试文件中,继续测试分析主函数
在这里插入图片描述一个sqlsessionfactorybuilder就已经将所有的数据源,语句拿了回来,之后我们就要执行语句了
首先看,他会执行opensession的方法,我们跳转
在这里插入图片描述进入下一步
在这里插入图片描述首先在代码中,先确定执行器类型,我们可以看到调用了newexecutor来构建一个执行器,我们切入到这个方法
在这里插入图片描述
我们可以看到,mybatis分别给出了三个执行器

batch 批量执行器
resue 复用执行器
simple 默认执行器 
这三个执行器分别有定义的类,都继承了父执行器(executor)

在这里我们可以看到,mybatis对缓存的加载策略,默认一级缓存开启
在这里插入图片描述至此,opensession已经全部执行完毕,我们可以看到,他并没做什么,只是确定了mybatis的执行器
接下来分析selectone方法,继续执行下一步,程序跳转到selectlist方法
在这里插入图片描述我们进入到selectlist方法,可以看到他是向我们之前的mappedstatement(包含一大堆参数的)获取到statement参数,将他作为参数执行query方法,那么query方法都干了什么呢,我们继续分析在这里插入图片描述到query函数里,如下图所示,我们查看boundsql是什么东西
在这里插入图片描述

在这里插入图片描述我们可以看到,sql已经被拿到了,并且创建了一级缓存但是好像缺点东西

我们继续分析他下面调用的query方法,可以看到跳转到这里 在这里插入图片描述记下来他会执行delegate。quary方法,调用simpleexcutor的query方法,我们继续执行
在这里插入图片描述代码跳转到这里,在这里插入图片描述这里是mybatis最核心的代码
首先将本地缓存传递判断,如果为空说明本地缓存未命中,执行quary方法,否则直接命中本地缓存返回
在这里插入图片描述由于是第一次访问,那么他会进行查询数据库(else)操作
在这里插入图片描述当执行查询方法时,首先把一级缓存放入到本地缓存中,这就是一级缓存的使用方法
在这里插入图片描述之后进行最关键的doquery方法(执行语句)
在这里插入图片描述最后的执行方法还是prepare statement方法(注意,在这里stmt里面就同时进行了connect的获取,如下图所示
在这里插入图片描述从statement中获取connecton(这个connecton就是rt.jar的包,最基础的包),然后
,至此,以及执行了parastatement方法
最后一步就是获取resultset,代码继续运行到这里,可以看到返回了一个resultsethandler的handleresultsets方法

在这里插入图片描述我们观看最主要的getfirstresultset方法
在这里插入图片描述进入这个方法,可以看到,像之前servlet一样获取到了resultset而已在这里插入图片描述拿到了result之后,返回,就执行完了所有的查询操作
谢谢您的观看

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值