百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT知识 > 正文

Java vs SQL:数据业务场景下谁才是最优选项?

liuian 2025-03-12 16:41 5 浏览

应用中的数据业务通常涉及持久化数据的访问、数据计算和流程处理。数据库中的持久化数据可以用 SQL 计算,存储过程的 loop/if 语句可以进行流程处理,JDBC(含 ODBC)可以让 SQL 和应用集成,所以复杂 SQL(含存储过程)常用于数据业务开发。

但是,复杂 SQL 深度绑定数据库,存在架构上的缺陷,不能满足现代应用的要求。

复杂 SQL(及存储过程)的缺陷

难以扩展

用复杂 SQL 或存储过程实现数据业务时,压力会集中在数据库上。而数据库无法被成熟框架集成,无法利用框架实现高可用性和易扩展性,本身扩展时无论横向还是纵向的成本都很高,不符合现代应用的建设理念。

代码可移植性差

简单 SQL 通用性强,容易在数据库间移植,复杂 SQL 则不然。复杂 SQL 经常用到绑定数据库的特殊语法(含函数),代码就很难移植;存储过程甚至没有统一的标准,互相差异更大,移植也更加困难。

耦合性高

数据业务是为应用服务的,最好是处于应用内部,但存储过程存在于数据库,两者耦合性过高。数据库通常是共享的,还会造成应用间的耦合。

为了弥补复杂 SQL 的缺陷,很多应用开始使用 Java+ 简单 SQL 实现数据业务。主要有两类技术可选择:ORM 和 Stream,ORM 技术以 Hibernate 和 JOOQ 为主;Stream 是 Java8 开始提供的类库,在此基础上又发展出 Kotlin。大量的数据计算和处理压力由应用承担,Java 程序很容易被成熟框架集成,进行低成本的扩展。ORM 和 Stream 负责数据计算,基础 Java 语言负责流程处理,同为 Java 代码,移植性非常好。这种方式的耦合性也很低,数据库仅用作存储,数据业务全部集中于应用,可单独维护,不同应用间的数据业务天然隔离。

相比复杂 SQL,Java+ 简单 SQL 可以得到较好的架构优势,但也带来了新的缺陷。

Java+ 简单 SQL 的缺陷

计算能力弱导致开发困难

Hibernate 的计算能力远不如 SQL,很多简单计算都无法用 Hibernate 描述,包括 from 子查询、涉及行号的计算等。比如 SQL 很容易实现的 from 子查询:

select orderId, m from (select orderId, month(orderDate) m from OrdersEntity) t1

复杂些的计算 Hibernate 更加无法描述,比如 Oracle SQL 用窗口函数计算各组前 3 名

select * from (
select *, row_number() over (partition by Client order by Amount) rn from Orders) T where rn<=3

很多基础的日期函数和字符串函数 Hibernate 都不支持,包括日期增减、求年中第几天、求季度数,以及替换、left 截取、求 ASCII 码等。以日期增加为例:

select date_add(OrderDate,INTERVAL 3 DAY) from OrdersEntity

想实现类似的功能,只有两种办法,引入方言 SQL,或者用 Java 硬编码。前者绑定数据库,代码难以移植,偏离了 ORM 的初衷,后者代码量巨大。

JOOQ 需要程序员先设计好 SQL,再把 SQL 翻译成 JOOQ 代码,最后由引擎把 Java 代码解析成 SQL 去执行,想获得接近方言 SQL 的计算能力,就要大量使用绑定数据库的 JOOQ 函数,但这样并没有解决架构上的缺陷;想弥补架构上的缺陷,就要尽量使用通用的 JOOQ 函数,计算能力又将大幅下降。Java 语法不适合表达 SQL,为了正确表达,JOOQ 经常对函数过度封装,代码比 SQL 复杂,实际的计算能力低于 SQL。

比如,各组前 3 名:

//等价的SQL见前文,绑定Oracle的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();


明显要复杂很多。

Stream 提供了流式编程风格、Lambda 语法、集合函数,可以对简单类型(数字、字符串、日期)的集合进行简单计算,但 Stream 是通用的底层工具,在记录集合方面还不够专业,计算能力远低于 SQL。很多基本计算 Stream 实现起来都很困难,比如分组汇总:

