您的位置 首页 java

Java嵌入数据引擎:从SQLite到SPL

可以在 Java 应用中嵌入的数据引擎看起来比较丰富,但其实并不容易选择。 Redis 计算能力很差,只适合简单查询的场景。 Spark 架构复杂沉重,部署维护很是麻烦。H2\HSQLDB\Derby 等内嵌数据库倒是架构简单,但计算能力又不足,连基本的窗口函数都不支持。

相比之下, SQLite 在架构性和计算能力上取得了较好的平衡,是应用较广的 Java 嵌入数据引擎。

SQLite适应常规基本应用场景

SQLite 架构简单,其核心虽然是 C 语言 开发的,但封装得比较好,对外呈现为一个小巧的 Jar 包,能方便地集成在 Java 应用中。SQLite 提供了 jdbc 接口,可以被 Java 调用:

 Connection connection = DriverManager.getConnection("jdbc:sqlite::memory:");
 Statement  st = connection.createStatement();
st.execute("restore from d:/ex1");
ResultSet rs = st.executeQuery("SELECT * FROM orders");  

SQLite 提供了标准的 SQL 语法,常规的数据处理和计算都没有问题。特别地,SQLite 已经能支持窗口函数,可以方便地实现很多组内运算,计算能力比其他内嵌数据库更强。

SELECT x, y, row_ number ()OVER (ORDER BY y) AS row_number FROM t0 ORDER BY x;

