1.Mybatis的使用

public static void main(String[] args) throws IOException {
//1.获取配置文件流
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//2.构建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//4.获取mapper
UnitMapper mapper = sqlSession.getMapper(UnitMapper.class);
//5.执行增删改查
Unitinfo_source unitinfo_source = mapper.selectById(1);
System.out.println(unitinfo_source);
}

这是我们在简单使用mybatis时的代码,并未整合Spring,首先来看下Mybatis具体的执行流程是什么。后续我们再看整合Spring后Mybatis做了什么处理。以及Springboot中对Mybatis做了怎样的处理。

2.执行流程

第一步:通过Resources获取mybatis配置文件的输入流

Resources是Mybatis下的一个类,getResourceAsStream读取到mybatis的配置文件,以流的形式加载进来

第二步:构建SqlSessionFactory

 1     public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
2 SqlSessionFactory var5;
3 try {
4 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
5 var5 = this.build(parser.parse());
6 } catch (Exception var14) {
7 throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
8 } finally {
9 ErrorContext.instance().reset();
10
11 try {
12 inputStream.close();
13 } catch (IOException var13) {
14 }
15
16 }
17
18 return var5;
19 }

调用了XMLConfigBuilder的parse()方法,具体看下这个方法是干什么的

 1     public Configuration parse() {
2 if (this.parsed) {
3 throw new BuilderException("Each XMLConfigBuilder can only be used once.");
4 } else {
5 this.parsed = true;
6 this.parseConfiguration(this.parser.evalNode("/configuration"));
7 return this.configuration;
8 }
9 }
10
11 private void parseConfiguration(XNode root) {
12 try {
13 this.propertiesElement(root.evalNode("properties"));
14 Properties settings = this.settingsAsProperties(root.evalNode("settings"));
15 this.loadCustomVfs(settings);
16 this.loadCustomLogImpl(settings);
17 this.typeAliasesElement(root.evalNode("typeAliases"));
18 this.pluginElement(root.evalNode("plugins"));
19 this.objectFactoryElement(root.evalNode("objectFactory"));
20 this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
21 this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
22 this.settingsElement(settings);
23 this.environmentsElement(root.evalNode("environments"));
24 this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
25 this.typeHandlerElement(root.evalNode("typeHandlers"));
26 this.mapperElement(root.evalNode("mappers"));
27 } catch (Exception var3) {
28 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
29 }
30 }

可以看到XMLConfigBuilder的parse()方法来解析配置文件的配置信息,可以看到,解析properties标签,settings,typeAliases别名等等一些信息,并返回一个Configuration;后续我们会看到,Configuration是Mybatis很重要的一个核心类,包括了很多执行过程中的上下文信息,我们这里的配置信息被加载到了这个类中

1     public SqlSessionFactory build(Configuration config) {
2 return new DefaultSqlSessionFactory(config);
3 }

通过调用SqlSessionFactory的build方法,返回了DefaultSqlSessionFactory对象,DefaultSqlSessionFactory是SqlSessionFactory的一个实现类

第三步:创建SqlSession

1     public SqlSession openSession() {
2 return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
3 }
 1     private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
2 Transaction tx = null;
3
4 DefaultSqlSession var8;
5 try {
6 Environment environment = this.configuration.getEnvironment();
7 TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
8 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
9 Executor executor = this.configuration.newExecutor(tx, execType);
10 var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
11 } catch (Exception var12) {
12 this.closeTransaction(tx);
13 throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
14 } finally {
15 ErrorContext.instance().reset();
16 }
17
18 return var8;
19 }

首先获取到刚才从配置文件解析到事务,TransactionFactory创建出Transaction, 我们知道sql执行一般都会用到事务提交、回滚等操作。

创建Executor对象,executor是mybatis的核心执行器,用于执行增删改查操作。一二级缓存都在执行器中进行处理。

最后得到了SqlSession对象了,DefaultSqlSession是SqlSession的一个实现。

第四步:获取Mapper

调用SqlSession的getMapper方法

1     public <T> T getMapper(Class<T> type) {
2 return this.configuration.getMapper(type, this);
3 }
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}

然后可以发现调用了Configuration中的getMapper方法,再调用MapperRegistry(注册mapper的类)获取Mapper

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}

可以看到调用了mapperProxyFactory的newInstance方法

1     protected T newInstance(MapperProxy<T> mapperProxy) {
2 return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
3 }
4
5 public T newInstance(SqlSession sqlSession) {
6 MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
7 return this.newInstance(mapperProxy);
8 }

可以看到我们在SqlSession调用getMapper时,实际上是通过jdk的动态代理来实现的,通过Proxy.newProxyInstance来创建mapper代理对象

第五步:执行mapper的增删改查方法

通过第四步中,我们知道了getMapper获取到了代理对象,也就是说Proxy.newProxyInstance最后一个参数mapperProxy对象类必定实现了InvocationHandler的invoke方法,

 1     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
