全栈低码设计开源框架 json-script-rule 高级查询说明

高级查询

说明

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","#&currentDate@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的用法可以结合参数的说明以及本篇提到的子查询来理解其用法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

九天流云

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值