MyBatis源码之MyBatis中SQL语句执行过程

mybatis,sql · 浏览次数 : 0

小编点评

**SQL执行流程图** ```mermaid graph LR A[SqlSession] --> B[selectList] B --> C[MappedStatement] C --> D[executor] D --> E[query] E --> F[DefaultResultSetHandler] F --> G[return result] A --> H[insert] H --> I[update] I --> J[delete] ``` **执行过程说明** 1. **selectList**方法从数据库中查询所有**Student**对象并将其存储在**studentList**中。 2. **MappedStatement**封装了 SQL 语句,并使用 **executor** 进行执行。 3. **executor** 执行 SQL 语句并封装查询结果。 4. **DefaultResultSetHandler** 处理查询结果并将其封装成 **Student** 对象。 5. **insert、update、delete** 方法调用了 **update** 方法,该方法最终执行了 **update** 操作。

正文

MyBatis源码之MyBatis中SQL语句执行过程

SQL执行入口

我们在使用MyBatis编程时有两种方式:

方式一代码如下:

SqlSession sqlSession = sqlSessionFactory.openSession();
List<Student> studentList = sqlSession.selectList("com.sjdwz.dao.StudentMapper.findAll");

方式二代码如下:

SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
List<Student> studentList = studentMapper.findAll();

方式一入口分析

方式一调用的是SqlSession接口的selectList方法:

截图

执行的是DefaultSqlSession中的实现

截图

经过多次重载后,调用的是此方法:

截图

MappedStatement封装的是我们的SQL语句;

方法内部执行的是executor的query方法。

方法二入口分析

方法二调用的是SqlSession接口的getMapper(Class type)方法:

截图

我们实际调用的是DefaultSqlSession实现类中的方法:

截图

截图

此方法内部又调用了

 configuration.<T>getMapper(type, this);

截图

最后调用了:

截图

return mapperProxyFactory.newInstance(sqlSession);

通过工厂创建了接口的代理对象。

Mapper接口动态代理

上面讲到会通过

 mapperProxyFactory.newInstance(sqlSession);

来创建动态代理类;

那创建动态代理类会执行哪些方法呢?

截图

可以看到有MapperProxy这个类,实际上都会执行MapperProxy类中的invoke方法。

invoke方法

invoke方法会先判断方法是不是Object的方法,如果是,就直接调用;否则会执行cachedInvoker()方法

cachedInvoker()的作用是获取缓存中MapperMethodInvoker,如果没有则创建一个,而MapperMethodInvoker内部封装了MethodHandler。

cachedInvoker

当cacheInvoker返回了PalinMethodInvoker实例之后,紧接着调用了这个实例的PlainMethodInvoker#invoke方法

PlainMethodInvoker

然后就调用了execute()方法

execute方法

里面调用了sqlSession的方法。

方法的调用关系如下:

MapperProxy#invoke()//代理对象执行的方法,代理后所有Mapper的方法调用时,都会调用这个invoke方法
	MapperProxy#cachedInvoker()//代理对象里的Method也是对象,为了避免频繁new对象,在这里给Method对象加缓存
		methodCache#computeIfAbsent()//从缓存中取Method对象,取不到创建之后加入缓存
        //PainMethodInvoker是MapperProxy的内部类
        PlainMethodInvoker#invoke()//执行方法,所有代理对象的方法都会执行同一个。代理对象本质是方法的拦截器!
			MapperMethod#execute()//执行方法,最终还是在调用SqlSession接口
                SqlSession#insert()
                SqlSession#update()
                SqlSession#delete()
                SqlSession#selectOne()
                SqlSession#selectList()

SQL执行流程

查询SQL执行流程

主要步骤:

  1. selectOne/selectList
  2. SQL获取
  3. 参数设置
  4. SQL执行
  5. 封装结果集

调用关系如下:

DefaultSqlSession#selectOne()//执行单记录
DefaultSqlSession#selectList()//查询列表
    CachingExecutor#query()
		MappedStatement#getBoundSql()//获取SQL语句
	CachingExecutor#query()//二级缓存查询
		CachingExecutor.delegate = SimplyExecutor//装饰设计模式:Caching的对SimpleExecutor查询加二级缓存装饰
	SimplyExecutor#query()
    	BaseExecutor#query()//子类执行查询直接调用父类方法,一级缓存
        BaseExecutor#queryFromDatabase()//缓存没有则去查询数据库,一级缓存
	SimplyExecutor#doQuery()//simple执行查询
		SimplyExecutor#prepareStatement()//准备查询语句
			RoutingStatementHandler#parameterize()//路由delegate=PreparedStatementHandler
				PreparedStatementHandler#parameterize()//设置查询参数
				DefaultParameterHandler#setParameters()//设置查询参数
			RoutingStatementHandler#query()//路由delegate=PreparedStatementHandler
				PreparedStatementHandler#query()//执行SQL,封装结果集
    				PreparedStatement#execute()//执行SQL查询操作
					DefaultResultSetHandler#handleResultSets()//封装返回值,将查询结果封装成Object对象

