聊聊 Mybatis 动态 SQL,你学会了吗?

数据库 SQL Server
如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。
这篇文章,我们聊聊 Mybatis 动态 SQL  ,以及我对于编程技巧的几点思考 ,希望对大家有所启发。

图片图片

1 什么是 Mybatis 动态SQL

如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。

Mybatis  借助功能强大 OGNL 表达式,可以根据参数条件,动态生成执行 SQL 。

使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。

图片图片

这条语句提供了可选的查询博客文章列表 ,如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 博客都会返回。

如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果。

如果希望通过 “title” 和 “author” 两个参数都可以搜索,只需要加入另一个条件即可,见下图:

图片图片

我们也可以使用 where 标签,该标签只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 标签也会将它们去除。

图片图片

Mybatis 还支持 choose (when, otherwise)、trim (where, set)、foreach 等其他的动态标签,这里就不一一赘述了。

2 人生第一次线上OOM事故

我曾服务一家电商公司的用户中心,用户中心提供用户注册,查询,修改等基础功能 。

那个时候 Dubbo 等 RPC 框架并没有开源,所有的服务都以 HTTP 接口形式提供,数据传输格式是 XML 。

因为写接口非常费劲,所以为了接口复用,我写了一个通用接口 getUserByConditions ,该接口支持通过 「用户名」、「昵称」、「手机号」、「用户编号」这三个查询用户基本信息。

使用的是 ibatis (mybatis 的前身), SQLMap 见下图 。

图片图片

当构建动态 SQL 查询时,条件通常会追加到 WHERE 子句后,而以 WHERE 1 = 1 开头,可以轻松地使用 AND 追加其他条件。

用户中心在上线后,竟然每隔三四个小时就发生了内存溢出问题 ,经过通过和 DBA 沟通,发现高频次出现全表查用户表,执行 SQL 变成 :

图片图片

查看日志后,发现前端传递的参数出现了空字符串,笔者在代码中并没有做参数校验,所以才出现全表查询 ,当时用户表的数据是 1000多万 ,页面调用几次,用户中心服务就 OOM 了。

最终解决问题的方式很简单,后端在接收参数时,做了参数校验。

事故虽然解决了,但对我的影响一直延续至今。我仍然记得当时站在运维同学旁边,不断的看他调整 JVM 参数 ,重启服务的画面,自己那个时候真的是羞愧难当,心中发誓:以后绝对不可以发生类似的事故。

对于动态 SQL ,我的编程思维也经历了如下三个阶段 :

  • 前后端参数校验
  • 复用和专用要做平衡
  • 防御性编程意识

3 前后端参数校验

为了提升开发效率,我们人为的将系统分为前端、后端,分别由两拨不同的人员开发 ,经常出现系统问题时,两拨人都非常不服气,相互指责。

要想系统健壮,前后端应该同时做接口参数校验 (后端必须做参数校验),当大家都遵循这个规约时,出现系统问题的风险大大减少。

1)前端校验

图片图片

前端校验,主要是为了提高用户体验,例如用户输入一个邮箱地址,要校验这个邮箱地址是否合法,没有必要发送到服务端进行校验,直接在前端用 js 进行校验即可。

但是大家需要明白的是,前端校验无法代替后端校验,前端校验可以有效的提高用户体验,但是无法确保数据完整性,因为在 B/S 架构中,用户可以方便的拿到请求地址,然后直接发送请求,传递非法参数。

2)后端校验

后端必须做参数校验,后端必须做参数校验,后端必须做参数校验,重要的事情表达三次。

数据在网络传输过程中有可能被篡改了,或者数据不满足业务需求,假如不做后端参数校验,则有可能导致系统异常,或者业务出现重大事故。

比如在 SpringBoot 项目中,我们可以使用 hibernate-validator 进行参数校验 。

POST、PUT 请求一般会使用 requestBody 传递参数,这种情况下,后端使用 DTO 对象进行接收。

只要给 DTO 对象加上 @Validated 注解就能实现自动参数校验。比如,有一个保存 User 的接口,要求 userName 长度是 2-10,account 和 password 字段长度

