Java 下数据业务逻辑开发技术 JOOQ 和 SPL_java实现逻辑与sql实现逻辑

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
img

正文

写数据库

将处理后的结构化数据对象持久化保存到数据库,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) {


**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)**
![img](https://img-blog.csdnimg.cn/img_convert/a1e1c67865f04fb53238f6d184582cc4.png)

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

金:



Orders.forEach(r->{
Double amount=r.getValue(ORDERS.AMOUNT);
if (amount>10000) {

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
[外链图片转存中…(img-SaQp9GP8-1713299515964)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 21
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值