小白都能懂的Mybatis源码分析

Mybatis源码分析

MyBatis是当前最流行的java持久层框架之一,Mybatis方便地解决了我们之前使用jdbc大量重复的getter、setter,使用起来很方便。

源码分析的切入点在哪呢?今天我们来看看。

结合配置文件使用Mybatis

我们知道Mybatis要想查询数据,首先得连接数据库,Mybatis的数据库连接是靠会话来完成的,我们通常会这样写:
在这里插入图片描述
sqlmap-config.xml

在这里插入图片描述

这里边有开启会话的所有方法,我们逐一来看看。

1. 获取会话session
SqlSessionFactoryBuilder

我们看到首先 new SqlSessionFactoryBuilder(),这一步没有什么好说的。

Reader

接下来看reader的获取,我们看到了getResourceAsReader()方法,参数传递的是我们配置文件的路径,那这个方法是干了什么呢?

在这里插入图片描述

这里边主要的代码就是getResourceAsStream(),继续看

在这里插入图片描述

这个我们很好理解,通过类加载器读取到配置文件的输入流

SqlSessionFactory

SqlSessionFactory的获取是个非常关键的地方,builder.build(reader)这个方法究竟做了什么工作?能够这么厉害

在这里插入图片描述

首先呢创建了一个XMLConfigBuilder对象,这个过程底层主要是对xml格式进行了校验,之后DocumentBuilderFactory创建DocumentBuilder实例,DocumentBuilder创建document,

然后来看XMLConfigBuilder的parse()。

在这里插入图片描述

我们看到程序取了好多参数,有些参数我们没有配置过,先来看看这些参数都是什么,有什么作用

在这里插入图片描述

parser.evalNode("/configuration")其实就是获取我们配置文件中的configuration节点,parseConfiguration(XNode root)这个方法就是解析configuration节点里边的节点

  • propertiesElement()

在这里插入图片描述

这个方法里,首先获取了默认的配置参数对象,之后获取配置文件中properties节点的resource和url,这两个参数我们都没有配置
接着往下看判断,我们发现:配置文件中的resource和url不能同时存在只能取其一

  • typeAliasesElement()

在这里插入图片描述

这个方法主要处理的是配置文件中的typeAliases节点,别名的设置有两种级别,一种是包级别,另一种是类级别,可以配置多项,所以方法中遍历并设置别名。

  • pluginElement()

在这里插入图片描述

这个方法主要就是获取到我们配置的拦截器进行配置

我们再来看一个方法,也就是获取我们写的sql,其他的几个方法,不在这里具体介绍了,都是获取配置文件中的相关节点进行配置。具体每个节点都是做什么的,在上边的图片中已经有了介绍。

  • mapperElement()

在这里插入图片描述

我们都知道mapper的配置方式有三种:

在这里插入图片描述

我们可以看到,这个方法就是读取mapper节点的内容,然后获取输入流,或通过反射得到类,进行操作,这里边主要的方法有两个,一个是mapperParser.parse();另一个是configuration.addMapper(mapperInterface);

- a

mapperParser.parse()

在这里插入图片描述

来看这里边的第一个方法configurationElement()

在这里插入图片描述

获取到sql文件的命名空间,进而解析各个节点,我们主要来看后边的4个方法。
parameterMapElement()、resultMapElements()、sqlElement()、buildStatementFromContext()

在这里插入图片描述

以上4个方法就是获取到我们的参数、返回对象、sql、sql-id等

在这里插入图片描述

这个方法获取的是我们命名空间中的mapper和我们的命名空间的绑定

再来看这三个方法parsePendingResultMaps()、parsePendingChacheRefs()、parsePendingStatements()

在这里插入图片描述

- b

configuration.addMapper()

在这里插入图片描述

这样的话,我们的SqlSessionFactory就创建好了

Session

在这里插入图片描述

我们看到,其实就是获取我们配置的环境,创建TransactionFactory,JdbcTransaction,进而创建Executor,最终创建一个DefaultSqlSession实例返回。

至此,我们获得了和数据库链接的session

#####补充说一点:

参考 https://www.cnblogs.com/zhjh256/p/8512392.html

sql语句解析的核心:mybatis语言驱动器XMLLanguageDriver
虽然官方名称叫做LanguageDriver,其实叫做解析器可能更加合理。MyBatis 从 3.2 开始支持可插拔的脚本语言,因此你可以在插入一种语言的驱动(language driver)之后来写基于这种语言的动态 SQL 查询比如mybatis除了XML格式外,还提供了mybatis-velocity,允许使用velocity表达式编写SQL语句。可以通过实现LanguageDriver接口的方式来插入一种语言:

在这里插入图片描述

一旦有了自定义的语言驱动,你就可以在 mybatis-config.xml 文件中将它设置为默认语言:

在这里插入图片描述

除了设置默认语言,你也可以针对特殊的语句指定特定语言,这可以通过如下的 lang 属性来完成:

在这里插入图片描述

默认情况下,mybatis使用org.apache.ibatis.scripting.xmltags.XMLLanguageDriver。通过调用createSqlSource方法来创建SqlSource,如下:

在这里插入图片描述

他有两个重载版本,第二个主要是传递文本的格式,所以我们重点分析第一个版本。XMLScriptBuilder构造器中设置相关字段外,还硬编码了每个支持的动态元素对应的处理器。如果我们要支持额外的动态元素比如else/elsif,只要在map中添加对应的key/value对即可。

在这里插入图片描述

这里采用了内部类的设计,内部类只有在有外部类实例的情况下才会存在。在这里因为内部类不会单独被使用,所以应该设计为内部类而不是内部静态类。每个动态元素处理类都实现了NodeHandler接口且有对应的SqlNode比如IfSqlNode。
现在来看parseScriptNode()的实现:

在这里插入图片描述

在解析mapper语句的时候,很重要的一个步骤是解析动态标签(动态指的是SQL文本里面包含了${}动态变量或者包含等元素的sql节点,它将合成为SQL语句的一部分发送给数据库),然后根据是否动态sql决定实例化的SqlSource为DynamicSqlSource或RawSqlSource。
可以说,mybatis是通过动态标签的实现来解决传统JDBC编程中sql语句拼接这个步骤的(就像现代web前端开发使用模板或vdom自动绑定代替jquery字符串拼接一样),mybatis动态标签被设计为可以相互嵌套,所以对于动态标签的解析需要递归直到解析至文本节点。一个映射语句下可以包含多个根动态标签,因此最后返回的是一个MixedSqlNode,其中有一个List类型的属性,包含树状层次嵌套的多种SqlNode实现类型的列表,也就是单向链表的其中一种衍生形式。MixedSqlNode的定义如下:

在这里插入图片描述

这样在运行的时候,就只要根据对应的表达式结果(mybatis采用ONGL作为动态表达式语言)调用下一个SqlNode进行计算即可。

IfHandler
IfHandler的实现如下:

在这里插入图片描述

OtherwiseHandler
otherwise标签可以说并不是一个真正有意义的标签,它不做任何处理,用在choose标签的最后默认分支,如下所示:

在这里插入图片描述

BindHandler
bind 元素可以使用 OGNL 表达式创建一个变量并将其绑定到当前SQL节点的上下文。

在这里插入图片描述

对于这种情况,bind还可以用来预防 SQL 注入。

在这里插入图片描述

ChooseHandler
choose节点应该说和switch是等价的,其中的when就是各种条件判断。如下所示:

在这里插入图片描述

ForEachHandler
foreach可以将任何可迭代对象(如列表、集合等)和任何的字典或者数组对象传递给foreach作为集合参数。当使用可迭代对象或者数组时,index是当前迭代的次数,item的值是本次迭代获取的元素。当使用字典(或者Map.Entry对象的集合)时,index是键,item是值。

在这里插入图片描述

SetHandler
set标签主要用于解决update动态字段,例如:

在这里插入图片描述

一般来说,在实际中我们应该增加至少一个额外的最后更新时间字段(mysql内置)或者更新人比较合适,并不需要使用动态set。

在这里插入图片描述

因为在set中内容为空的时候,set会被trim掉,所以set实际上是trim的一种特殊实现。

TrimHandler
trim使用最多的情况是截掉where条件中的前置OR和AND,update的set子句中的后置”,”,同时在内容不为空的时候加上where和set。
比如:

在这里插入图片描述

虽然这两者都可以通过非动态标签来解决。从性能角度来说,这两者是可以避免的。

在这里插入图片描述

WhereHandler
和set一样,where也是trim的特殊情况,同样where标签也不是必须的,可以通过方式解决。

在这里插入图片描述

2. 调用API查询数据库
selectList()

我们在sql.xml中有这么一个sql

在这里插入图片描述

这条sql其实就是查询所有,然后排了一个顺序,我们的测试类中这样写

在这里插入图片描述

其实我们已经看出来,findAll就是sql的id,排序的规则就是数据表中的ID字段,那究竟是怎么样完成的查询呢?一起来看看

我们先来看sql是怎么存的,存在哪里了,什么时候存的?这就要来看一下之前我们没有分析的一个方法

在这里插入图片描述

这个方法就是我们在读取sql.xml时的一个方法,首先读取文件中的节点及参数

来看里边的几个方法

在这里插入图片描述

总的来说,将节点分为文本节点、include、非include三类进行处理。因为一开始传递进来的是CRUD节点本身,所以第一次执行的时候,是第一个else if,也就是source.getNodeType() == Node.ELEMENT_NODE,然后在这里开始遍历所有的子节点。
对于include节点:根据属性refid调用findSqlFragment找到sql片段,对节点中包含的占位符进行替换解析,然后调用自身进行递归解析,解析到文本节点返回之后。判断下include的sql片段是否和包含它的节点是同一个文档,如果不是,则把它从原来的文档包含进来。然后使用include指向的sql节点替换include节点,最后剥掉sql节点本身,也就是把sql下的节点上移一层,这样就合法了。举例来说,这里完成的功能就是把:

在这里插入图片描述

对于文本节点:根据入参变量上下文将变量设置替换进去;
对于其他节点:首先判断是否为根节点,如果是非根且变量上下文不为空,则先解析属性值上的占位符。然后对于子节点,递归进行调用直到所有节点都为文本节点为止。

上述解析完成之后,CRUD就没有嵌套的sql片段了,这样就可以进行直接解析了。现在,我们回到parseStatementNode()刚才中止的部分继续往下看。接下去是解析selectKey节点。selectKey节点用于支持数据库比如Oracle不支持自动生成主键,或者可能JDBC驱动不支持自动生成主键时的情况。对于数据库支持自动生成主键的字段(比如MySQL和SQL Server),那么你可以设置useGeneratedKeys=”true”,而且设置keyProperty到你已经做好的目标属性上就可以了,不需要使用selectKey节点。由于已经处理了SQL片段节点,当前在处理CRUD节点,所以先将包含的SQL片段展开,然后解析selectKey是正确的,因为selectKey可以包含在SQL片段中。

在这里插入图片描述

在这个方法里边,就是设置一些参数,主要的方法是configuration.addMappedStatement(statement)

在这里插入图片描述

所以我们的sql被放在了map中,key为sql的id

我们通过id取出了sql,之后就进行了查询,调用的是executor执行器的query方法

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值