是 6-20。

在 DTO 字段上声明约束注解:

图片图片

在方法参数上声明校验注解:

图片图片

虽然,我们可以使用接口校验,可以保证动态 SQL 的参数正确,但是假如我们仅仅只是复用 SQLMap (Dao 方法)时,也有可能因为调用方传递参数错误,导致非预期的问题。

当然,我们也可以使用 Mybatis 拦截器从根本上来解决,但是我想这样会加大系统的复杂度。于是,我思考了了另外一点:复用和专用要做平衡。

4 复用和专用要做平衡

我当时写的那个接口 getUserByConditions ,是支持四种不同参数的查询,同样也是为了省时间,快点出活。

后来,随着我工作经验的日益丰富,我的编程习惯也慢慢发生了改变,对于业务需求明确的场景,我更多的倾向于将通用接口拆分成专用接口。

比如 getUserByConditions 可以拆分成如下四个接口 ,

  • 按照用户 ID 查询用户信息
  • 按照用户昵称查询用户信息
  • 按照手机号查询用户信息
  • 按照用户名查询用户信息

比如按照用户 ID 查询用户信息 , SQLMAP 就简化为:

图片图片

通过这样的拆分,我们的接口设计更加细粒度,也更容易维护 , 同时也可以规避 where 1 =1 产生的不确定性(虽然我做了后端校验,依然存在不确定性)。

有的同学会有疑问:假如拆分得太细,会不会增加我编写接口和 SQLMap 的工作量 ?

笔者的思路是:定制自己的代码生成器,将生成的 SQLMap  、Mapper 保证更细的颗粒度。

5 防御性编程意识

笔者刚入行的时候,只是机械性的完成任务,并没有思考代码后面的资源占用,以及有没有可能产生恶劣的影响。

随着见识更多的系统,学习开源项目,笔者慢慢培养了一种习惯:

  • 这段代码会占用多少系统资源
  • 如何规避风险 ,做好预防性编程。

其实,这和玩游戏差不多 ,在玩游戏的时,我们经常说一个词,那就是意识。

图片图片

上图,后裔跟墨子在压对面马可蔡文姬,看到小地图中路铠跟小乔的视野,方向是往下路来的,这时候我们就得到了一个信息。

知道对面的人要来抓,或者是协防,这种情况我们只有两个人,其他的队友都不在,只能选择避战,强打只会损失两名“大将”。

通过小地图的信息,并且想出应对方法,就是叫做“猜测意识”。

编程也是一样的,我们思考代码可能产生的系统资源占用,以及可能存在的风险,并做好防御性编程,就是编程的意识。

6 写到最后

人生第一次线上 OOM 事故,因我在使用 Mybatis 动态 SQL 时,没有做后端校验而出现,造成了比较坏的影响。

在后面的职业生涯里面,为了规避生产环境的事故,我试着打磨自己的编程思维,比如做好后端校验、平衡好复用和专用接口、培养防御性编程的意识 。


责任编辑:武晓燕 来源: 勇哥Java实战
相关推荐

2022-07-11 09:00:37

依赖配置文件Mybati

2023-06-05 08:36:04

SQL函数RANK()

2022-12-26 07:48:04

敏捷项目

2024-03-05 10:09:16

restfulHTTPAPI

2023-01-29 08:08:34

并发库conc通用库

2023-07-10 08:36:21

工具pptword

2022-09-26 08:49:11

Java架构CPU

2024-08-19 10:24:14

2022-12-08 10:49:43

2022-04-13 09:01:45

SASSCSS处理器

2024-11-08 08:56:01

2023-03-07 07:50:15

Transactio事务代码

2023-04-27 08:18:10

MyBatis缓存存储

2022-09-29 08:32:14

查询语句OR

2022-12-27 08:45:00

绘制菜单符号

2024-03-04 07:41:18

SpringAOPOOP​

2022-10-11 08:48:08

HTTP状态码浏览器

2022-12-14 08:31:43

#error编译命令

2022-03-05 23:29:18

LibuvwatchdogNode.js

2024-10-29 08:08:44

点赞
收藏

51CTO技术栈公众号