高级查询
说明
json-script-rule内置的查询插件支持绝大部分的sql场景,包括left join,视图,函数,union,where,子查询,分组,排序等
下面列举一个用例,展示如何使用一个复杂的json查询指令,开发者可以通过对比解析后的sql来更好的理解它的使用
{
"actions": [
{
"name": "test_function",
"get": {
"relation":{"classes":["ZsTestPO","ZsTestSon2"],
"condition":{
"where":{"eq":{"ZsTestPO.id":["#&ZsTestSon2.zs_test_id"]}}
}
},
"condition":{
"type":"or",
"matches":{
"br":[
{"br":[
{"ge":{"ZsTestPO.bonus":"#$abs(&ZsTestPO.bonus)"}},
{"eq":{"#$now()":["#&ZsTestPO.birthDay"],"#$substr(aaazzzb,3,3)":["zzz"],"test_field_a":["sss"]}}
]
}
]
}
},
"groupShow":true,
"fields": ["#$substr(&ZsTestSon2.name,1,5)@qwe","#¤tDate@kkks","#$now()@now","#kow@ZsTestPO-id=mkjk",
"ZsTestPO.id","#&ZsTestPO.name@bieming"]
}
}
]
}
解析后的sql
select substr(zs_test_son2.name,'1','5') as "qwe" ,current_date as "kkks" ,now() as "now" ,'kow' as "ZsTestPO-id=mkjk" ,zs_test.id as "ZsTestPO-id" ,zs_test.name as "bieming" from zs_test_son2 , zs_test where zs_test.id=zs_test_son2.zs_test_id and (((zs_test.bonus>=abs(zs_test.bonus) or now()=zs_test.birth_day or substr('aaazzzb','3','3')='zzz' or test_field_a='sss')))
查询表达式(R表达式)
3.0版本发布以后支持查询表达式语法,它使得开发者在进行复杂查询时更加容易的使用常量、字段、函数和别名,简化json指令的复杂性,目前查询表达式总共分为4种类型
- 常量:语法为#xx,这里的xx为常量,解析时会在常量两边分别加上单引号
- 字段:语法为#&xx,,这里的xx为字段名,字段应为实体类里所定义的字段,或是数据库函数字段
- 函数:语法为#$xx(yy,zz,......),xx为函数名,语法遵循数据库函数的语法,可嵌套使用,可自定义函数名称
- 别名:语法为#......@xx,xx为别名,用于对常量、字段以及函数进行别名的处理
视图
视图是一个sql语句,可通过实体类上的@JSRuleTable注解中的view属性来实现,通常用于一个实体类映射多个表以及多个列的应用场景。当@JSRuleTable注解中的view属性不为空时表示当前实体类是一个视图类,此时name属性定义的是这个视图的别名而不是表的名字。
@JSRuleTable(name="view_test",view="select v.pid,c.name as cname,v.detail as name2 from json_test_category c,json_test_category_detail v " +
"where c.id=v.pid and length(v.name) = ? and v.id = ?")
public class CategoryView {
@JSRuleCrudField(fk="Category")
private Long pid;
@JSRuleCrudField(name="cname")
private String name1;
private String name2;
}
通过上面的例子可以看到view其实就是一个sql语句,上面的实体类中"view"属性里用到了?符号,它表示查询条件是不确定的,这个时候需要在json脚本中声明"vp"参数来对?符号的条件进行替换。
- 注意:"view"属性中的字段的别名必须要与java字段能够对应上,例如上面的"cname"以及"name2"。
接下来用一个简单的例子来看看view是如何使用的
{
"actions":[{
"name":"get_test",
"define":{
"vp":{"CategoryView":["#$length(kkk)","4"]}
},
"get":{
"relation":{
"classes":["Category","CategoryView"]
},
"fields":["Category.id","name1","name2"],"execute":false
}
}]
}
- viewParams:别名是vp,map类型,key为视图的名字,value为要查询的条件,value可以是一个"R"表达式,数组大小需要与?符号数量相等
执行后的sql如下
select json_test_category.id as "Category-id" ,cname as "name1" ,name2 as "name2" from json_test_category , (select v.pid,c.name as cname,v.detail as name2 from json_test_category c,json_test_category_detail v where c.id=v.pid and length(v.name) = length('kkk') and v.id = '4') view_test where json_test_category.id=view_test.pid
子查询
关于子查询可以先了解JSRuleToPointer以及JSRulePointer对象,JSRuleToPointer对象表示查询条件需要连接到某一个点才能完成,这个点就是JSRulePointer对象。JSRulePointer对象表示一个点,这个点可以是一个实体类,也可以是一个子查询的结果集,也就是执行get插件后的结果,又或是一个视图,因此它有如下3种属性
- className:该属性是某个实体类的名字,它最终将转化为一个表的名字,它不是一个结果集
- action:action的名字,它将获取一个action解析后的sql语句,因此这个action应该是已经执行过的,如果仅仅只是为了生成sql,此get插件中的execute属性应该设置为false,以避免多余的查询操作
- ** view:**视图的名字,也就是@JSRuleTable注解中的name属性,"view"属性可以指向当前"action"中定义的动态视图,也可以指向实体类上面的静态视图
下面看一个复杂的json例子,看看view,join,union是如何一起使用的
{
"actions": [
{
"name": "poAction",
"get": {
"relation":{
"classes":["ZsTestPO"]
},
"execute":false,
"fields":["id"]
}
},
{
"define":{
"vp":{"view.ZsTestView":["#$substr(kkk,0,3)","#%1%"]}
},
"name": "test_view_left",
"get": {
"unions":[{
"type":"all","pointer":{"action":"poAction"}
},{
"type":"all","pointer":{"view":"view.ZSTestUpdateSon"}
}],
"relation": {
"classes":["ZsTestPO"],
"joins":[{
"class":"view.ZSTestUpdateSon",
"condition":{
"type":"or",
"matches":{
"to":{
"in":{
"ZsTestPO.test_field":{"action":"poAction"},
"view.ZSTestUpdateSon.id":{"view":"view.ZSTestSonSonView"}
}
}
}
}
},{
"class":"view.ZsTestView"
}]
},
"condition":{
"type":"or",
"matches":{
"to":{
"in":{
"ZsTestPO.test_field":{"action":"poAction"},
"view.ZSTestUpdateSon.id":{"view":"view.ZSTestSonSonView"}
}
}
}
},
"fields":["ZsTestPO.id"],
"page":{"pageNum":"1","pageSize":"10"}
}
}
]
}
生成的sql
(select zs_test.id as "ZsTestPO-id" from zs_test left join (select id from zs_test_son1_son1) zs_test_son1_son1 on zs_test.test_field in (select id as "id" from zs_test ) or zs_test_son1_son1.id in (select id from zs_test_son1_son1) left join (select * from zs_test_son1 where oh_no=substr('kkk','0','3') and id like '%1%') suibian on zs_test.id=suibian.zs_test_id where zs_test.test_field in (select id as "id" from zs_test ) or zs_test_son1_son1.id in (select id from zs_test_son1_son1) ) union all (select id as "id" from zs_test ) union all (select id from zs_test_son1_son1)
- 具体说明:借助上面视图的例子,在matches(别名where)属性中有一个to的属性,该属性的类型是JSRuleToPointer,打开这个对象可以看到里面有很多类似matches对象的条件属性。以in属性为例进行说明,其LinkedHashMap的值对应的类型是JSRulePointer,key为字段名,如下
"to":{
"in":{
"ZsTestPO.test_field":{"action":"poAction"},
"ZsTestPO.id":{"view":"view.ZsTestView"},
"view.ZSTestUpdateSon.id":{"view":"view.ZSTestSonSonView"}
}
}
解析后的sql为
zs_test.test_field in (select test_field as "sum_test_field" from zs_test ) or zs_test.id in (select zs_test_id from zs_test_son1 where oh_no=substr('kkk','0','3') and id like '%1%') or zs_test_son1_son1.id in (select id from zs_test_son1_son1)
其中子查询是poAction执行后产生的sql语句嵌入到了当前的sql语句之中,可以通过json脚本以及sql的对比来理解内置插件的使用
数据脱敏
从4.4版本开始,框架增加数据脱敏功能,脱敏配置需要在实体类中完成,代码如下
@JSRuleTable(name= "xxxx")
public class DesensitizedPO {
@JSRuleDesensitized(type=JSRuleDesensitizedEmail.class)
private String email;
@JSRuleDesensitized
private Double password;
@JSRuleCrudField(alias="mobile_phone")
@JSRuleDesensitized(startIndex=3,endIndex=4)
private String mobile;
@JSRuleDesensitized(startToken="http://",endIndex=4)
private String url;
}
最终结果如下:
email = zhangsan@163.com,z*******@163.com
password = abcd#1234,*********
mobile = 13212346789,132****6789
url = http://localhost:8080/api/json,http://*********************json
通过结果的对比很容易理解参数的作用,type=JSRuleDesensitizedEmail.class表示脱敏类型为自定义类型,这里的JSRuleDesensitizedEmail为框架内置的脱敏类型,不需要开发者去实现。
自定义脱敏规则类
需要开发者实现IJSRuleDesensitizedInfo接口,这里引用框架内部定义的JSRuleDesensitizedEmail类来举例说明,代码如下
public class JSRuleDesensitizedEmail implements IJSRuleDesensitizedInfo{
@Override
public int startIndex() {
return 1;
}
@Override
public String endToken() {
return "@";
}
}
上面的例子定义了startIndex以及endToken属性,两个属性可以搭配着使用,下面是关于各个参数的具体说明
参数说明
- replaced:脱敏时替换该字符,默认为*符号
- startIndex:从前往后开始计算表示保留多少位字符,其余字符将进行脱敏处理,默认为-1,表示不使用该属性
- endIndex:从后往前开始计算表示保留多少位字符,其余字符将进行脱敏处理,默认为-1,表示不使用该属性
- startToken: startIndex不存在时有效,从第一个字符开始,从前往后查找匹配该属性的字符串,并记录其位置下标赋值给startIndex属性,默认为空,为空时startIndex值为第一个匹配元素的下标
- endToken: endIndex不存在时有效,从最后一个字符开始,从后往前查找匹配该属性的字符串,并记录其位置下标赋值给endIndex属性,默认为空,为空时endIndex值为最后一个匹配元素的下标
- isFirst: startToken存在时有效,从前往后匹配是否是第一个匹配到的字符串并开始记录元素的下标,默认true,为false时表示从前往后最后一个匹配到的值的元素的下标
- isLast: endToken存在时有效,从后往前匹配是否是第一个匹配到的字符串并开始记录元素的下标,默认true,为false时表示从后往前最后一个匹配到的值的元素的下标
- isKeepSt:startToken存在时有效,当匹配到startToken字符串时是否对匹配的字符串同时进行脱敏处理,默认为false
- isKeepEd:endToken存在时有效,当匹配到endToken字符串时是否对匹配的字符串同时进行脱敏处理,默认为false
- regex: 正则表达式,匹配到的字符串将被替换为{@link #replaced()},当startIndex以及endIndex都有值的时候,该属性将被忽略,否则将优先执行该属性
动态脱敏配置
如果你想要更灵活的配置,那么可以动态的定义,参考对象JSRuleDefinition。比如某一个字段在实体类中已经配置了脱敏规则,但是在某一次请求的时候不需要对该字段进行脱敏处理,又或者在某一次请求的时候需要对该字段重新定义脱敏的规则,那么这个时候你只需要在JSRuleDefinition对象下的"fd"属性中增加这个字段,并对该字段重新定义脱敏规则就可以了,如下
{
"actions":[{
"name":"get_test",
"define":{
"fd":{
"imgUrl":{"regex":"[1-9]+","replaced":"!"}
}
},
"get":{
"relation":{
"classes":["Category"]
},
"fields":["id","name","#&imgUrl@asd"]
}
}]
}
上面的例子中,"fd"是字段和脱敏规则的映射集合,它是一个别名。"imgUrl"是字段名称,"key"对应的"value"是一个JSRuleDesensitizedInfo对象,该对象的属性将会取代实体类的注解属性。如果不需要对"imgUrl"字段进行脱敏处理,可以将其定义为null,如下
"define":{
"fd":{
"imgUrl":null
}
}
注意:"fd"属性中的"key"是java字段的名称,这里不可以使用别名。除此之外,如果"define"或者"fd"属性为null,则默认实体类的配置
总结:关于查询的说明到这里基本就结束了,关于unions的用法可以结合参数的说明以及本篇提到的子查询来理解其用法