//等价的SQL:
select year(OrderDate), sellerid, sum(Amount), count(1) from Orders group by year(OrderDate), sellerid
//Stream:
Calendar cal=Calendar.getInstance();
Map c=Orders.collect(Collectors.groupingBy(
        r->{
            cal.setTime(r.OrderDate);
            return cal.get(Calendar.YEAR)+"_"+r.SellerId;
            },
            Collectors.summarizingDouble(r->{
                return r.Amount;
            })
        )
);
    for(Object sellerid:c.keySet()){
        DoubleSummaryStatistics r =c.get(sellerid);
        String year_sellerid[]=((String)sellerid).split("_");
        System.out.println("group is (year):"+year_sellerid[0]+"\t (sellerid):"+year_sellerid[1]+"\t sum is:"+r.getSum()+"\t count is:"+r.getCount());
    


Kotlin 对 Stream 进行了改进,Lambda 表达式更加简洁,集合函数更加丰富,另外增加了热情集合计算(Eager Evaluation,与 Stream 的惰性集合计算 Lazy evaluation 相对)。但 Kotlin 也是通用的底层工具,设计目标是简单集合的计算,在记录集合方面还不够专业,计算能力依然远低于 SQL。比如基本的分组汇总:

data class Grp(var OrderYear:Int,var SellerId:Int)
data class Agg(var sumAmount: Double,var rowCount:Int)
var result=Orders.groupingBy{Grp(it.OrderDate.year+1900,it.SellerId)}
    .fold(Agg(0.0,0),{
        acc, elem -> Agg(acc.sumAmount + elem.Amount,acc.rowCount+1)
    })
.toSortedMap(compareBy { it. OrderYear}.thenBy { it. SellerId})
result.forEach{println("group fields:${it.key.OrderYear}\t${it.key.SellerId}\t aggregate fields:${it.value.sumAmount}\t${it.value.rowCount}") }


Hibernate、JOOQ、Stream、Kotlin 这些类库之所以计算能力不足,根本原因在于它们的宿主语言是静态的编译型语言,很难支持动态数据结构,表达能力受到极大限制。像 JOOQ 这种勉强支持动态数据结构的类库,必须写成静态代码 + 动态代码(字符串)混合的形式,如 T2.field("continuousdays"),业务数据只要稍显复杂,编码难度就会陡增。SQL 之所以计算能力强,根本原因在于它是动态的解释型语言,天生支持动态数据结构,表达能力的上限较高。

难以热部署导致运维复杂

编译型语言不支持热部署,修改代码后经常需要重新编译并重启应用,系统安全较差,运维复杂度较高。

esProc SPL 解决一切

实现数据业务,还有一个更好的选择:esProc SPL+ 简单 SQL。

esProc SPL 是 Java 下开源的数据处理引擎,基本功能可涵盖数据业务的每个阶段,SPL 本身具有数据计算和流程处理能力,简单 SQL 负责读写数据库,前端 Java 代码通过 JDBC 调用 SPL。

读写数据库。SPL 提供了 query 函数执行 SQL,用来将数据库的查询读为内部的序表(SPL 的结构化数据对象)。

T=db.query("select * from salesR where SellerID=?",10)

SPL 提供 update 函数将序表保存到数据库,SPL 引擎会对比修改前后的数据,自动解析为不同的 SQL DML 语句(insert、delete、update)并执行。比如,原序表为 T,经过增删改之后的序表为 NT, 将变化结果持久化到数据库:

db.update(NT:T,sales;ORDERID)

数据计算。基于序表,SPL 提供了丰富的计算函数。

过滤:T.select(Amount>1000 && Amount<=3000 && like(Client,"*bro*"))

排序:T.sort(-Client,Amount)

去重:T.id(Client)

汇总:T.max(Amount)

关联:join(Orders:o,SellerId ; Employees:e,EId).groups(e.Dept; sum(o.Amount))

流程处理。类似 Java 的 for/while/if 和存储过程的 loop/if 语句,SPL 提供了完整的流程控制能力。分支判断语句:


A

B

2


3

if T.AMOUNT>10000

=T.BONUS=T.AMOUNT*0.05

4

else if T.AMOUNT>=5000 && T.AMOUNT<10000

=T.BONUS=T.AMOUNT*0.03

5

else if T.AMOUNT>=2000 && T.AMOUNT<5000

=T.BONUS=T.AMOUNT*0.02

循环语句:


A

B

1

=db=connect("db")


2

=T=db.query@x("select * from sales where SellerID=? order by OrderDate",9)

3

for T

=A3.BONUS=A3.BONUS+A3.AMOUNT*0.01

4


=A3.CLIENT=CONCAT(LEFT(A3.CLIENT,4), "co.,ltd.")

5


 …

JDBC 接口。SPL 编写的数据业务代码可以保存在脚本文件中,Java 通过 JDBC 接口引用脚本文件名,形同调用存储过程。

Class.forName("com.esproc.jdbc.InternalDriver");
Connection conn =DriverManager.getConnection("jdbc:esproc:local:// ");
CallableStatement statement = conn.prepareCall("{call InsertSales(?, ?,?,?)}");
statement.setObject(1, d_OrderID);
statement.setObject(2, d_Client);
statement.setObject(3, d_SellerID);
statement.setObject(4, d_Amount);
statement.execute();


除了这些基础能力外,SPL+ 简单 SQL 还能克服复杂 SQL 和 Java+ 简单 SQL 的各种缺陷,扩展成本低、代码可移植性好,耦合性低、计算能力强、支持热部署。

扩展简单

用 SPL 实现数据业务,压力会集中在 SPL 上。作为 Java 类库,SPL 可以无缝被成熟的 Java 框架集成,便于横向扩展。

代码可移植好

SPL+ 简单 SQL 实现数据业务时,代码集中在数据计算和流程处理,这部分由 SPL 实现。SPL 与数据库无关,代码可在数据库间无缝移植。读写数据库由简单 SQL 实现,不涉及方言 SQL,移植起来也很方便。

SPL 的初衷之一就是便于移植,为此提供了许多工具。SPL 鼓励通过数据源名取数,移植时只要修改配置文件,不必修改代码。SPL 支持动态数据源,可通过参数或宏切换不同的数据库,从而进行更方便的移植。SPL 还提供了与具体数据库无关的标准 SQL 语法,使用 sqltranslate 函数可将标准 SQL 转为主流方言 SQL,仍然通过 query 函数执行。

耦合性低

数据库只负责存储,不负责数据业务。数据业务由 SPL+ 简单 SQL 实现,与应用处于同一位置。当数据业务发生变化时,只要修改应用中的代码,不必维护数据库,两者耦合度低。SPL 是普通的 Java 类库,可部署在不同应用中,应用间天然隔离。

计算能力强

SPL 提供了丰富的计算函数,可以用直观简短的代码实现 SQL 式计算:

子查询:Orders.new(OrderId,month(OrderDate):m).new(OrderId,m)

分组汇总: T.groups(year(OrderDate),Client; avg(Amount):amt)

各组前 3 名:Orders.groups(Client;top(3,Amount))

SPL 支持有序计算、集合计算、分步计算、关联计算,适合简化复杂的数据计算,计算能力超过 SQL。比如,最大连续上涨天数:


A

1

=orcl.query@x(select price from stock order by transDate)

2

=t=0,A1.max(t=if(price>price[-1],t+1,0))

再比如,找出司公中与其他人生日相同的员工:


A

1

=mysql5.query(“select * from emp”).group(month(birthday),day(birthday))

2

=A1.select(~.len()>1).conj()

SPL 还提供了更丰富的日期和字符串函数,在数量和功能上超过 Java 计算类库和 SQL。

值得一提的是,为了进一步提高开发效率,SPL 还创造了独特的函数语法。

支持热部署

SPL 是解释型语言,代码以脚本文件的形式外置于 JAVA,无须编译就能执行,脚本修改后立即生效,支持不停机的热部署,适合变化的业务逻辑,运维复杂度低。

SPL 还有其他优点:支持全功能调试,包括断点、单步、进入、执行到光标等;代码通常是脚本文件的形式,可存储于操作系统目录,方便进行团队代码管理;SPL 代码不依赖 JAVA,数据业务和前端代码物理分离,代码耦合性低。

SPL已开源免费,欢迎前往乾学院了解更多!

相关推荐

GANs为何引爆机器学习?这篇基于TensorFlow的实例教程为你解惑!

「机器人圈导览」:生成对抗网络无疑是机器学习领域近三年来最火爆的研究领域,相关论文层出不求,各种领域的应用层出不穷。那么,GAN到底如何实践?本文编译自Medium,该文作者以一朵玫瑰花为例,详细阐...

高丽大学等机构联合发布StarGAN:可自定义表情和面部特征

原文来源:arXiv、GitHub作者:YunjeyChoi、MinjeChoi、MunyoungKim、Jung-WooHa、SungKim、JaegulChoo「雷克世界」编译:嗯~...

TensorFlow和PyTorch相继发布最新版,有何变化

原文来源:GitHub「机器人圈」编译:嗯~阿童木呀、多啦A亮Tensorflow主要特征和改进在Tensorflow库中添加封装评估量。所添加的评估量列表如下:1.深度神经网络分类器(DNNCl...

「2022 年」崔庆才 Python3 爬虫教程 - 深度学习识别滑动验证码缺口

上一节我们使用OpenCV识别了图形验证码躯壳欧。这时候就有朋友可能会说了,现在深度学习不是对图像识别很准吗?那深度学习可以用在识别滑动验证码缺口位置吗?当然也是可以的,本节我们就来了解下使用深度...

20K star!搞定 LLM 微调的开源利器

LLM(大语言模型)微调一直都是老大难问题,不仅因为微调需要大量的计算资源,而且微调的方法也很多,要去尝试每种方法的效果,需要安装大量的第三方库和依赖,甚至要接入一些框架,可能在还没开始微调就已经因为...

大模型DeepSeek本地部署后如何进行自定义调整?

1.理解模型架构a)查看深度求索官方文档或提供的源代码文件,了解模型的结构、输入输出格式以及支持的功能。模型是否为预训练权重?如果是,可以在预训练的基础上进行微调(Fine-tuning)。是否需要...

因配置不当,约5000个AI模型与数据集在公网暴露

除了可访问机器学习模型外,暴露的数据还可能包括训练数据集、超参数,甚至是用于构建模型的原始数据。前情回顾·人工智能安全动态向ChatGPT植入恶意“长期记忆”,持续窃取用户输入数据多模态大语言模型的致...

基于pytorch的深度学习人员重识别

基于pytorch的深度学习人员重识别Torchreid是一个库。基于pytorch的深度学习人员重识别。特点:支持多GPU训练支持图像的人员重识别与视频的人员重识别端到端的训练与评估简单的re...

DeepSeek本地部署:轻松训练你的AI模型

引言:为什么选择本地部署?在AI技术飞速发展的今天,越来越多的企业和个人希望将AI技术应用于实际场景中。然而,对于一些对数据隐私和计算资源有特殊需求的用户来说,云端部署可能并不是最佳选择。此时,本地部...

谷歌今天又开源了,这次是Sketch-RNN

前不久,谷歌公布了一项最新技术,可以教机器画画。今天,谷歌开源了代码。在我们研究其代码之前,首先先按要求设置Magenta环境。(https://github.com/tensorflow/magen...

Tensorflow 使用预训练模型训练的完整流程

前面已经介绍了深度学习框架Tensorflow的图像的标注和训练数据的准备工作,本文介绍一下使用预训练模型完成训练并导出训练的模型。1.选择预训练模型1.1下载预训练模型首先需要在Tensorf...

30天大模型调优学习计划(30分钟训练大模型)

30天大模型调优学习计划,结合Unsloth和Lora进行大模型微调,掌握大模型基础知识和调优方法,熟练应用。第1周:基础入门目标:了解大模型基础并熟悉Unsloth等工具的基本使用。Day1:大模...

python爬取喜马拉雅音频,json参数解析

一.抓包分析json,获取加密方式1.抓包获取音频界面f12打开抓包工具,播放一个(非vip)视频,点击“媒体”单击打开可以复制URL,发现就是我们要的音频。复制“CKwRIJEEXn-cABa0Tg...

五、JSONPath使用(Python)(json数据python)

1.安装方法pipinstalljsonpath2.jsonpath与Xpath下面表格是jsonpath语法与Xpath的完整概述和比较。Xpathjsonpath概述/$根节点.@当前节点...

Python网络爬虫的时候json=就是让你少写个json.dumps()

大家好,我是皮皮。一、前言前几天在Python白银交流群【空翼】问了一个Python网络爬虫的问题,提问截图如下:登录请求地址是这个:二、实现过程这里【甯同学】给了一个提示,如下所示:估计很多小伙伴和...