从其他项目中复制过来的mapper加载不进bean_降龙-第20章:Mapper解析

3976ccb0983ef90673a9cba77fe7b185.png

网上有很多解读Mybatis源码的文章,无外乎断点跟踪源码,复制源码粘贴到文章中然后对代码写注释,也就是说先有了Mybatis然后才有的这些源码解析的文章,这就好比我们上语文课来解读课文一样。本章我们来自己动手实现Mapper文件的解析。​

9aeab3578b7060119d94d8bdf5fcb5ab.png

这是本地提交到GitHub代码的截图,主要是一个DaoMapper类实现了几乎所有功能。​

69dd0aa1444f750fa57ab4c0ac02d8f6.png

这是本次代码在GitHub上展示的文件变动信息,由于目前处于开发后端框架阶段,工程还没有集成mvc所有功能及测试验证,所以只能一边实现代码,一边先发布文章。​

很多事情说的远比做的简单,写代码就是这么一回事。你可能在后端开发中会用Mybatis,但是要很顺的按照自己的理解分析一遍整个运作流程还是有点困难的。我自己没看过Mybatis源码,在自己动手实现这个过程时,都是从突破口入手,确保走一条能走通的路才开始干的。

f02c8936dd1b4c8250c1b13e197fb6a4.png

来看一下提交的5个类中最简单的一个实体类。

在培训学校或者大学课堂内老师肯定先教大家写在Java中用字符串调用jdbc执行SQL语句,肯定还会讲到SQL防注入,然后演示一波用恶意字符串实现SQL注入的操作。这里这个类就是接收解析后的预编译SQL,SQL中的参数值都会被替换成问号,在SQL预编译阶段确定了其结构,后续执行的过程才会捕获SQL注入异常。

上一章中我们有定义了Mapper文件的标签枚举,于是我们就能解析xml文档节点是那种标签,就能知道是增删改查中的哪种SQL语句,然后就能按分支展开讨论,用代码实现业务逻辑了。

bd81e8728163ecfa7d70c8e9b1ef9d32.png

上图是这次提交的5个类中的BaseUtil类的改动,增加了一个方法,实现像前端json对象一样链式取值的方法。考虑到一个复杂结构的实体参数有层次结构,那在SQL参数取值里面就可以用链式取值了,不用把参数打平成map结构。可以看到随着框架的不断迭代,工具类里面的方法也慢慢丰富起来了。​

2db2ef56de85f7638ea1129d8f32ad74.png

这是第3个类,里面是实现了一个第三方插件的接口类,这是按照其插件提供的方法的规定写的接口实现类,主要是用于一些代码反射时的权限自定义控制。这个插件是一个叫OGNL的技术的代码Java实现jar包,网上对其有诟病的就是安全问题,因为其动态编程和代码反射存在很大安全隐患,但是我们可以根据这个权限控制实现类来进行管理。一些分模块开发的工程,或者核心模块带代码不允许反射或者动态编程的就可以用这个类类做控制了,我们这里暂不做控制,按照我写的去用就可以了。​

启动方法

fd8ed2bebcd8ea1023c5f6897133b68a.png

在项目启动的入口添加Mapper解析代码,这是可以先开发的,属于打地基工作。想了下目前除了第一步加载配置文件方法必须先执行后面的几个步骤都可以没有顺序要求,后续可以考虑出错率高的先执行,节省项目错误时程序启动时间。

总体流程

644cc0e22930757a95a0d13687543402.png

现在看到的代码是最终呈现运作的代码,这就跟直接给你参考答案一样,整个解题过程我省略了。

首先,这个类有3个静态缓存,其中文档缓存和文件名缓存会在最后清空,最后只保留节点​缓存。

其次,这个类有几个成员变量的定义,主要是为了减少一些方法的参数。

然后,我们来看总体解析流程的代码,整个过程​很清晰顺畅的用注释给你写了,我就不复述了。上面先是扫描了一边所有存放Mapper文件的文件夹,获取到了所有文件对象,然后多线程解析xml文件​。接着就开始串行校验xml文件,为什么是串行,因为涉及到嵌套依赖,并行会有​问题。​校验完根节点后就开始校验整个文档了。

递归读取文件

aa965d017766e712f67604864702578b.png

上面截图代码中获取mapper文件存放的包,然后读取下面所有xml文件,读取到线性的文件列表后,就可以根据文件数量来分每个配线程处理文件数量进行并发解析xml文件了。

多线程解析XML文件

c62dc98566fcce75e44da0ca910b5901.png

根据总文件数量,按照最多30个线程进行分配每个线程处理的数量,除不尽的余数作为最后一个线程单独处理。然后处理把所有异常都抛出,启动方法会自动终止程序启动。

5dfe431eda53f8bb25dd2224c81cb751.png

解析XML线程方法

a996a0f1fa81627b54480fcb9f21e238.png

前面提到当前类有两个静态成员变量缓存了xml文件名称和xml文件对象​。后面在解析单个xml文件内容的时候,抛出的异常需要提示出xml文件路径。

fb3af123a8d2d9035605782dde1234a3.png

在解析xml文件的时候需要校验xml文件语法,我用的jdk自带的xml解析类处理时会联网获取dtd文件,所以就把它的校验方式改为读取本地校验文件处理了。

2220bef3e06db27536102918df36c8c0.png

注意到用的就是mybatis原始的语法校验规则文件,我的简化版mapper解析同样支持​移植。

缓存所有文档节点

注意到写sql语句的mapper文件有一个很大的特点,就是其内容节点都是并列写的,增删改查语句都是在根节点下挂载的,这带来了很大好处就是做节点缓存只需要一次遍历即可,不需要递归遍历文档树。

