文章目录
前言
在前几篇中,我们对MyBatis的源码进行了简单分析,大体了解了MyBatis的工作流程,以及MyBatis是如何去加载Mapper配置文件的,那么,我们又有了新的疑问,MyBatis是如何执行一条SQL的呢?本篇,我们就来解开这个问题。
MyBatis SQL执行流程
我们在使用MyBatis进行开发时,只需要编写一个Mapper interface
,关联好配置XML文件,使用Spring的AutoWired
注解进行一下注入,就可以使用了,那么这里有个问题,我们仅仅编写了一个interface
,并没有编写对应的实现类,MyBatis
是如何执行到配置文件中的SQL语句呢?
读MyBatis
的源代码,我们的核心原则一定是观其大略,不要深抠细节,MyBatis
作为一个极为成熟的框架,有大量的细枝末节的逻辑处理,如果太纠结于这些细节信息,会看的头昏脑胀,陷入泥潭之中,因此,我们主要是要理解MyBatis
的核心执行逻辑,了解其设计的思想精华所在,抓住主干,核心脉络,有的放矢。
MapperProxy代理模式
答案是使用代理类的方式进行实现,说到代理,你肯定并不陌生,在Spring中,大量的采用了代理的模式进行实现,MyBatis也同样采用了代理的方式,我们来通过Debug来验证一下,我们的说法是否是正确的:
我们使用AutoWired
注入一个Mapper interface
,如上图所示,我们可以看到,其交给Spring管理的实现类,是一个MapperProxy
类,进入MapperProxy
看看其实现。
MapperProxy
实现了InvocationHandler
接口,实现了invoke()
方法,我们对invoke()
方法进行简要的分析:
1、判断代理对象是否是方法所声明的Class,使用Spring的情况下,代理对象为MapperProxy
,因此条件不成立;
2、判断执行的方法是否是默认方法,默认方法(default method)是在Java 8以后加入的,是指在接口中定义的公共非抽象的方法,关于默认方法的解释,可以参考:https://www.geeksforgeeks.org/default-methods-java/
3、检查待执行的mapperMethod
是否已经缓存,如果已经缓存,那么直接取出缓存的对象,反之,进行缓存;
4、真正的SQL执行逻辑。
前三步都是进行的各种判断逻辑,第四步才是我们真正关心的核心逻辑,下面,我们来看一下SQL的执行逻辑。
SQL执行解析
MapperMethod
SQL的执行逻辑在MapperMethod
,我们进入MapperMethod
的execute()
方法:
在MapperMethod
的execute()
方法中,可以看到,会根据不同的方法类型,执行不同的逻辑操作,SQL的执行类型分为增删改查以及刷新,而方法的类型来源,则是根据MappedStatement
的信息进行得到的,MappedStatement
的初始化,则是在Spring容器的启动过程中完成的,也就是我们上篇中所讲到的Mapper关联SQL配置加载过程
,如果您没有读过上一篇,可以回顾:浅入浅出Mybatis:(三)Mapper关联SQL配置加载过程
DefaultSqlSession
我们回到主干,继续分析,这里,我们以INSERT
操作举例说明,SQL
的执行操作,最终是通过SqlSession
来进行执行的,而SqlSession
接口的默认实现类,为DefaultSqlSession
,我们继续跟进:
INSERT
方法最终调用的是update
方法的实现,在方法中,获取了MappedStatement
对象,其包含了SQL语句
的内容,继续跟进:
调用BaseExecutor
的update
方法,
SimpleExecutor
最终调用默认的SimpleExecutor
的doUpdate()
:
在doUpdate()
方法中,是真正的SQL执行的逻辑,我们来看一下:
1、获取MappedStatement
对象的Configuration
对象,获取核心配置信息;
2、组装StatementHandler
,加载plugin
插件,生成代理方法;
3、执行SQL,返回结果集。
针对步骤2,我们来简单分析一下:
这里首先创建了一个默认的RoutingStatementHandler
对象,接下来,调用plugin
责任链,MyBaits
使用责任链的方式来加载plugin
插件类,通过生成代理的方式,来执行插件方法,这里值得注意的是:
- 形参
Object target
,这个是Executor、ParameterHandler、ResultSetHandler、StatementHandler
接口的实现类,换句话说,plugin
方法是要为Executor、ParameterHandler、ResultSetHandler、StatementHandler
的实现类生成代理,从而在调用这几个类的方法的时候,其实调用的是InvocationHandler
的invoke
方法; - 这里的
target
是通过for循环
不断赋值的,也就是说如果有多个拦截器,那么如果我用P表示代理,生成第一次代理为P(target),生成第二次代理为P(P(target)),生成第三次代理为P(P(P(target))),不断嵌套下去,这就得到一个重要的结论:<plugins>...</plugins>
中后定义的<plugin>
实际其拦截器方法先被执行,因为根据这段代码来看,后定义的<plugin>
代理实际后生成,包装了先生成的代理,自然其代理方法也先执行。
关于插件,这里我们先不过多的介绍,下一篇中,我们会着重介绍plugin
的实现。
PreparedStatement
我们继续向下看,StatementHandler
生成后,初始化Statement
对象,默认情况下,是使用PreparedStatement
执行SQL:
看到这里,是不是熟悉的味道,还是原来的配方,还是原来的味道,最原始的JDBC的执行方式,执行成功后,拿到影响的结果行数,返回。
上面我们对INSERT
的执行进行了分析,事实上,QUERY
的查询也是一样的逻辑,这里,就不再演示一遍,留给您自己挖掘。
MyBatis $ 与 # 的区别
我们在编写MyBatis
的SQL
语句的时候,经常用到的是#{}
的字符去替代其中的查询入参,偶尔也会在网上看到${}
这样的字符使用。
对于这两种写法,比较通用的解释是,${}
这种写法容易引起SQL注入,那么,这个说法的原因是什么呢?MyBatis
是如何实现的呢?下一篇中,我们会解开这个答案。
结语
本篇,我们对MyBatis
的SQL执行过程进行了简要分析,对于SQL的执行全过程,我并没有特别详细的展开,避免您陷入细节的泥潭中来,事实上SQL的执行过程中,对于SQL语句的解析还是比较复杂的,其中做了大量的细节逻辑处理,如果您对这方面感兴趣,可以自行深入研究源码。
通过对MyBatis
的源码分析,我们了解到其使用了大量的代理模式,这样做的好处是非常的灵活,当然也引入了一定的复杂度,JDK的代理模型也是我们可以好好学习的。
在下一篇中,我们将对MyBatis的SQL解析中的#{}
与${}
的区别进行探究,敬请期待。
更多精彩文章, 请关注我的个人公众号:老宣说
让我们一起共同学习成长!
感谢您的支持!