2 try {
3 if (Object.class.equals(method.getDeclaringClass())) {
4 return method.invoke(this, args);
5 }
6
7 if (method.isDefault()) {
8 if (privateLookupInMethod == null) {
9 return this.invokeDefaultMethodJava8(proxy, method, args);
10 }
11
12 return this.invokeDefaultMethodJava9(proxy, method, args);
13 }
14 } catch (Throwable var5) {
15 throw ExceptionUtil.unwrapThrowable(var5);
16 }
17
18 MapperMethod mapperMethod = this.cachedMapperMethod(method);
19 return mapperMethod.execute(this.sqlSession, args);
20 }

invoke方法中首先判断了是否是普通类或者判断是否是默认方法,如果是普通类,执行其默认方法;如果是默认defaut方法,执行相应的java8或java9相应方法(这个没具体了解,如果感兴趣可以自己深入了解);咱们主要是看19行, mapperMethod的excute方法

 1     public Object execute(SqlSession sqlSession, Object[] args) {
2 Object result;
3 Object param;
4 switch(this.command.getType()) {
5 case INSERT:
6 param = this.method.convertArgsToSqlCommandParam(args);
7 result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
8 break;
9 case UPDATE:
10 param = this.method.convertArgsToSqlCommandParam(args);
11 result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
12 break;
13 case DELETE:
14 param = this.method.convertArgsToSqlCommandParam(args);
15 result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
16 break;
17 case SELECT:
18 if (this.method.returnsVoid() && this.method.hasResultHandler()) {
19 this.executeWithResultHandler(sqlSession, args);
20 result = null;
21 } else if (this.method.returnsMany()) {
22 result = this.executeForMany(sqlSession, args);
23 } else if (this.method.returnsMap()) {
24 result = this.executeForMap(sqlSession, args);
25 } else if (this.method.returnsCursor()) {
26 result = this.executeForCursor(sqlSession, args);
27 } else {
28 param = this.method.convertArgsToSqlCommandParam(args);
29 result = sqlSession.selectOne(this.command.getName(), param);
30 if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
31 result = Optional.ofNullable(result);
32 }
33 }
34 break;
35 case FLUSH:
36 result = sqlSession.flushStatements();
37 break;
38 default:
39 throw new BindingException("Unknown execution method for: " + this.command.getName());
40 }
41
42 if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
43 throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
44 } else {
45 return result;
46 }
47 }

这个就是我们核心执行增删改查的方法,通过判断 sql的执行类型,执行相应的方法,返回相应的结果

也许有人有疑问,是从哪里解析到sql语句的呢,其实在解析配置文件的时候就已经将sql解析到Configuration类对象中了,我们看下源码

 1     private void parseConfiguration(XNode root) {
2 try {
3 this.propertiesElement(root.evalNode("properties"));
4 Properties settings = this.settingsAsProperties(root.evalNode("settings"));
5 this.loadCustomVfs(settings);
6 this.loadCustomLogImpl(settings);
7 this.typeAliasesElement(root.evalNode("typeAliases"));
8 this.pluginElement(root.evalNode("plugins"));
9 this.objectFactoryElement(root.evalNode("objectFactory"));
10 this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
11 this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
12 this.settingsElement(settings);
13 this.environmentsElement(root.evalNode("environments"));
14 this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
15 this.typeHandlerElement(root.evalNode("typeHandlers"));
16 this.mapperElement(root.evalNode("mappers"));
17 } catch (Exception var3) {
18 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
19 }
20 }

我们看到在16行,有this.mapperElement(root.evalNode("mappers"))来解析mapper节点信息

 1     private void mapperElement(XNode parent) throws Exception {
2 if (parent != null) {
3 Iterator var2 = parent.getChildren().iterator();
4
5 while(true) {
6 while(var2.hasNext()) {
7 XNode child = (XNode)var2.next();
8 String resource;
9 if ("package".equals(child.getName())) {
10 resource = child.getStringAttribute("name");
11 this.configuration.addMappers(resource);
12 } else {
13 resource = child.getStringAttribute("resource");
14 String url = child.getStringAttribute("url");
15 String mapperClass = child.getStringAttribute("class");
16 XMLMapperBuilder mapperParser;
17 InputStream inputStream;
18 if (resource != null && url == null && mapperClass == null) {
19 ErrorContext.instance().resource(resource);
20 inputStream = Resources.getResourceAsStream(resource);
21 mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
22 mapperParser.parse();
23 } else if (resource == null && url != null && mapperClass == null) {
24 ErrorContext.instance().resource(url);
25 inputStream = Resources.getUrlAsStream(url);
26 mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
27 mapperParser.parse();
28 } else {
29 if (resource != null || url != null || mapperClass == null) {
30 throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
31 }
32
33 Class<?> mapperInterface = Resources.classForName(mapperClass);
34 this.configuration.addMapper(mapperInterface);
35 }
36 }
37 }
38
39 return;
40 }
41 }
42 }

通过源码我们可以看到在16行以下,XMLBuilderMapper的parse方法解析mapper的xml文件,在这个方法中会将sql解析。

其实在执行器执行的时候会根据解析到的id与sql的映射去拿到执行的SQL,再交由底层jdbc来执行相应的sql语句