452d8e96498a3b7b8234d94469668496.png

节点缓存设置了二级索引,方便在做交叉引用命名空间的时候用到。缓存节点的同时校验每个xml文件的节点id不能重复。​

节点校验

所有节点缓存设置好后,开始逐个遍历xml文档,校验下面的所有二级节点​。

905d62a226419678906bf82a71009101.png

校验无非就是标签名称、属性名称以及属性值这三个​,我们逐个来看校验逻辑​:

节点校验:标签

46c511392e3a7da4a61f65d1c1cecb1c.png

只允许支持的节点标签可以输入,不支持的写了也没用,这样的​强校验报错方便后续拓展没有支持的标签。

节点校验:属性值​

84491e2df4c0395888243f1bf052121b.png

通过枚举定义每个标签支持配置的属性,只对支持的属性进行校验,多余的属性在我的框架中用不到,但是也不会影响业务逻辑,为了能友好的支持MyBatis迁移过来的mapper文件,对不支持的属性做跳过处理,不再是抛出异常了。

7546d6532738beaad85e6c4228a12b3a.png

而在校验属性的方法里面则先保留抛出异常的代码。必填属性没有值需要抛出异常,有值则需要校验值的合法性,下面我们来看下节点属性的校验:

属性值​校验

ecc8bb6d90178df0771e6b87a26be3b8.png

其实前面的代码主要是把整个过程串起来,现在的代码主要是做业务逻辑校验,相对后面的代码偏逻辑一点,看起来会比较简单。目前要校验的地方就几个​分别罗列如下:

属性值​校验:id

c5b342b3bcefe4b6219c552387a5a642.png

id为空或者包含点都是非法的,其中id有点如果允许则破坏了​命名空间的规则所有要报错。

属性值​校验:namespace

e5514a5b8072157147731cd95dc45094.png

命名空间被设计为类名就比较方便把类和mapper对应起来了,所以校验命名空间就只需要校验对应的类存不存在即可。

属性值校验:test表达式

8fb7710346751fdf431a6a187bdaeae1.png

mapper文件写SQL的优势之一就是写条件判断语句很清晰,多属性的拓展写法是用java代码拼接字符串所做不到的,对于表达式由于要依赖运行中的数据,所以暂时只能校验其表达式是否存在。​

属性值校验:resultType

12f6a7e823c12be7a52faf2228804162.png

除了byte不支持外还去除了float的返回值。

属性值校验:index

6924f7a4860e0c88f7df5247430134ce.png

这个主要用在数组操作上,方便拼接in条件的sql查询。

属性值校验:collection

a990983fe3898cd1270bff0814cb4ead.png

这个是配合index一起使用的,做数组的遍历操作。​

属性值校验:refid

aac3c70114594569d7c3beea2218dff2.png

​​最后这个是关联节点的属性,这个是相对复杂些。针对没有包含"."的从当前节点缓存获取节点对象,而包含有点的则是从所有命名空间里面查找,这就解释了前面为什么说校验id属性包含"."必须抛出异常。

递归校验

461af1294a469c4c0cf6d5e6402d84a1.png

由于每个节点都可以自由组合sql片段,所以需要做递归校验。​由于每个节点都可以自由组合sql片段,所以需要做递归校验。一般这种嵌套层次不会太深,直接用递归问题不大。​

获取SQL语句

本来这个方法是应该放到讲dao的动态代理那一章去的,无奈这个方法写在这个类里面,又和整个类关系比较密切,所以把这个方法也放出来了。

1d8531378c49a353f36c01692935b239.png

在之前daoFactory的实现中用到了带有占位符的sql语句,这个方法就是提取这个sql的方法实现。后续在dao的接口代理类调用接口方法时会拿到namespace、id以及参数,就可以通过这个方法把节点的sql文本提取出来了。

由于每个节点都可以嵌套组合,所以在解析单个节点时还需要对节点的子节点做递归解析,解析子节点代码如下:

a83c1171ae19e4c23762be93a0e8e087.png

这个类的实现需要花大量的时间和精力去调试。像xml节点的遍历会有空文本节点这种根本不在自己的思考之内,但是调试的时候却偏偏出来了这个没用的节点;像要保持xml文档内容的排版格式要如何组合字符串;像数组遍历操作的自己实现;还有test表单式的实现所需要的jar包和方法实现等。

test表达式是需要用到一个ognl的jar包和javassist动态编程jir包的,我把源码解压出来看了,差不多几十个类,要自己实现支持自己框架的ognl其实也挺简单,但是要考虑到MyBatis移植,所以就先用jar包把所有ognl给支持了。​

OGNL:test表达式

61a7fe678be2ac883431be7911dfbe4d.png

我的解析遇到test表单式用到了一个第三方插件包,下次遇到高级的语法时我再来拓展支持。

OGNL:forEach表达式

有MyBatis在前面开路,我只需要把这个标准拿过来用就行。

9825da177c51b18b51693738d7e12afa.png

反正就是同样自己实现一下,这个就没用到ongl包的方法了,自己实现了一下,方便今后做技术拓展。像test表达式简单但是实现起来却比较复杂,就用专业的包处理了。这里面由于涉及到修改sql语句结构所以会处理#{}和${}的逻辑。

OGNL:#{}和${}替换

上面用到的字符串占位替换方法也是自己实现的一次遍历算法,代码比较长截图如下:

a1a6a4dee57c7a8d571592d6620ad114.png

不解释这串代码了,逻辑很简单,在以前的章节套路网页模板的时候有比这个更复杂的方法,上面写有注释,这里不再重复写了。

后面的章节要先实现mvc控制才能最终实现dao的调用,所以dao的代理反射还是要放一放再来写。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值