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&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之后,返回,就执行完了所有的查询操作
谢谢您的观看