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

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

ccwgpt 2025-03-06 12:51 61 浏览 0 评论

应用中的数据业务通常涉及持久化数据的访问、数据计算和流程处理。数据库中的持久化数据可以用 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已开源免费,欢迎前往乾学院了解更多!

相关推荐

定时任务工具,《此刻我要...》软件体验

之前果核给大家介绍过一款小众但实用的软件——小说规则下载器,可以把网页里的小说章节按照规则下载到本地,非常适合喜欢阅读小说的朋友。有意思的是,软件作者当时看到果核写的体验内容后,给反推荐到他的帖子里去...

前端定时任务的神库:Node-cron,让你的项目更高效!

在前端开发中,定时任务是一个常见的需求。无论是定时刷新数据、轮询接口,还是发送提醒,都需要一个可靠且灵活的定时任务解决方案。今天,我要向大家介绍一个强大的工具——Node-cron,它不仅能解决定时任...

Shutter Pro!一款多功能定时执行任务工具

这是一款可以在电脑上定时执行多种任务的小工具,使用它可以根据时间,电量等来设定一些定时任务,像定时打开程序、打开文件,定时关机重启,以及定时弹窗提醒等都可以轻松做到。这是个即开即用的小工具,无需安装,...

深度解析 Redis 缓存击穿及解决方案

在当今互联网大厂的后端开发体系中,Redis缓存占据着极为关键的地位。其凭借高性能、丰富的数据类型以及原子性操作等显著优势,助力众多高并发系统从容应对海量用户的访问冲击,已然成为后端开发从业者不可或...

从零搭建体育比分网站完整步骤(比较好的体育比分软件)

搭建一个体育比分网站是一个涉及前端、后端、数据源、部署和维护的完整项目。以下是从零开始搭建的详细流程:一、明确项目需求1.功能需求:实时比分展示(如足球、篮球、网球等)支持多个联赛和赛事历史数据查询比...

告别复杂命令行:GoCron 图形界面让定时任务触手可及

如果你是运维人员或者经常接触一些定时任务的配置,那么你一定希望有一款图形界面来帮助你方便的轻松配置定时任务,而GoCron就是这样一款软件,让你的配置可视化。什么是GoCron从名字你就可以大概猜到,...

Java任务管理框架核心技术解析与分布式高并发实战指南

在当今数字化时代,Java任务管理框架在众多应用场景中发挥着关键作用。随着业务规模的不断扩大,面对分布式高并发的复杂环境,掌握其核心技术并进行实战显得尤为重要。Java任务管理框架的核心技术涵盖多个方...

链表和结构体实现:MCU软件定时器(链表在单片机中的应用)

在一般的嵌入式产品设计中,介于成本、功耗等,所选型的MCU基本都是资源受限的,而里面的定时器的数量更是有限。在我们软件设计中往往有多种定时需求,例如脉冲输出、按键检测、LCD切屏延时等等,我们不可能...

SpringBoot定时任务(springboot定时任务每小时执行一次)

前言在我们开发中,经常碰到在某个时间点去执行某些操作,而我们不能人为的干预执行,这个时候就需要我们使用定时任务去完成该任务,下面我们来介绍下载springBoot中定时任务实现的方式。定时任务实现方式...

定时任务新玩法!systemd timer 完整实战详解

原文链接:「链接」Hello,大家好啊!今天给大家带来一篇使用systemdtimer实现定时任务调度的详细实战文章。相比传统的crontab,systemdtimer更加现代化、结构清晰...

Celery与Django:打造高效DevOps的定时任务与异步处理神器

本文详细介绍了Celery这一强大的异步任务队列系统,以及如何在Django框架中应用它来实现定时任务和异步处理,从而提高运维开发(DevOps)的效率和应用性能。下面我们先认识一下Cele...

订单超时自动取消的7种方案,我用这种!

前言在电商、外卖、票务等系统中,订单超时未支付自动取消是一个常见的需求。这个功能乍一看很简单,甚至很多初学者会觉得:"不就是加个定时器么?"但真到了实际工作中,细节的复杂程度往往会超...

裸机下多任务框架设计与实现(gd32裸机配置lwip 网络ping不通)

在嵌入式系统中,特别是在没有操作系统支持的裸机环境下,实现多任务执行是一个常见的挑战。本文将详细介绍一种基于定时器的多任务框架设计,通过全局时钟和状态机机制,实现任务的非阻塞调度,确保任务执行中不会出...

亿级高性能通知系统构建,小白也能拿来即用

作者介绍赵培龙,采货侠JAVA开发工程师分享概要一、服务划分二、系统设计1、首次消息发送2、重试消息发送三、稳定性的保障1、流量突增2、问题服务的资源隔离3、第三方服务的保护4、中间件的容错5、完善...

运维实战:深度拆解Systemd定时任务原理,90%的人不知道的玩法

运维实战:深度拆解Systemd定时任务原理,90%的人不知道的高效玩法一、Systemd定时任务的核心原理Systemd定时任务是Linux系统中替代传统cron的现代化解决方案,通过...

取消回复欢迎 发表评论: