mybatis开发,你用 xml 还是注解?我 pick ... 您所在的位置:网站首页 mybatis注解好还是xml好 mybatis开发,你用 xml 还是注解?我 pick ...

mybatis开发,你用 xml 还是注解?我 pick ...

2024-06-15 08:09| 来源: 网络整理| 查看: 265

最近在看公司项目时发现有的项目mybatis是基于注解开发的,而我个人的习惯是基于xml文件开发。

对于mybatis注解开发的原理理解不够,于是翻阅了部分源码,写下此文。主要介绍了mybatis开发的两种形式、三种写法。还有一点瞎思考,介绍了一处骚代码、还有一个坑。

原创不易,感谢阅读,感谢关注,感谢点赞,感谢转发。

荒腔走板

大家好,我是 why 。老规矩,在技术分享开始之前,先荒腔走板,聊点别的。

上周我写的这篇文章《我告诉你这书的第 3 版到底值不值得买?》居然被《深入理解Java虚拟机》的作者周志明先生看到了,还给我赞赏并留言给我说:作者表示感谢,真心的。

说实话,我看到这个赞赏的时候我都震惊了。有一种和大神产生了交集的感觉。

其实上周这篇文章是出版社找到我说送我一本第三版,让我看看,然后写个观后感就行。

恰好,在他们没有找到我之前我也是有这样的打算的。

我不是为了白嫖出版社几本书,而是我早在去年年底打算买第三版后,这篇文章就一直在着手准备了。

在机缘巧合之下,即完成了自己的计划,又获得了出版社的几本书,通过出版社,又勾搭上了本书作者,不仅获得了作者的赞赏还得到一本签名版。

哎,这疯狂而又操蛋的人生呀。

所以你问我写了这么久的文章收获了什么?

说实在的,我没有通过写文章挣到几个钱。但是我收获的是与一群志同道合的原创作者同行的机会、是读者读完文章后对我的文章的指点与赞扬、是偶尔与几位业界大佬之间互动的惊喜。

仅此而已。

好了,说回文章。

两种形式,三种写法

最近在看公司的一些项目的时候发现有的项目里面的 mybatis 是基于注解开发的。而我个人的习惯是基于 xml 文件开发。

所以对于基于注解开发的原理不太了解,于是去翻看了一下相关源码,形成此文。

本文主要介绍基于 mybatis 开发的两种形式,三种写法。

其中两种形式是指:

1.基于 xml 文件。

2.基于注解开发。

三种写法是指除了 xml 的形式外,注解又有两种不同的写法,它们的实现原理也略有不同,拿 Select 语句举例,就有两种注解 @Select、@SelectProvider 。

演示示例

先上一个演示示例给大家直观的感受一下:

首先,我们有个用户表,包含这些字段和这样一条数据:

然后我们搞个接口类,用三种方式去查询用户的年龄,具体如下:

xmlQueryAgeByName 方法是使用 xml 的方法去查询用户年龄,对应的 xml 如下:

annotationQueryAgeByName 方法是使用 @Select 注解去查询用户的年龄,SQL 就写在注解里面:

classQueryAgeByName 方法是使用 @SelectProvider 注解去查询用户的年龄,可以看到注解里面有个 type 字段,对应一个 class 类。一个 method 字段,对应 class 类中的一个方法:

其中 UserInfoSql 类如下:

然后,再来一个测试用例,把三个方法都测试一下:

最后的输出结果如下:

xmlQueryAgeByName whyAge = 18 annotationQueryAgeByName whyAge = 18 classQueryAgeByName whyAge = 18

测试用例就演示完成了,是一个极简的用例。

我就是基于这个案例去分析源码的,在分析之前,其实有点经验的老哥也能看出来了,我们先撇开常规的 xml 文件的形式不谈。

基于 @Select 注解的接口, SQL 就在注解里面,所以我们只需要通过反射取出注解里面的 SQL 进行分析就行了。

基于 @SelectProvider 注解的接口,SQL 虽然在一个类的方法中,但是注解上都告诉你是哪个类的哪个方法了,所以,一定是基于反射去取出方法里面的 SQL 的。

接下来,我们就是去验证一下。

好,准备发车。

小心求证

关于 mybatis 我之前写过这篇文章《很开心,在使用mybatis的过程中我踩到一个坑》,其中提到了一个逆向排查法。有兴趣的可以去看一下。

在这篇文章中我们还是来个常规分析吧。本文分析源码为 mybatis 3.4.0 版本。

首先,我先问你一个问题。SpringBoot 是怎么加载 mybatis 的?

熟悉 SpringBoot 启动过程的朋友知道,SpringBoot 会去加载mybatis-spring-boot-autoconfigure-x.x.x.jar下 META-INF 中的spring.factories文件:

所以,下面的 sqlSessionFactory 方法就是我们的入口处:

入口给你找到了,你可以直接在这里加上断点开始 debug 了。

我知道,虽然是刚刚开始,但是可能有些读者觉得已经超纲了。但是没有关系的,继续看下去,我这里只是给你说个入口在哪而已。

由于 debug 的过程不是文本重点,这里就不去介绍了。debug 的时候我们会看到这个方法:

org.apache.ibatis.builder.xml.XMLMapperBuilder#parse

这个方法的第 92 行,就是我们的 xml 内容:

然后在下面这个方法中对 xml 文件进行疯狂的解析:

org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode

图片可以点开看大图哦,debug 模式,可以看到一些输出:

上面的源码的第 94 行,获 取 SqlSource 很关键,要好好看看,这里调用了这个方法:

org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class)

接着在下面方法的第 52 行,剥离出整个完整的 sql:

org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode

上面就是常规的 xml 形式的 SQL 原始语句(变量、条件表达式都还未进行替换,不可直接执行的 SQL)获取过程,不是本文重点,简单的分析一下就行。

接下来继续 debug 的时候会遇到下面这个方法,看包名你就知道,这就是我们关心的注解解析相关的方法了:

org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse

在这个方法里面,会去循环处理 mapper 类中的方法:

接下来,就会遇到这个方法了:

org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#getSqlSourceFromAnnotations

当循环到 annotationQueryAgeByName 方法的时候,下面方法的一些关键参数如下所示:

首先我们看 428 行,解析到了 sqlAnnotationType 为 Select:

所以会进入下面的 if 分支,然后运行到 435 行,通过反射获取到了 @Select 注解上的 SQL 语句:

继续往下走,通过 436 行,我们可以走到这个方法:

org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, java.lang.String, java.lang.Class)

这个方法就有点意思了,进来判断了 script 即 SQL 是否是以 script 脚本开头的,如果是,则走的和之前 xml 一样的解析逻辑:

我第一次看到这个地方的时候,一下才恍然大悟过来,我才明白,@Select 的本质还是 xml 文件的形式啊。只是换了个展现形式而已。

我之前的一个问题,或者说是错误的看法也就迎刃而解了。

我之前认为 @Select 的方式是只能支持简单 SQL 的书写,对于一些类似于判空的需求是不支持的。(因为对 mybatis 注解开发确实不熟)

比如在 xml 文件中这样去写:

LIMIT #{startPage},#{pageSize}

只是这个写法,呃,怎么说呢,非常不优雅。

不要为了注解而注解,很明显,这种情况直接用 xml 形式更好。

到这里,我们也知道了,基于 @Select 注解的方式开发时, mybatis 会通过反射获取到注解里面的 SQL ,而这些 SQL 需要一些比较复杂功能,比如判断条件是否为空时,可以用 script 标签包裹起来。写法和在 xml 里面开发是一样的。

接下来,我们看看 @SelectProvider 方法是什么个样式。

还是在同样的方法中,只是走向了另外一个分支:

此时的 sqlProviderAnnotation 里面的东西如下:

接着去 new ProviderSqlSource 对象:

在这个方法中,获取到了注解上的具体的提供 SQL 原始语句的方法。

注意红框中框起来的 providerMethod 对象,后面获取真正执行的 SQL 语句的时候还会用到。

同时,我们可以看到 ProviderSqlSource 是 SqlSource 的实现类。

所以,不管是 xml 还是注解,最终都需要获取到一个 SqlSource 对象。

而在本文的示例代码中, xml 和 @Select 生成的是 RawSqlSource。

@SelectProvider 生成的是 ProviderSqlSource。他们里面放的东西是不一样的。

在 RawSqlSource 里面的 sqlSource 变量(类型 StaticSqlSource)放的已经是从 xml 或者 @Select 注解中获取到的 SQL 原始语句了(但是里面的变量还没替换,因为程序启动过程中根本不知道变量的值具体是什么,如果有一些条件表达式的话同理)。

而在ProviderSqlSource 里面,我们前面已经说了,放的是 @SelectProvider 注解上具体的提供 SQL 语句的方法,仅仅是方法,而不是语句。

前面的所有分析都是在我们的方法真正执行之前,接下来,才会 debug 到我们的测试用例,因为只有我们的测试用例里面才有真正的入参, mybatis 才能根据入参,执行最终的 SQL 语句。

所以,接下来,我们就是要找到真正生成 SQL 语句的地方,这里就能和之前文章《很开心,在使用mybatis的过程中我踩到一个坑》中的逆向排查法中得出的结论进行呼应了。

进入 getBoundSql 我们可以看到第292行,就是通过 sqlSource 的 getBoundSql 方法获取到的 boundSql 对象:

org.apache.ibatis.scripting.xmltags.DynamicSqlSource#getBoundSql

这不就又呼应上了吗?又看到 sqlSource 了。

所以,接下来,我们看一下这两个方法就可以了:

org.apache.ibatis.builder.StaticSqlSource#getBoundSql


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有