许多开源技术都能够在Java下完成以数据库为核心的事务逻辑,其中JOOQ的核算才干比Hibernate强,可移植性比MyBatis强,受到越来越多的关注。esProc SPL是新晋的数据核算言语,相同在核算才干和可移植性方面优势突出。下面临二者进行多方面的比较,从中找出开发功率更高的数据事务逻辑开发技术。JOOQ商业版首要支撑了商业数据库和存储进程,不在此次讨论范围。
言语特征
编程风格
JOOQ支撑完整的面向方针的编程风格,能够将多个方针(方法)组合起来,形成相似SQL的语法逻辑。JOOQ能够运用Java的Lambda表达式、函数调用接口和流程操控语法,理论上也支撑面向函数和面向进程,但这些表达式\语法没有为JOOQ的结构化数据方针(Result)而规划,运用时还不行便利。
SPL支撑面向方针、面向函数、面向进程的编程风格,并进行大幅简化。SPL有方针的概念,能够用点号拜访属性并进行多进程核算,但没有承继重载这些内容。SPL的Lambda表达式比SQL愈加简略易用,函数调用接口和流程操控语法专为结构化数据方针(序表)而规划,运用愈加便利。
运行模式
JOOQ是编译履行的Java代码,功用高一些,灵敏性较差。但JOOQ自身没有核算才干,履行后只生成SQL句子,再交由数据库核算并回来成果,实际功用并不高,有些事务逻辑需求重复读写数据库,功用就更差了。SPL是解释型言语,编码更灵敏,相同代码功用会差一点。但是,SPL有不依赖数据库的独立核算才干,无需重复读写数据库,内置大量时刻杂乱度更低的根底运算,核算功用经常能超过编译型言语。
外部类库
JOOQ能够引进其他恣意的第三方Java类库,用来补偿自身的短板,比方运用Stream增加自己的独立核算才干,但这些类库没有为结构化数据方针而规划,供给的功用比较有限。SPL内置专业的数据处理函数,供给了大量开发功率更高、时刻杂乱度更低的根本运算,一般不需求外部Java类库,特殊状况可在自界说函数中调用。
IDE和调试
两者都有图形化IDE和完整的调试功用。JOOQ运用Java IDE,好处是更通用,缺点是没有为数据处理做优化,无法便利地调查结构化数据方针。SPL的IDE专为数据处理而规划,结构化数据方针呈现为表格形式,调查愈加便利。
学习难度
JOOQ需求学习三种语法,SQL、通用Java、JOOQ。其中,SQL的言语才干要高于一般水平,才干转化为JOOQ语法;开发时首要运用JOOQ语法,难度不高,但转化进程较杂乱;通用Java的言语才干能够低于一般水平。SPL的方针是简化Java乃至SQL的编码,不管入门学习仍是深化开发,难度都不高。但涉及到高功用核算时需求学习较多特有的算法,难度也会进步。
代码量
SQL拿手结构化数据核算,语法较简练,代码量较低,但为了把SQL翻译成JOOQ,需求引进许多函数,存在过度封装的现象,实际代码量较大。JOOQ的流程操控要借助Java语法,但Java语法没有为结构化数据方针而规划,代码量也不低。
SPL的表达才干强于SQL,远强于JOOQ,可用更低的代码量完成结构化数据核算,SPL的流程处理句子专为结构化数据方针而规划,代码量低于Java。
结构化数据方针
结构化数据方针用于将数据库表方针化,是数据处理和事务逻辑开发的根底,专业的结构化数据方针能够便利地与数据库交换数据,支撑丰厚的核算函数,并简化流程处理的难度。
界说
JOOQ的结构化数据方针由记载和记载调集组成。记载方针的品种许多,第一类是Record方针,合适字段的数量、类型、姓名都是动态生成的状况,Record尽管很灵敏但面向方针的程度较低,用法比较费事,比方要经过getValue(M)来获得第M个字段。第二类是Record[N]方针,N从1到22,比方Record3,合适字段类型和字段数量已知但不超过22个,而字段名是动态生成的状况,Record[N]灵敏性差些但面向方针的程度稍高,用法便利些,比方能够经过valueM取得第M个字段。第三类记载方针由JOOQ的代码东西依据库表结构生成,有几个表就有几个方针,字段的数量、类型、姓名都和库表字段严格对应,比方OrdersRecord、EmployeesRecord,这种记载方针不灵敏但面向方针的程度很高,用法也很便利,能够直接经过字段名取字段。第三类对应库表,可称之为固定数据结构的记载方针,前两类一般来自对库表的查询核算,可称之为动态数据结构的记载方针。这三类比较常用,还有些不常用的记载方针,比方用户自界说记载类型UDT,这儿就不展开说了。JOOQ的记载方针品种繁复,用法差异较大,增加了开发的难度,这首要因为事务逻辑存在大量动态数据结构,而Java是编译型言语,只拿手表达固定数据结构,假如硬要表达动态数据结构,就只能规划杂乱的接口规矩,或者依据字段数量预界说大量方针。
JOOQ记载调集的品种相对较少,常用的有原生方针Result,及其父类ArrayList,有时候也会用Stream。
SPL的结构化数据方针相同由记载(Record)和记载调集(序表)组成。SPL的记载方针只需一种,首要因为SPL是解释型言语,动态数据结构和固定数据结构表达起来相同便利,接口都很简略,没必要分红多个。此外,记载方针与单记载调集尽管实质不同,但事务意义相似,用起来容易混淆。SPL是解释型言语,能够经过灵敏的接口使两者的外部用法保持共同,然后进一步进步易用性。相反,JOOQ是编译型言语,很难规划出这种灵敏的接口,只能供给两类不同的接口,别离用来处理记载方针和单记载调集。
读数据库
JOOQ读取外部数据库表,生成固定记载调集:
java.sql.Connection conn = DriverManager.getConnection(url, userName, password);
DSLContext context = DSL.using(conn, SQLDialect.MYSQL);
Result<OrdersRecord> R1=context.select().from(ORDERS).fetchInto(ORDERS);
查询外部数据库表,生成动态记载调集:
Result<Record3<Integer,String,Double>>R2=context.select(ORDERS.SELLERID,ORDERS.CLIENT,ORDERS.AMOUNT).from(ORDERS).fetch();
动态记载调集的后续用法稍显费事,但能够兼容固定记载调集,下面文章中首要用动态记载调集。
SPL读取或查询外部数据库表,生成序表:
A | |
1 | =conn=connect(“mysql8”) |
2 | =conn.query(“select * from Orders”) |
3 | =conn.query(“select SellerID,Client,Amount from Orders”) |
SPL不分固定记载调集或动态记载调集,生成方法共同,后续用法相同。
写数据库
将处理后的结构化数据方针持久化保存到数据库,JOOQ供给了三种函数,别离是insert、update、delete。修正记载r,再更新到数据库:
r.setValue(ORDERS.AMOUNT,r.getValue(ORDERS.AMOUNT).doubleValue()+100);
r.update();
上面是单条记载的更新。要注意的是,数据库表有必要有主键,自动生成的记载类才会承继UpdatableRecordImpl,只需承继UpdatableRecordImpl的记载类才支撑update函数。
批量写入数据库是数据事务逻辑常见的场景,JOOQ也能完成。批量修正记载调集T,并更新到数据库:
R1.forEach(r->{r.setValue(ORDERS.AMOUNT,r.getValue(ORDERS.AMOUNT).doubleValue()+100);});
R1.forEach(r->{r.update();});
上面代码循环记载调集,手艺更新每一条记载,然后完成对整体调集的更新。能够看到,JOOQ经过硬写代码完成批量写入,没有进行封装,许多时候不便利。假如一批记载既有修正又有新增还有删去,就有必要区别三类记载,再用不同的函数循环写入,常见的方法是承继记载类,新加一个“标识”属性予以区别,或者保存一个未修正的原始记载调集T,将修正后的调集NT与原始调集进行手艺比对。不管哪种方法,手艺完成的进程都很费事。
SPL对数据库的写入进行了封装,只用一个update函数就完成单条和批量记载的新增、修正、删去,且支撑混合更新。比方:原序表为T,经过增删改一系列处理后的序表为NT,将改变成果持久化到数据库的orders表:
conn.update(NT:T,orders)
拜访字段
JOOQ读取单条记载的Client字段:
R1.get(0).getClient();
R1.get(0).get(ORDERS.CLIENT);
上面代码体现了JOOQ的核心优势:支撑朴实的面向方针的字段拜访方法,不掺杂字符串、数字常量,或其他非Java的表达式,代码风格高度共同。惋惜之处在于,上面代码只适用于固定结构化数据方针。假如是查询核算生成的动态记载方针,就只能运用字符串字段名或数字序号拜访字段:
R2.get(0).get("Client");
R2.get(0).get(1);
动态记载方针愈加遍及,上面的字段拜访方法不算朴实的面向方针,代码风格不共同,不支撑自动补全,编写时遍及费事。
SPL支撑朴实的面向方针的字段拜访方法,不分固定或动态,编写时遍及便利:
T(1).Client
当然也支撑字符串字段名或数字序号拜访字段:
T(1).field(2)
T(1).field("Client")
SPL在面向方针方面愈加朴实,风格更共同,编写代码愈加便利。此外,SPL供给了许多JOOQ不支撑的便利功用:默认字段名,能够用点号直接拜访,比方取第2个字段:T(1).#2;取多个字段,回来调集的调集:T.([Client,Amount])
有序拜访
有序拜访是事务逻辑开发的难点之一,JOOQ的记载调集承继自Java的有序调集ArrayList,具备必定的有序拜访才干,支撑按下标取记载和按区间取记载:
R.get(3)
R.subList(3,5);
再进一步的功用,就需求硬编码完成了,比方后3条:
Collections.reverse(R);
R.subList(0,3);
至于按方位调集取记载、步进步记载等功用,硬编码就更费事了。
SPL序表相同是有序调集,供给了次序相关的根本功用,比方按下标取、按区间取:
T(3)
T.to(3,5)
序表是专业的结构化数据方针,许多次序相关的高档功用JOOQ Result没有支撑,序表则直接供给了,比方按倒数序号取记载,能够直接用负号表明:
T.m(-3) //倒数第3条
T.m(to(-3,-5)) //倒数区间
再比方按方位调集取记载、步进步记载:
T.m(1,3,5,7:10) //序号是1、3、5、7-10的记载
T.m(-1,-3,-5) //倒数第1,3,5条
T.step(2,1) //每2条取第1条(等价于奇数方位)
结构化数据核算
结构化数据核算才干是数据事务逻辑的核心功用,下面从简略到杂乱选取几个常见题目,比较JOOQ和SPL的核算代码。
改名
//等价的SQL
select SellerID eid,Amount amt from Orders
//JOOQ
context.select(ORDERS.SELLERID.as("eid"),ORDERS.AMOUNT.as("amt")).from(ORDERS).fetch()
//SPL
Orders.new(SellerID:EID, Amount:amt)
JOOQ的语法逻辑与SQL根本共同,能够达到用面向方针的方法模仿SQL的意图,这是JOOQ的重要长处。相应的也有缺点,JOOQ的一项运算需求多个函数的组合才干完成,每个函数都有自己的参数和语法规矩,学习和编写难度较大。此外,许多函数里的字段名有必要附带表名,即便单表核算也是如此,这阐明JOOQ的语法不行专业,还有很大的改进空间。
SPL直接用面向方针的语法完成核算,一项运算对应一个函数,引证字段不用附带表名,语法更专业,代码更简略。
条件查询
//等价的SQL
select * from Orders where
((SellerID=2 and Amount<3000) or (SellerID=3 and Amount>=2000 and Amount<5000))
and
year(OrderDate)>2010
//JOOQ
context.select().from(ORDERS)
.where(
((ORDERS.SELLERID.equal(2).and(ORDERS.AMOUNT.lessThan(3000.0)))
.or((ORDERS.SELLERID.equal(3).and(ORDERS.AMOUNT.greaterOrEqual(2000.0).and(ORDERS.AMOUNT.lessThan(5000.0))))))
.and(year(ORDERS.ORDERDATE).greaterThan(2012)))
.fetch();
//SPL
Orders.select(
((SellerID==2 && Amount<3000) || (SellerID==3 && Amount>=2000 && Amount<5000))
&&
year(OrderDate)>2010)
SQL的条件表达式自身满足简略,JOOQ尽管在模仿SQL,但对条件表达式进行了过度封装,函数数量过多,多层括号难阅读,远不如SQL好了解。SPL用一个函数完成条件查询,条件表达式简略易读。
分组汇总
//等价的SQL:
select Client, extract(year from OrderDate) y,count(1) cnt
from Orders
group by Client, extract(year from OrderDate)
having amt<20000
//JOOQ
context.select(ORDERS.CLIENT,year(ORDERS.ORDERDATE).as("y"),sum(ORDERS.AMOUNT).as("amt"),count(one()).as("cnt"))
.from(ORDERS)
.groupBy(ORDERS.CLIENT,year(ORDERS.ORDERDATE))
.having(field("amt").lessThan(20000)).fetch();
//SPL
Orders.groups(Client,year(OrderDate):y;sum(Amount):amt,count(1):cnt)
.select(amt<20000)
为了模仿SQL,JOOQ运用了许多函数,规矩很杂乱,导致代码过长。SPL直接用面向方针的语法,规矩简略,代码更短。
前面都是较简略核算,相似的核算还包含排序、去重、相关、调集交并差等核算,这儿不再一一列举,总的来说,JOOQ进行简略核算时比SQL和SPL代码长,许多时候不易了解,开发功率较低。
各组前3名
//等价的SQL
select * from (select *, row_number() over (partition by Client order by Amount) rn from Orders) T where rn<=3
//JOOQ
WindowDefinition CA = name("CA").as(partitionBy(ORDERS.CLIENT).orderBy(ORDERS.AMOUNT));
context.select().from(select(ORDERS.ORDERID,ORDERS.CLIENT,ORDERS.SELLERID,ORDERS.AMOUNT,ORDERS.ORDERDATE,rowNumber().over(CA).as("rn")).from(ORDERS).window(CA) ).where(field("rn").lessOrEqual(3)).fetch();
//SPL
Orders.group(Client).(~.top(3;Amount)).conj()
这道题目稍有难度,JOOQ尽管模仿出了SQL,但运用了许多函数,代码长度远超SQL,语法也越来越不像SQL,编写了解愈加困难。SPL先对客户分组,再求各组(即~)的前3名,最后兼并各组核算成果,不只代码更简略,且更易了解。
JOOQ运用了窗口函数,只合适特定版别的数据库,比方MySQL8,不能通用于其他版别的数据库,要想在MySQL5下完成相同的核算,代码改动十分费事。SPL有独立核算才干,代码可通用于任何数据库。
某支股票最大接连上涨天数
JOOQ:
WindowDefinition woDay1 = name("woDay").as(orderBy(APPL.DAY));
Table<?>T0=table(select(APPL.DAY.as("DAY"),when(APPL.PRICE.greaterThan(lag(APPL.PRICE).over(woDay1)),0).otherwise(1).as("risingflag")).from(APPL).window(woDay1)).as("T0");
WindowDefinition woDay2 = name("woDay1").as(orderBy(T0.field("DAY")));
Table<?>T1=table(select(sum(T0.field("risingflag").cast(java.math.BigDecimal.class)).over(woDay2).as("norisingdays")).from(T0).window(woDay2)).as("T1");
Table<?>T2=table(select(count(one()).as("continuousdays")).from(T1).groupBy(T1.field("norisingdays"))).as("T2");
Result<?> result=context.select(max(T2.field("continuousdays"))).from(T2).fetch();
这个问题难度较高,需求归纳运用多种简略核算。JOOQ很难直接表达接连上涨的概念,只能运用技巧变相完成,即经过累计不涨天数来核算接连上涨天数。具体是,先按时刻次序给每条记载打涨跌符号risingflag,假如跌落,则标为1,假如上涨,则标为0;再按时刻次序累计每条记载的不涨天数norisingdays,只需当时记载跌落时,这个数字才会变大,假如当时记载上涨,则这个数字不变;再按不涨天数norisingdays分组,求各组记载数,明显,接连跌落的一批记载的norisingdays不同,每条记载都会分到不同的组,该组计数为1,这个值不是解题方针,而接连上涨的一批记载的norisingdays相同,能够分到同一组,该组计数即接连上涨的天数,这个值是解题方针;最后用max函数求出最大的接连上涨天数。
JOOQ的编程进程是先写SQL,再翻译成JOOQ,关于简略核算来说,SQL比较好写,翻译也不会太难,但关于本题这种归纳性核算来说,核算逻辑的技巧性比较强,SQL不好写,翻译的难度更大。此外,JOOQ表面上是便利调试的Java,但实质却是SQL,和SQL一样难以调试,这又为将来的保护作业埋下了大坑。
SPL代码简略多了:
APPL.sort(day).group@i(price<price[-1]).max(~.count())
这条SPL句子的核算逻辑和JOOQ是相同的,也是将连涨记载分到同一组中再求最大的组成员数,但表达起来要便利许多。group@i()表明遍历序表,假如契合条件则开端新的一组(并使之前的记载分到同一组),price<price[-1]这个条件表明股价跌落,则之前股价上涨的记载会分到同一组。[-1]表明上一条,是相对方位的表明方法,price[-1]表明上一个交易日的股价,比整体移行(lag.over)更直观。
相对方位归于有序核算,SPL是专业的结构化核算言语,支撑有序核算,代码因而更简略。除了有序调集,SPL还能够简化多种杂乱核算,包含多进程核算、调集核算、杂乱的相关核算。相反,这几类核算都是JOOQ不拿手的,一般要经过特殊技巧完成,代码很难写。
SPL函数选项和层次参数
值得一提的是,为了进一步进步开发功率,SPL还供给了独特的函数语法。有大量功用相似的函数时,JOOQ只能用不同的姓名或者参数进行区别,运用不太便利。而SPL供给了十分独特的函数选项,使功用相似的函数能够共用一个函数名,只用函数选项区别差别。比方,select函数的根本功用是过滤,假如只过滤出契合条件的第1条记载,可运用选项@1:
T.select@1(Amount>1000)
对有序数据用二分法进行快速过滤,运用@b:
T.select@b(Amount>1000)
函数选项还能够组合搭配,比方:
Orders.select@1b(Amount>1000)
有些函数的参数很杂乱,可能会分红多层。JOOQ对此并没有特别的语法方案,只能拆成多个函数互相嵌套,极力模仿成SQL语法,导致代码冗长繁琐。而SPL创造性地发明了层次参数简化了杂乱参数的表达,经过分号、逗号、冒号自高而低将参数分为三层。比方相关两个表:
join(Orders:o,SellerId ; Employees:e,EId)
流程处理
JOOQ支撑部分存储进程语法,包含循环句子和判别句子,但这归于商业版功用,且权限要求高、安全隐患大,难以移植,一般很少用到。除了存储进程,JOOQ还能够运用Java完成流程处理,对数据库没有权限要求,安全隐患小,且可无缝移植。比方,依据规矩核算奖金:
Orders.forEach(r->{
Double amount=r.getValue(ORDERS.AMOUNT);
if (amount>10000) {
r.setValue(ORDERS.BONUS), amount * 0.05);
}else if(amount>=5000 && amount<10000){
r.setValue(ORDERS.BONUS),amount*0.03);
}else if(amount>=2000 && amount<5000){
r.setValue(ORDERS.BONUS),amount*0.02);
}
});
forEach循环函数针对JOOQ的结构化数据方针进行了优化,能够经过Lambda表达式简化循环结构的界说,能够便利地处理调集方针的每个成员(代码中的循环变量r)。forEach函数合作Lambda语法,整体代码要比传统循环句子简略些。但也应该注意到,forEach函数里运用字段需求附带循环变量名,对单表核算来说是剩余的,相同运用Lambda语法的SQL就能够省掉变量名。此外,界说循环变量名也是剩余的,SQL就不用界说。这些缺点都阐明JOOQ在流程处理方面还不行专业,代码还有很大的优化空间。
SPL也有针对结构化数据方针进行优化的循环函数,直接用括号表明。相同依据规矩核算奖金:
Orders.(Bonus=if(Amount>10000,Amount*0.05,
if(Amount>5000 && Amount<10000, Amount*0.03,
if(Amount>=2000 && Amount<5000, Amount*0.02)
)))
SPL的循环函数相同支撑Lambda表达式,并且接口更简略,不用界说循环变量,运用字段时不用引证变量名,比JOOQ更便利,专业性也更强。除了循环函数,SPL还有更多专业的流程处理功用,比方:每轮循环取一批而不是一条记载;某字段值改变时循环一轮。
SPL专业的流程处理功用,合作专业的结构化数据方针和结构化数据核算才干,可大幅进步数据事务逻辑的开发功率。一个完整的例子:核算出奖金,并向数据库刺进新记载。JOOQ需求生成多个文件,编写大段代码才干完成,SPL就简略多了:
A | B | C | |
1 | =db=connect@e(“dbName”) | /衔接数据库,开启事务 | |
2 | =db.query@1(“select sum(Amount) from sales where sellerID=? and year(OrderDate)=? and month(OrderDate)=?”, p_SellerID,year(now()),month(now())) | /查询当月销售额 | |
3 | =if(A2>=10000 :200, A2<10000 && A2>=2000 :100, 0) | /本月累计奖金 | |
4 | =p_Amount*0.05 | /本单固定奖金 | |
5 | =BONUS=A3+A4 | /总奖金 | |
6 | =create(ORDERID,CLIENT,SELLERID,AMOUNT,BONUS,ORDERDATE) | /创立订单的数据结构 | |
7 | =A6.record([p_OrderID,p_Client,p_SellerID,p_Amount,BONUS,date(now())]) | /生成一条订单记载 | |
8 | >db.update@ik(A7,sales;ORDERID) | /尝试写入库表 | |
9 | =db.error() | /入库成果 | |
10 | if A9==0 | >A1.commit() | /成功,则提交事务 |
11 | else | >A1.rollback() | /失利,则回滚事务 |
12 | >db.close() | /封闭数据库衔接 | |
13 | return A9 | /回来入库成果 |
运用SPL的流程处理句子,能够完成存储进程的所有才干,包含游标的循环和判别。SPL不依赖数据库,不需求数据库权限,没有安全隐患,相当于库外的存储进程,一起,这些功用也都是开源的。
使用结构
Java集成
JOOQ自身就是Java,可被其他Java代码直接调用。
SPL是基于JVM的数据核算言语,供给了易用的JDBC接口,可被JAVA代码无缝集成。比方,将事务逻辑代码存为脚本文件,在JAVA中以存储进程的形式调用文件名:
Class.forName("com.esproc.jdbc.InternalDriver");
Connection connection =DriverManager.getConnection("jdbc:esproc:local://");
Statement statement = connection.createStatement();
ResultSet result = statement.executeQuery("call genBonus()");
热布置
JOOQ(Java)是编译型言语,不支撑热布置,修正代码后需求从头编译并重启整个使用,加大了保护难度,降低了系统稳定性。
SPL是解释型言语,代码以脚本文件的形式外置于JAVA,支撑热布置,修正后不用编译,也不用重启使用。由于SPL代码不依赖JAVA,事务逻辑和前端代码物理别离,耦合性也更低。
代码移植
JOOQ的部分代码能够移植,比不行移植的MyBatis便利。比方事务逻辑中常用于分页的limit(M).offset(N),在Oracle11g数据库下会被翻译为rownum子查询;假如数据库改为MSSQL2012,只需从头生成并布置实体类,不用修正事务逻辑,相同的代码就会翻译成offset next句子。
能够移植的代码毕竟是少量,大部分JOOQ代码都是不能够移植的,比方前面例子里的窗口函数。移植时需求读懂原JOOQ代码,反翻译成原SQL,再改成新SQL,最后翻译成新JOOQ代码,进程较繁难度较高。事务逻辑遍及具有杂乱性,移植作业就更难了。
SPL具有独立核算才干,不用借用SQL,凭借丰厚的内置函数库就能完成杂乱的结构化数据核算,核算代码可在数据库间无缝移植。在数据库取数代码中,SPL也要履行方言SQL生成序表,尽管取数SQL比较简略,手艺移植不难,但仍有必定作业量,为了使取数代码便于移植,SPL专门供给了不依赖特定数据库的通用SQL,可在主流数据库间无缝移植。
经过多方面的比较可知:JOOQ能够进行较简略的查询统计,但关于较杂乱的事务逻辑开发就显得比较繁琐,尤其是有序核算、多进程核算、调集核算、杂乱的相关查询,存在翻译SQL的作业量大,代码冗长,难以修正,难以移植等问题。SPL语法简练、表达功率高、代码移植便利,结构化数据方针更专业,函数更丰厚且核算才干更强,流程处理更便利,开发功率远高于JOOQ。
资料
- SPL下载
- SPL源代码