Mybatis源码系列 执行流程(一)的更多相关文章

  1. mybatis源码阅读-执行一个sql的流程(九)

    图解 图片来源:https://my.oschina.net/zudajun/blog/670373 Mapper接口调用原理 我们整合成Spring  直接使用Mapper就能执行对应的sql 表现 ...

  2. MyBatis源码分析(五):MyBatis Cache分析

    一.Mybatis缓存介绍 在Mybatis中,它提供了一级缓存和二级缓存,默认的情况下只开启一级缓存,所以默认情况下是开启了缓存的,除非明确指定不开缓存功能.使用缓存的目的就是把数据保存在内存中,是 ...

  3. drf源码系列

    过滤器 对查询出来的数据进行筛选可写可不写 from rest_framework.filters import BaseFilterBackend 源码 ''' def filter_queryse ...

  4. MyBatis源码分析-SQL语句执行的完整流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  5. mybatis源码专题(2)--------一起来看下使用mybatis框架的insert语句的源码执行流程吧

    本文是作者原创,版权归作者所有.若要转载,请注明出处.本文以简单的insert语句为例 1.mybatis的底层是jdbc操作,我们先来回顾一下insert语句的执行流程,如下 执行完后,我们看下数据 ...

  6. MyBatis 源码篇-SQL 执行的流程

    本章通过一个简单的例子,来了解 MyBatis 执行一条 SQL 语句的大致过程是怎样的. 案例代码如下所示: public class MybatisTest { @Test public void ...

  7. mybatis源码学习:插件定义+执行流程责任链

    目录 一.自定义插件流程 二.测试插件 三.源码分析 1.inteceptor在Configuration中的注册 2.基于责任链的设计模式 3.基于动态代理的plugin 4.拦截方法的interc ...

  8. MyBatis 源码分析系列文章合集

    1.简介 我从七月份开始阅读MyBatis源码,并在随后的40天内陆续更新了7篇文章.起初,我只是打算通过博客的形式进行分享.但在写作的过程中,发现要分析的代码太多,以至于文章篇幅特别大.在这7篇文章 ...

  9. MyBatis 源码分析 - SQL 的执行过程

    * 本文速览 本篇文章较为详细的介绍了 MyBatis 执行 SQL 的过程.该过程本身比较复杂,牵涉到的技术点比较多.包括但不限于 Mapper 接口代理类的生成.接口方法的解析.SQL 语句的解析 ...

随机推荐

  1. Django url中可以使用类视图.as_view()进行映射的原因

    说明:在练习天天生鲜项目时,对利用类视图去与正则匹配到的url做映射有点疑惑,经过查看他人博客以及自我分析算是整明白了,所以记录一下 参考:https://www.zmrenwu.com/post/5 ...

  2. 零基础学习java------day12------数组高级(选择排序,冒泡排序,二分查找),API(Arrays工具类,包装类,BigInteger等数据类型,Math包)

    0.数组高级 (1)选择排序 它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的起始位置 ...

  3. 关于C语言中不同类型数据进行计算 有符号和无符号数进行计算

    float是8个有效位, 做个试验: 输出如下: 上面说明了什么: 1, 18/2.2 是除不尽的, 因为是define,所以没有给ratio变量赋值类型,但是从sizeof输出的结果是8,所以系统默 ...

  4. gitlab基础命令之代码回滚

    #:gitlab状态 root@ubuntu:~# gitlab-ctl status run: alertmanager: (pid 13305) 215965s; run: log: (pid 1 ...

  5. @Conditional 注解,基于条件实例对象

    .catalogue-div { position: relative; background-color: rgba(255, 255, 255, 1); right: 0 } .catalogue ...

  6. 1.使用Lucene开发自己的搜索引擎--倒排索引基础知识

    1.单词--文档矩阵 单词-文档矩阵是表达两者之间所具有的一种包含关系的概念模型,图3-1展示了其含义.图3-1的每列代表一个文档,每行代表一个单词,打对勾的位置代表包含关系.

  7. MicroK8S 安装 修改IP 设置镜像加速 升级 卸载等

    系统要求: Ubuntu 20.04 LTS, 18.04 LTS or 16.04 LTS或其他支持snapd的操作系统 内存:4G+ 硬盘:20G+ 官方文档 安装microk8s sudo sn ...

  8. 安装MySQL5.7.26教程图解

    安装MySQL5.7.26教程图解 1.安装mysql所需的yum源 yum -y install gcc-c++ ncurses-devel cmake make perl gcc autoconf ...

  9. ciscn_2019_n_8 1

    拿到题目老样子先判断是多少位的程序 可以看到是32位的程序,然后再查看开启的保护 然后将程序放入ida进行汇编 先shift+f12查看程序是否有system和binsh 可以看到有system和bi ...

  10. 如何完成符合ISO 26262要求的基于模型设计(MBD)的测试

    背景介绍 随着汽车行业的迅速发展,汽车的复杂程度不断增加,越来越多的汽车电子控制系统具有与安全相关的功能,因此对ECU的安全要求也越来越高.复杂的软件功能,将会带来大量的软件风险问题,如何保证软件的安 ...