增删改SQL执行流程

主要步骤

  1. insert|update|delete方法分析
  2. SQL获取
  3. 参数设置
  4. SQL执行
  5. 封装结果集

MyBatis中增删改的代码如下:

//DefaultSqlSession
@Override
public int insert(...) {
return update(statement, parameter);
}
@Override
public int update(String statement) {
return update(statement, null);
}
@Override
public int delete(...) {
return update(....);
}

我们发现,增删改最后执行的都是update,这是因为insert、update、delete实际上都是对数据库中数据的改变

执行流程为:

DefaultSqlSession#update()
	CachingExecutor#update()//执行更新
		flushCacheIfRequired()//执行增删改前,清除对应二级缓存
		CachingExecutor.delegate = SimplyExecutor//装饰模式
	SimplyExecutor#update()//调用父类模板方法
		BaseExecutor#update()//执行更新
			BaseExecutor#clearLocalCache()//清除一级缓存,LocalCache
		    BaseExecutor#doUpdate()//调用子类方法
	SimplyExecutor#doUpdate()//simple执行更新
		SimplyExecutor#prepareStatement()//准备查询语句
			RoutingStatementHandler#parameterize()//delegate=PreparedStatementHandler
				PreparedStatementHandler#parameterize()//设置查询参数
				DefaultParameterHandler#setParameters()//设置查询参数
            RoutingStatementHandler#update()//delegate=PreparedStatementHandler
                PreparedStatementHandler#update()
                    PreparedStatement#execute()//执行SQL,完成增删改查操作
                    reparedStatement#getUpdateCount()//获取影响行数

图示

最后我们画出执行的流程图如下:

执行流程图

与MyBatis源码之MyBatis中SQL语句执行过程相似的内容:

MyBatis源码之MyBatis中SQL语句执行过程

本文是MyBatis源码之MyBatis中SQL语句执行过程,使用图文并茂的方式,讲解了SQL语句执行过程,调用了哪些方法,和这些方法是如何调用的。

Mybatis源码解析之执行SQL语句

作者:郑志杰 mybatis 操作数据库的过程 // 第一步:读取mybatis-config.xml配置文件 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); // 第二步:构建SqlSes

MyBatis源码之前言—JDBC编码存在的问题和Mybatis的介绍

本文是MyBatis源码之前言,主要介绍了JDBC编码存在的问题和介绍了Mybatis的配置、使用。 本文指出了JDBC编码操作数据库的问题,并给出了分析。

Mybatis中的设计模式

最近在看《通用源码阅读指导书:Mybatis源码详解》,这本书一一介绍了Mybatis中的各个包的功能,同时也涉及讲了一些阅读源码的技巧,还讲了一些源码中涉及的设计模式,这是本篇文章介绍的内容 在多说一点这本书,Mybatis是大部分Java开发者都熟悉的一个框架,通过这本书去学习如何阅读源码非常合

聊聊Mybatis的实现原理

### 使用示例 平时我们使用的一般是集成了Spring或是Spring Boot的Mybatis,封装了一层,看源码不直接;如下,看看原生的Mybatis使用示例 ![image](https://img2023.cnblogs.com/blog/971683/202305/971683-2023

聊聊Mybatis集成Spring的原理

一般都是研究框架源码,我为什么要反过来研究集成原理呢? 在我自己看来,集成虽然比较简单,但要求的细节比较多,需要掌握根本性的东西才能做到集成。 Mybatis集成Spring用到了FactoryBean以及BeanDefinition注册的原理,从这两个维度来实现集成,而我们单独学习Spring时,

MyBatis是如何初始化的?

摘要:我们知道MyBatis和数据库的交互有两种方式有Java API和Mapper接口两种,所以MyBatis的初始化必然也有两种;那么MyBatis是如何初始化的呢? 本文分享自华为云社区《MyBatis详解 - 初始化基本过程》,作者:龙哥手记 。 MyBatis初始化的方式及引入 MyBat

MyBatis ORA-01465: 无效的十六进制数字

MyBatis 在插入 Oralce 时报:ORA-01465: 无效的十六进制数字 解决方法: # 插入或更新时 String -> BLOB字段:RAWTOHEX(#{字段名}) String -> DATE:to_date(#{字段名},'yyyy-mm-dd hh24:mi:ss') # 查

把Mybatis Generator生成的代码加上想要的注释

作者:王建乐 1 前言 在日常开发工作中,我们经常用Mybatis Generator根据表结构生成对应的实体类和Mapper文件。但是Mybatis Generator默认生成的代码中,注释并不是我们想要的,所以一般在Generator配置文件中,会设置不自动生成注释。带来的问题就是自动生成代码之

MyBatis实现动态SQL更新

博主记得在一个周五快下班的下午,产品找到我(为什么总感觉周五快下班就来活 😂),跟我说有几个业务列表查询需要加上时间条件过滤数据,这个条件可能会变,不保证以后不修改,这个改动涉及到多个列表查询,于是博主思考了一会想了几种实现方案, 1. 最简单,直接将时间条件写死,由 Service 层传递给 D