Mybatis是我们在项目开发中用的比较多的一个ORM框架,Mybatis通过xml配置的方式可以动态的编写我们的sql,那Mybatis是如何去解析的呢
Mybatis解析配置文件
我们在使用Mybatis的时候通常需要去配置一些XML文件,Mybatis通过解析XML文件来获取数据库的连接数据以及sql,我们来看下具体是如何解析的
我们在使用Mybatis来操作数据库的时候通常需要SqlSessionFactory来进行操作,我们先来看下具体的代码
package com.mybatisdemo;
//import com.mybatisdemo.entity.Account;
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.Reader;
public class MybatisDemo {
public static void main(String[] args) {
String resource = "mybatis-config.xml";
Reader reader;
try {
//将XML配置文件构建为Configuration配置类
reader = Resources.getResourceAsReader(resource);
// 通过加载配置文件流构建一个SqlSessionFactory DefaultSqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
// 数据源 执行器 DefaultSqlSession
SqlSession session = sqlMapper.openSession();
try {
// 执行查询 底层执行jdbc
Account account = (Account)session.selectOne("com.mybatisdemo.mapper.AccountMapper.selectById", 1);
System.out.println(account);
session.commit();
} catch (Exception e) {
e.printStackTrace();
}finally {
session.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上面我们通过构建一个Reader对象然后将Mybatis配置文件传入进去,通过SqlSessionFactoryBuilder来构建一个SqlSessionFactory
那么它是通过build()方法来进行构建的,我们可以直接进入到这个方法里看
在这个方法里面可以看到它直接创建了一个XMLConfigBuilder对象,那么这个对象就是用来解析我们的Mybatis配置文件的
这个对象是继承了BaseBuilder,那么除了这个对象是继承了BaseBuilder之外,还有很多的对象继承了这个BaseBuilder对象,他们分别处理不同的业务
我们可以看下都有哪些对象继承了它
这几个对象分工很明确,我们可以看看这几个类具体是处理什么业务的
继承类 | 作用 |
---|---|
XMLMapperBuilder | 主要就是用来解析写sql的XML文件 |
SqlSourceBuilder | 主要就是用来解析sql语句的 |
XMLConfigBuilder | 主要就是用来解析Mybatis全局配置文件的 |
XMLStatementBuilder | 主要就是解析映射文件的条件节点 |
XMLScriptBuilder | 用来解析脚本的 |
MapperBuilderAssistant | 也是用来解析Mapper文件的 |
那么XMLConfigBuilder在构建的时候就初始化了一个XPathParser对象
这个对象就是用来解析XML文件的,这个是Mybatis专门提供的,其实就是对XPath的一个封装
在对象创建完成之后,就调用parse()方法开始解析了
在这个解析方法里面就会开始解析Mybatis全局配置的节点
它这里调用parser.evalNode()方法就是获取到了Mybatis全局配置文件的节点
也就是这个节点
那我们可以继续进入到parseConfiguration()方法里
在这个方法里可以看到它开始解析这里面的各个节点
在这里我们需要看下解析Mapper文件的这个方法
我们可以进入到这个方法里
这个方法里会去循环mapper节点(Mybatis全局配置文件里的mapper节点就是配置的写Mapper文件的路径)
在这个循环里会根据路径来获取Mapper文件
这个里的Mapper文件通常就是只我们的那个写接口的java文件 ,Mapper映射文件就是只写Sql的XML文件
我们可以继续进入到addMappers()方法里,最终会来到MapperRegistry这个类里的addMappers()方法
这个方法里会根据路径获取所有的Mapper文件的Class,然后再循环Class对象并添加到Map里去
存放完了之后就会去解析Mapper映射文件
我们可以进入到这个解析方法里来
这个方法里调用了loadXmlResource()方法
这个方法里就是根据之前解析的Mapper文件的路径和名称来获取Mapper映射文件
它会根据Mapper文件的后缀替换成xml的,也就是我们再写xml映射文件的时候为什么要和接口的路径保持一致了
在获取到Mapper映射文件之后就会,就会把文件交由XMLMapperBuilder对象来解析了
我们可以进入到parse()方法里
在这个方法里它会解析Mapper映射文件的各个节点
然后在解析的过程中,我们来看下是如何解析我们的select、insert、update、delete这个节点的
我们可以进入到这个方法里可以看到调用了buildStatementFromContext()方法来解析这些节点的
最后它会将这些节点解析成XMLStatementBuilder类
在这里可以看到解析的时候是直接调用了parseStatementNode()方法
我们可以继续进入到这个方法里
在这个方法里面它会将映射文件的每个节点全部解析都这个SqlSource里,我们可以看看这个SqlSource对象
这个SqlSource对象有多个对象继承,我们可以直接选择DynamicSqlSource这个对象
进入到这个对象里可以看到对象里有个SqlNode
它会将每个XML里的节点解析成一个SqlNode对象
rootSqlNode就是最外层的节点,如果里面还有其他的节点的话,比如if、where等节点,它会把里面的节点也解析成一个SqlNode,然后由根节点的SqlNode指向子节点的SqlNode
这个SqlNode有多个类实现,我们可以看下比较重要的几个
类名 | 作用 |
---|---|
MixedSqlNode | 这个对象里面有个集合,主要是用来存放各个sqlNode节点的 |
WhereSqlNode | 主要封装where节点 |
IfSqlNode | 主要封装if节点 |
TextSqlNode | 主要是用来封装不带任何拼接条件的sql语句 |
ForEachSqlNode | 主要是用来封装forEach节点 |
TrimSqlNode | 主要是用来封装trim节点 |
ChooseSqlNode | 主要是用来封装choose节点 |
就像是一个链表一样将各个节点串起来
再把解析后的各个节点封装到MappedStatement对象里,然后添加到configuration对象里
到这里整个配置文件就就解析完了
Mybatis操作数据解析
在调用SqlSession里的方法的时候,实际上它底层是直接通过Executor对象来调用的
这个Executor对象我们称为执行器
Mybatis为我们提供了五个执行器
类名 | 作用 |
---|---|
SimpleExecutor | 这个主要是用于执行单条数据操作 |
ReuseExecutor | 主要是用于不会重复解析sql |
BatchExecutor | 主要是用于执行批量数据操作 |
BaseExecutor | 上面的三个类继承了BaseExecutor,这个执行器主要用于一级缓存 |
CachingExecutor | 这个主要是用于Mybatis的二级缓存 |
在我们通过SqlSessionFactory调用openSession()方法的时候,其实这个方法里就给我们初始化了我们所需要的执行器
我们可以进入到这个方法里看看
在我们调用查询方法
底层就会直接调用执行器的查询方法
在调用这个查询方法之前,它首先会去根据id获取对应的MapperStatement,上面我就讲过了,映射文件解析的节点信息会直接封装到MapperStatement里面
我们可以进入到这个query()方法里看看
这个query()方法有多个类实现了
我们直接进入到CachingExecutor里面,这个类我在上面就说了主要用来处理二级缓存的,Mybatis在调用查询的时候会从二级缓存里查询
进入到这个方法里后,我们可以看到它首先获取解析我们的sql语句
我们可以进入到这个getBoundSql()方法里看看
这个方法里就通过SqlSource对象来获取对应的sqlNode,在上面就说了,Mybatis会把映射文件解析封装到一个SqlSource对象里
在这个方法里会去调用每个sqlNode节点的apply()方法
通过一个一个节点调用,然后条件判断,将sql拼接起来
那么拼接完之后还要去做参数的替换
这里调用了parsr()方法来进行参数替换
解析完sql语句之后会去创建一个缓存的key
创建完之后就直接调用了query()方法来查询了
我们可以进入到这个方法里看看
这个方法里它首先会从二级缓存里查询
如果二级缓存里没有就会直接从走sql查询了
queryFromDatabase()这个方法就是直接从数据库里查询数据
我们可以继续进入到这个方法里看看
这个方法里在调用了doQuery()方法,这个方法有4个执行器实现了,我们直接进入到SimpleExecutor执行器里
在这个方法里面调用了prepareStatement()方法
那么这个方法里就是去获取连接然后获取Statement对象,最后设置参数
最后调用query()方法去执行sql
我们可以进入到这个query()方法里
最后将查询出来的结果集进行封装
Mybatis插件详解
Mybatis插件主要是为了对Myatis四大核心对象进行增强—Executor、ParameterHandler、ResultSetHandler、StatementHandler
那么我们在配置插件的时候只需要在Mybatis文件里添加一个plugin节点就行了
<plugins>
<plugin interceptor="com.mybatisdemo.plugins.ExamplePlugin" ></plugin>
</plugins>
Mybatis会将这个插件添加到一个执行链里
我们可以在Configuration对象里看到这个执行链对象
那么这个插件在初始化执行器对象的时候就会为其进行增强
我们可以进入到这个puginAll()方法里来
这个方法里主要是调用了plugin()方法,他就会直接调用我们自己重写的这个plugin()方法
这里我重写的这个方法里没有写任何的东西
这里就调用了wrap()方法
我们可以进入到这个wrap()方法里看看
这里其实就是给我们的执行器做了动态代理
那么在实际调用的时候就会来到这个Plugin对象里的invoke方法
在这个invoke方法里,它会直接调用intercept()方法,最终就会来到我们自己重写的那个方法里面