前言
看GraphQL不爽很久了,一直认为这是个鸡肋技术,过分复杂,功能有限,定位不清,存在安全问题。个人觉得GraphQL主要价值是两点,一是提供了一种模式,把业务逻辑前推到前端,让前端动态查询,第二个是结构化查询,输出结果和输入结构一样,所见即所得。前者个人认为只有用MyServerless的开发模式才能理想地同时解决安全和开发效率问题,后者则是本次更新内容,即在jSqlBox这个后端ORM工具添加类似GraphQL的结构化查询功能,但要做到不像GraphQL那么复杂,要让学习和使用成本最低。
顺便介绍一下jSqlBox本身,这是一个全功能开源Java数据库持久层工具,只要是与数据库操作相关的功能,jSqlBox都已具备,如DDL操作、分页、分库分表、声明式事务、关联映射查询、ActiveRecord等,所有这些功能都包含在一个1M大小的jar包中,不依赖任何第三方库。jSqlBox主要特点是Java和SQL混写,把SQL写出花来了,包括这次的主从表结构化查询也是。
使用jSqlBox只要在项目中添加以下依赖:
<dependency>
<groupId>com.github.drinkjava2</groupId>
<artifactId>jsqlbox</artifactId>
<version>5.0.15.jre8</version> <!-- 或最新版 -->
</dependency>
本次更新内容
本次5.0.15.jre8更新增加了类似GraphQL的结构化查询功能,这个功能在编程序时发现非常简单,在原有jSqlBox基础上,只需要300行代码即可实现。
jSqlBox的主从表结构化查询是依然采用jSqlBox的Java/SQL混写方式,但是这次将查询写成方法嵌套的结构,即可实现类似GraphQL的结构化查询,输入和输出的树状结构一致,所见即所得。
jSqlBox主从表结构化查询主要优点有:
1.只需要编写针对单表查询的SQL,会自动按主从关联列名生成类似“id in (?, ?...?)”的SQL片段,并将最终查询结果组装成主从表树状结构。
2.采用纯Java和原生SQL混写,功能强,学习成本低,可以同时用Java执行复杂的参数、安全检查、写数据库等业务逻辑。
3.没有直接输出为JSON,而是输出Map/List对象或Java实体对象,查询结果可以被继续修改后再发送JSON给前端。
4.可以直接利用Java的IDE格式化和语法检查功能,不需要第三方工具。格式化功能可以直观显示出树结构的嵌套层级。
5.jSqlBox的内嵌式SQL参数、分页、分库分表、拦截器、事务等依然可以直接使用。
6.不提供安全、权限功能,无学习成本。安全、权限这些功能不属于ORM工具的职能,应该由后端的SpringSecurity/Shiro工具包或独立的Serverless/JsonAPI服务器来提供。
7.如果结合我的MyServerless开源项目,可以实现前端直接在html里书写Java、定制主从表多级查询并返回json, 将业务逻辑前移到前端。
8.性能好,用"in"的方式进行数据库表的关联查询,不存在1+N问题。
9.源码简洁(实现这个功能仅用了300行源码,见GraphQuery.java),可扩充性好。
使用示例:
GraphQuery q1 = //
$("addresstb as addresses", "where id>", que("a1"), " and id<", que("a5"), pagin(1, 10), //
$1("usertb", key("user"), ms("userId", "id"), $("userroletb as userRoleList", ms("id", "userId"), //
$("roletb as roleList", ms("rid", "id"), // ms方法也可以写成DB.masterSlave()
$("roleprivilegetb as rolePrivilegeList", ms("id", "rid"), //
$1("privilegetb as privilege", ms("pid", "id")) //
)//
)//
), //
$1("select * from emailtb as email", ms("id", "userId")), //
$("addresstb as addressList", ms("id", "userId"), "and addressName like ?", par("addr%"))//
)//
);
GraphQuery q2 = //
$("usertb as u", "where id>", que("u2"), pagin(1, 10), entity(User.class), //映射成User实体Bean
$1("emailtb as emailMap", ms("id", "userId")), //$1表示是单个元素,而不是一个List
$("addresstb as addressList", ms("id", "userId"))//
);
Object result = DB.graphQuery(q1, q2); //result是查询结果
String json = JsonUtil.toJSONFormatted(result); //输出为JSON文本
以上示例详见单元测试下的GraphQueryTest.java,输出结果如下:
{
"addresses":[
{
"addressName":"address2",
"id":"a2",
"userId":"u2",
"user":{
"id":"u2",
"userName":"user2",
"userRoleList":[
{
"id":"3i6yaxy2fusjkgisyfhypkti9",
"rid":"r1",
"userId":"u2",
"roleList":[
{
"id":"r1",
"roleName":"role1",
"rolePrivilegeList":[
{
"id":"b484ze4k44xemtkstehnprhxq",
"pid":"p1",
"rid":"r1",
"privilege":{
"id":"p1",
"privilegeName":"privilege1"
}
}
]
}
]
},
{
"id":"e41dln9m4jehmc7somvu5s2pf",
"rid":"r2",
"userId":"u2",
"roleList":[
{
"id":"r2",
"roleName":"role2",
"rolePrivilegeList":[
{
"id":"dhrh5kgsod6w76e6xtl36u8b9",
"pid":"p1",
"rid":"r2",
"privilege":{
"id":"p1",
"privilegeName":"privilege1"
}
},
{
"id":"b9h2aenn6jjacns9ng5vwhaiq",
"pid":"p3",
"rid":"r2",
"privilege":{
"id":"p3",
"privilegeName":"privilege3"
}
}
]
}
]
},
{
"id":"994a5o65pfa7wx8vq99gi1lkg",
"rid":"r3",
"userId":"u2",
"roleList":[
{
"id":"r3",
"roleName":"role3",
"rolePrivilegeList":[
{
"id":"7qf9us50mw95hijwkfvuzus4q",
"pid":"p3",
"rid":"r3",
"privilege":{
"id":"p3",
"privilegeName":"privilege3"
}
}
]
}
]
}
],
"email":{
"emailName":"email3",
"id":"e3",
"userId":"u2"
},
"addressList":[
{
"addressName":"address2",
"id":"a2",
"userId":"u2"
}
]
}
},
{
"addressName":"address4",
"id":"a4",
"userId":"u4",
"user":{
"id":"u4",
"userName":"user4",
"userRoleList":[
{
"id":"bb2d1kuwvii0gpa0pxgaph8zr",
"rid":"r1",
"userId":"u4",
"roleList":[
{
"id":"r1",
"roleName":"role1",
"rolePrivilegeList":[
{
"id":"b484ze4k44xemtkstehnprhxq",
"pid":"p1",
"rid":"r1",
"privilege":{
"id":"p1",
"privilegeName":"privilege1"
}
}
]
}
]
}
],
"addressList":[
{
"addressName":"address4",
"id":"a4",
"userId":"u4"
}
]
}
}
],
"u":[
{
"id":"u3",
"userName":"user3",
"addressList":[
{
"addressName":"address3",
"id":"a3",
"userId":"u3"
}
],
"emailMap":{
"emailName":"email5",
"id":"e5",
"userId":"u3"
}
},
{
"id":"u5",
"userName":"user5",
"addressList":[
{
"addressName":"address5",
"id":"a5",
"userId":"u5"
}
]
}
]
}
用法详解(下面就是全部文档了, 一共10条,看完就学会了,看看比GraphQL简单多少!)
每个数据库表格对应一个SQL查询,写在$()或$1()方法中
$()方法的第一个参数如果没有空格,则系统自动转换为 select * from xxx
$()方法的第一个参数如果有空格,如"select id, name from tb",则系统不转换
$()方法的第一个参数的最后一个单词,将作为输出结果的键名。键名也可以用key("键名")来手工指定。
ms()方法也可以写成DB.masterSlave(),它的参数是主表和从表的键名,参数个数必须是2的倍数, ms()支持复合主键,如ms("m1", "m2", "c1","c2" )表示主表的(m1,m2)列关联到从行的(c1,c2)列, 主表还是从表的判定与数据库定义无关,而是:如果一个$()方法写在另一个$()方法里,则它就是从表, ms()方法会被编译成 " where xxId in (?, ?,...,?) " 片段。问号是根据主表的所有关联列值填充为SQL参数
$1()方法表示仅输出单个元素而不是一个列表,$1()也可以写成$("xxxxx", DB.one)
从第二个参数起,即可使用jSqlBox的内嵌sql式语法,普通文本解析为SQL片段,pagin、par、que等方法都可以使用
缺省情况下,输出结果为Map/List结构,但是如果出现DB.entity(XxxClass)参数后,这个SQL的输出结果被转换为一 个实体Bean对象。实体Bean也可以嵌套从表的内容,但是要注意Bean里要有相应的字段定义。
使用DB.graphQuery($(), $()...)可以对一个或多个$()方法进行查询。
输出对象需要输出为Json时,需要使用者自行在pom中添加JSON工具依赖,并手工进行转换,jSqlBox是个ORM工具,本身并不提供JSON工具