SELECT a, b, group_concat(b, ‘.’) OVER (ORDER BY a ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS group_concat FROM t1;

SQLite面对复杂场景尚有不足

SQLite 的优点亮眼,但对于复杂应用场景时还是有些缺点。

Java 应用可能处理的数据源多种多样,比如 csv 文件、RDB、Excel、 Restful ,但 SQLite 只处理了简单情况,即对 csv 等文本文件提供了直接可用的命令行加载程序:

.import –csv –skip 1 –schema temp /Users/scudata/somedata.csv tab1

对于其他大部分数据源,SQLite 都没有提供方便的接口,只能硬写代码加载数据,需要多次调用命令行,整个过程很繁琐,时效性也差。

以加载 RDB 数据源为例,一般的做法是先用 Java 执行命令行,把 RDB 库表转为 csv;再用 JDBC 访问 SQLite,创建表结构;之后用 Java 执行命令行,将 csv 文件导入 SQLite;最后为新表建索引,以提高性能。这个方法比较死板,如果想灵活定义表结构和表名,或通过计算确定加载的数据,代码就更难写了。

类似地,对于其他数据源, SQL ite 也不能直接加载,同样要通过繁琐地转换过程才可以。

SQL 接近自然语言,学习门槛低,容易实现简单的计算,但不擅长复杂的计算,比如复杂的集合计算、有序计算、关联计算、多步骤计算。SQLite 采用 SQL 语句做计算,SQL 优点和缺点都会继承下来,勉强实现这些复杂计算的话,代码会显得繁琐难懂。

比如,某只 股票 最长的上涨天数,SQL 要这样写:

 select max(continuousDays)-1
from (select count(*) continuousDays
from (select sum(changeSign) over(order by tradeDate) unRiseDays
from (select tradeDate,
case when price>lag(price) over(order by tradeDate) then 0 else 1 end changeSign from AAPL) )
group by unRiseDays)  

这也不单是 SQLite 的难题,事实上,由于集合化不彻底、缺乏序号、缺乏对象引用等原因,其他 SQL 数据库 也不擅长这些运算。

业务逻辑由结构化数据计算和流程控制组成,SQLite 支持 SQL,具有结构化数据计算能力,但 SQLite 没有提供存储过程,不具备独立的流程控制能力,也就不能实现一般的业务逻辑,通常要利用 Java 主程序的判断和循环语句。由于 Java 没有专业的结构化数据对象来承载 SQLite 数据表和记录,转换过程麻烦,处理过程不畅,开发效率不高。

前面提过,SQLite 内核是 C 程序,虽然可以被集成到 Java 应用中,但并不能和 Java 无缝集成,和 Java 主程序交换数据时要经过耗时的转换才能完成,在涉及数据量较大或交互频繁时性能就会明显不足。同样因为内核是 C 程序,SQLite 会在一定程度上破坏 Java 架构的一致性和健壮性。

对于 Java 应用来讲,原生在 JVM 上的 esProc SPL 是更好的选择。

SPL全面支持各种数据源

esProc SPL 是 JVM 下开源的嵌入数据引擎,架构简单,可直接加载数据源,可以通过 JDBC 接口被 Java 集成调用,并方便地进行后续计算。

SPL 架构简单, 无须独立服务,只要引入 SPL 的 Jar 包,就可以部署在 Java 环境中。

直接加载数据源 ,代码简短,过程简单,时效性强。比如加载 Oracle

A

1

=connect(“orcl”)

2

=A1.query@x(“select OrderID,Client,SellerID,OrderDate, Amount from orders order by OrderID”)

3

>env(orders,A2)

对于 SQLite 擅长加载的 csv 文件,SPL 也可以直接加载,使用内置函数而不是外部命令行,稳定且效率高,代码更简短:

=T(“/Users/scudata/somedata.csv”)

多种外部数据源 。除了 RDB 和 csv,SPL 还直接支持 txt\xls 等文件, MongoDB Hadoop 、redis、 ElasticSearch Kafka Cassandra NoSQL ,以及 WebService XML、Restful Json 等多层数据。比如,将 HDSF 里的文件加载到内存:

A

1

= hdfs _open(;”hdfs://192.168.0.8:9000″)

2

=hdfs_file(A1,”/user/Orders.csv”:”GBK”)

3

=A2.cursor@t()

4

=hdfs_close(A1)

5

>env(orders,A4)

JDBC 接口可以方便地集成 。加载的数据量一般比较大,通常在应用的初始阶段运行一次,只须将上面的加载过程存为 SPL 脚本文件,在 Java 中以存储过程的形式引用脚本文件名:

 Class.forName("com.esproc.jdbc.InternalDriver");
Connection conn =DriverManager.getConnection("jdbc:esproc:local://");
CallableStatement statement = conn.prepareCall("{call init()}");
statement.execute();  

SPL的计算能力更强大

SPL 提供了丰富的计算函数,可以轻松实现日常计算。SPL 支持多种高级语法,大量的日期函数和 字符串函数 ,很多用 SQL 难以表达的计算,用 SPL 都可以轻松实现,包括复杂的有序计算、集合计算、分步计算、关联计算,以及带流程控制的业务逻辑。

丰富的计算函数 。SPL 可以轻松实现各类日常计算:

A

B

1

=Orders.find(arg_OrderIDList)

// 多键值查找

2

=Orders.select(Amount>1000 && like(Client,\”*S*\”))

// 模糊查询

3

= Orders.sort(Client,-Amount)

// 排序

4

= Orders.id(Client)

// 去重

5

=join(Orders:O,SellerId; Employees:E,EId)
.new(O.OrderID, O.Client,O.Amount,E.Name,E.Gender,E.Dept)

// 关联

标准 SQL 语法 。SPL 也提供了 SQL-92 标准的语法,比如分组汇总:

$select year(OrderDate) y,month(OrderDate) m, sum(Amount) s,count(1) c
from {Orders}
Where Amount>=? and Amount<? ;arg1,arg2

函数选项、层次参数等方便的语法 。功能相似的函数可以共用一个函数名,只用函数选项区分差别,比 SQL 更加灵活方便。比如 select 函数 的基本功能是过滤,如果只过滤出符合条件的第 1 条记录,可使用选项 @1:

T.select@1(Amount>1000)

二分法排序,即对有序数据用二分法进行快速过滤,使用 @b:

T.select@b(Amount>1000)

有序分组,即对分组字段有序的数据,将相邻且字段值相同的记录分为一组,使用 @b:

T.groups@b(Client;sum(Amount))

函数选项还可以组合搭配,比如:

Orders.select@1b(Amount>1000)

结构化运算函数的参数有些很复杂,比如 SQL 就需要用各种关键字把一条语句的参数分隔成多个组,但这会动用很多关键字,也使语句结构不统一。SPL 使用层次参数简化了复杂参数的表达,即通过 分号 逗号 、冒号自高而低将参数分为三层:

join(Orders:o,SellerId ; Employees:e,EId)

更丰富的日期和字符串函数 。除了常见函数,比如日期增减、截取 字符串 ,SPL 还提供了更丰富的日期和字符串函数,在数量和功能上远远超过了 SQL,同样运算时代码更短。比如:

季度增减:elapse@q(“2020-02-27”,-3) // 返回 2019-05-27

N 个工作日之后的日期:workday(date(“2022-01-01”),25) // 返回 2022-02-04

字符串类函数,判断是否全为数字:isdigit(“12345”) // 返回 true

取子串前面的字符串: substr @l(“abCDcdef”,”cd”) // 返回 abCD

按竖线拆成字符串数组:”aa|bb|cc”.split(“|”) // 返回 [“aa”,”bb”,”cc”]

SPL 还支持年份增减、求季度、按正则表达式拆分字符串、拆出 SQL 的 where 或 select 部分、拆出单词、按标记拆 HTML 等大量函数。

简化有序运算。 涉及跨行的有序运算,通常都有一定的难度,比如比上期和 同期比 。SPL 使用 “字段 [相对位置]” 引用跨行的数据,可显著简化代码,还可以自动处理数组越界等特殊情况,比 SQL 窗口函数更加方便。比如,追加一个计算列 rate,计算每条订单的金额增长率:

=T.derive(AMOUNT/AMOUNT[-1]-1: rate)

综合运用位置表达式和有序函数,很多 SQL 难以实现的有序运算,都可以用 SPL 轻松解决。比如,根据 考勤表 ,找出连续 4 周每天均出勤达 7 小时的学生:

A

1

=Student.select(DURATION>=7).derive(pdate@w(ATTDATE):w)

2

=A1.group@o(SID;~.groups@o(W;count(~):CNT).select(CNT==7).group@i(W-W[-1]!=7).max(~.len()):weeks)

3

=A2.select(weeks>=4).(SID)

简化集合运算 ,SPL 的集合化更加彻底,配合灵活的语法和强大的集合函数,可大幅简化复杂的集合计算。比如,在各部门找出比本部门平均年龄小的员工:

A

1

=Employees.group(DEPT; (a=~.avg(age(BIRTHDAY)),~.select(age(BIRTHDAY)<a)):YOUNG)

2

=A1.conj(YOUNG)

计算某支股票最长的连续上涨天数:

A

1

=a=0,AAPL.max(a=if(price>price[-1],a+1,0))

简化关联计算 。SPL 支持对象引用的形式表达关联,可以通过点号直观地访问关联表,避免使用 JOIN 导致的混乱繁琐,尤其适合复杂的多层关联和自关联。比如,根据员工表计算女经理的男员工:

=employees.select(gender:”male”,dept.manager.gender:” female “)

方便的分步计算 ,SPL 集合化更加彻底,可以用变量方便地表达集合,适合多步骤计算,SQL 要用嵌套表达的运算,用 SPL 可以更轻松实现。比如,找出销售额累计占到一半的前 n 个大客户,并按销售额从大到小排序:

A

B

2

=sales.sort(amount:-1)

/销售额逆序排序,可在SQL中完成

3

=A2.cumulate(amount)

/计算累计序列

4

=A3.m(-1)/2

/最后的累计即总额

5

=A3.pselect(~>=A4)

/超过一半的位置

6

=A2(to(A5))

/按位置取值

流程控制语法 。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

 …

与 Java 的循环类似,SPL 还可用 break 关键字跳出(中断)当前循环体,或用 next 关键字跳过(忽略)本轮循环,不展开说了。

计算性能更好 。在内存计算方面,除了常规的主键和索引外,SPL 还提供了很多高性能的数据结构和算法支持,比大多数使用 SQL 的内存数据库性能好得多,且占用内存更少,比如预关联技术、并行计算、指针式复用。

优化体系结构

SPL 支持 JDBC 接口,代码可外置于 Java,耦合性更低,也可内置于 Java,调用更简单。SPL 支持解释执行和热切换,代码方便移植和管理运营,支持内外存混合计算。

外置代码耦合性低。 SPL 代码可外置于 Java,通过文件名被调用,既不依赖数据库,也不依赖 Java,业务逻辑和前端代码天然 解耦

对于较短的计算,也可以像 SQLite 那样合并成一句,写在 Java 代码中:

 Class.forName("com.esproc.jdbc.InternalDriver");
Connection conn =DriverManager.getConnection("jdbc:esproc:local://");
Statement statement = conn.createStatement();
 String  arg1="1000";
String arg2="2000"
ResultSet result = statement.executeQuery(=Orders.select(Amount>="+arg1+" && Amount<"+arg2+"). groups(year(OrderDate):y,month(OrderDate):m; sum(Amount):s,count(1):c)");  

解释执行和热切换。 业务逻辑数量多,复杂度高,变化是常态。良好的系统构架,应该有能力应对变化的业务逻辑。SPL 是基于 Java 的解释型语言,无须编译就能执行,脚本修改后立即生效,支持不停机的热切换,适合应对变化的业务逻辑。

方便代码移植。 SPL 通过数据源名从数据库取数,如果需要移植,只要改动配置文件中的数据源配置信息,而不必修改 SPL 代码。SPL 支持动态数据源,可通过参数或宏切换不同的数据库,从而进行更方便的移植。为了进一步增强可移植性,SPL 还提供了与具体数据库无关的标准 SQL 语法,使用 sqltranslate 函数可将标准 SQL 转为主流方言 SQL,仍然通过 query 函数执行。

方便管理运营。 由于支持库外计算,代码可被第三方工具管理,方便团队协作;SPL 脚本可以按文件目录进行存放,方便灵活,管理成本低;SPL 对数据库的权限要求类似 Java,不影响数据安全。

内外存混合计算 。有些数据太大,无法放入内存,但又要与内存表共同计算,这种情况可利用 SPL 实现内外存混合计算。比如,主表 orders 已加载到内存,大明细表 orderdetail 是文本文件,下面进行主表和明细表的关联计算:

A

1

=file(“orderdetail.txt”).cursor@t()

2

=orders.cursor()

3

=join(A1:detail,orderid ; A2:main,orderid)

4

=A3.groups(year(main.orderdate):y; sum(detail.amount):s)

SQLite 使用简单方便,但数据源加载繁琐,计算能力不足。SPL 架构也非常简单,并直接支持更多数据源。SPL 计算能力强大,提供了丰富的计算函数,可以轻松实现 SQL 不擅长的复杂计算。SPL 还提供多种优化体系结构的手段,代码既可外置也可内置于 Java,支持解释执行和热切换,方便移植和管理运营,并支持内外存混合计算。

SPL下载地址:

SPL开源地址:

文章来源:智云一二三科技

文章标题:Java嵌入数据引擎:从SQLite到SPL

文章地址:https://www.zhihuclub.com/174338.shtml

关于作者: 智云科技

热门文章

网站地图