动态 SQL 流程控制实用小技巧

框架最核心的动态 SQL,除了拥有和常规语言几乎一致的逻辑外,还有不少实战技巧。下面结合一些实际用法来进一步了解。

Check 条件检查语句

在 SQL 到达数据库之前,对 SQL 的参数做一次合法性验证,并提前抛出异常,而不是等数据库来抛出异常,避免获取一次无效的 connection 对象。

同一条 SQL 被多个地方调用时,#check 可以统一校验参数。表达式应描述非法情况:结果为 true 时抛出 throw 后指定信息的 CheckViolationException;结果为 false 时继续解析。

-- #check :id == null throw 'ID不能为null'
-- #check :start | type != 'Date' throw '开始时间类型必须是Date'
select * from table where ...

Switch 语法讲解

在 Rabbit SQL 框架动态 SQL 中,switch 作用类似于 java 代码的 switch 语法,在 java 中,case 多个值的写法是这样的:

switch(value){
	case "cyx":
	case "mike":
	case "bob":
	// ....
	break;
}

同样的 rabbit sql 也支持,只是写法略有不同,多个值写在同一个 case 里面,用逗号分隔:

select * from user t where t.id = :id
-- #switch :name
	-- #case cyx, mike, 'bob'
	and t.name = :name
	-- #break
-- #end

需要特别说明:如果值不是纯数字和关键字(null、blank、true、false),可以不加引号,默认为字符串,所以 'bob' 的引号不是必需的。

switch 同样支持管道处理待比较的值:

select * from user t where t.id = :id
-- #switch :name | length
	-- #case 3
	...
	-- #break
	-- #case 4
	...
	-- #break
-- #end

length 是内置管道,管道语法类似于 shell 中的管道功能,| 操作符1 | 操作符2 ,可以连续写多个,可以根据需求实现更多功能的自定义管道。

for 循环技巧

最初实现 for 循环的目的为解决 SQL in 语句预编译的问题,为了预编译参数尽可能覆盖到所有地方,例如有一个 List("cyx", "mike", "bob") ,这个参数需要用在 in 语句:

select * from user where id = :id
or name in 

这里可以先利用字符串模板占位符实现 ${!names}:

select * from user where id = :id
or name in (${!names})

字符串模板占位符支持集合。前面的 ! 表示会对集合做安全处理,非数字值会自动加引号,最终得到 ... name in ('cyx', 'mike', 'bob')。

但这还不够严谨。字符串模板无法像预编译参数那样完整处理特殊字符,最好的方式仍是使用 for 循环构建:

select * from user where id = :id
or name in (
-- #for name of :names; last as islast
   -- #if !:isLast
   :name,
   -- #else
   :name
   -- #fi
-- #done
)

最终执行 SQL 的 in 部分为 in (?, ?, ?),达到预编译效果。当然 for 的能力不止于此,具体可参考 for 循环语法详解。

插件支持

根据流程控制语法规则,动态 SQL 可以互相嵌套,但过于复杂的动态 SQL 我们不可能等到正式环境再来测试效果,这显然是不科学的。

虽然框架本身支持动态 SQL 语法检查,但不可能每次写完动态 SQL 都重启项目,那样很低效。使用插件可以直接测试动态 SQL,它很可能是目前 SQL 框架中体验最好的测试工具之一,无论是 UI 还是交互都与 IDEA 融为一体,就像 IDEA 内置功能:

插件可自动识别 SQL 中的参数,没有数据源的情况下,可以直接测试动态 SQL 计算结果,配置数据源的情况下,甚至可以在测试库执行看到具体的查询效果,在正式上线前就可以避免很多问题!