Java 下數(shù)據(jù)業(yè)務(wù)邏輯開發(fā)技術(shù) JOOQ 和 SPL
引言
很多開源技術(shù)都可以在Java下實現(xiàn)以數(shù)據(jù)庫為核心的業(yè)務(wù)邏輯,其中JOOQ的計算能力比Hibernate強,可移植性比MyBatis強,受到越來越多的關(guān)注。esProc SPL是新晉的數(shù)據(jù)計算語言,同樣在計算能力和可移植性方面優(yōu)勢突出。下面對二者進行多方面的比較,從中找出開發(fā)效率更高的數(shù)據(jù)業(yè)務(wù)邏輯開發(fā)技術(shù)。JOOQ商業(yè)版主要支持了商業(yè)數(shù)據(jù)庫和存儲過程,不在此次討論范圍。
語言特征
編程風(fēng)格
JOOQ支持完整的面向?qū)ο蟮木幊田L(fēng)格,可以將多個對象(方法)組合起來,形成類似SQL的語法邏輯。JOOQ可以使用Java的Lambda表達式、函數(shù)調(diào)用接口和流程控制語法,理論上也支持面向函數(shù)和面向過程,但這些表達式\語法沒有為JOOQ的結(jié)構(gòu)化數(shù)據(jù)對象(Result)而設(shè)計,使用時還不夠方便。
SPL支持面向?qū)ο?、面向函?shù)、面向過程的編程風(fēng)格,并進行大幅簡化。SPL有對象的概念,可以用點號訪問屬性并進行多步驟計算,但沒有繼承重載這些內(nèi)容。SPL的Lambda表達式比SQL更加簡單易用,函數(shù)調(diào)用接口和流程控制語法專為結(jié)構(gòu)化數(shù)據(jù)對象(序表)而設(shè)計,使用更加方便。
運行模式
JOOQ是編譯執(zhí)行的Java代碼,性能高一些,靈活性較差。但JOOQ本身沒有計算能力,執(zhí)行后只生成SQL語句,再交由數(shù)據(jù)庫計算并返回結(jié)果,實際性能并不高,有些業(yè)務(wù)邏輯需要反復(fù)讀寫數(shù)據(jù)庫,性能就更差了。SPL是解釋型語言,編碼更靈活,相同代碼性能會差一點。但是,SPL有不依賴數(shù)據(jù)庫的獨立計算能力,無需反復(fù)讀寫數(shù)據(jù)庫,內(nèi)置大量時間復(fù)雜度更低的基礎(chǔ)運算,計算性能經(jīng)常能超過編譯型語言。
外部類庫
JOOQ可以引入其他任意的第三方Java類庫,用來彌補自身的短板,比如利用Stream增加自己的獨立計算能力,但這些類庫沒有為結(jié)構(gòu)化數(shù)據(jù)對象而設(shè)計,提供的功能比較有限。SPL內(nèi)置專業(yè)的數(shù)據(jù)處理函數(shù),提供了大量開發(fā)效率更高、時間復(fù)雜度更低的基本運算,通常不需要外部Java類庫,特殊情況可在自定義函數(shù)中調(diào)用。
IDE和調(diào)試
兩者都有圖形化IDE和完整的調(diào)試功能。JOOQ使用Java IDE,好處是更通用,缺點是沒有為數(shù)據(jù)處理做優(yōu)化,無法方便地觀察結(jié)構(gòu)化數(shù)據(jù)對象。SPL的IDE專為數(shù)據(jù)處理而設(shè)計,結(jié)構(gòu)化數(shù)據(jù)對象呈現(xiàn)為表格形式,觀察更加方便。
學(xué)習(xí)難度
JOOQ需要學(xué)習(xí)三種語法,SQL、通用Java、JOOQ。其中,SQL的語言能力要高于一般水平,才能轉(zhuǎn)化為JOOQ語法;開發(fā)時主要使用JOOQ語法,難度不高,但轉(zhuǎn)化過程較復(fù)雜;通用Java的語言能力可以低于一般水平。SPL的目標(biāo)是簡化Java甚至SQL的編碼,無論入門學(xué)習(xí)還是深入開發(fā),難度都不高。但涉及到高性能計算時需要學(xué)習(xí)較多特有的算法,難度也會提高。
代碼量
SQL擅長結(jié)構(gòu)化數(shù)據(jù)計算,語法較簡練,代碼量較低,但為了把SQL翻譯成JOOQ,需要引入很多函數(shù),存在過度封裝的現(xiàn)象,實際代碼量較大。JOOQ的流程控制要借助Java語法,但Java語法沒有為結(jié)構(gòu)化數(shù)據(jù)對象而設(shè)計,代碼量也不低。
SPL的表達能力強于SQL,遠強于JOOQ,可用更低的代碼量實現(xiàn)結(jié)構(gòu)化數(shù)據(jù)計算,SPL的流程處理語句專為結(jié)構(gòu)化數(shù)據(jù)對象而設(shè)計,代碼量低于Java。
結(jié)構(gòu)化數(shù)據(jù)對象
結(jié)構(gòu)化數(shù)據(jù)對象用于將數(shù)據(jù)庫表對象化,是數(shù)據(jù)處理和業(yè)務(wù)邏輯開發(fā)的基礎(chǔ),專業(yè)的結(jié)構(gòu)化數(shù)據(jù)對象可以方便地與數(shù)據(jù)庫交換數(shù)據(jù),支持豐富的計算函數(shù),并簡化流程處理的難度。
定義
JOOQ的結(jié)構(gòu)化數(shù)據(jù)對象由記錄和記錄集合組成。記錄對象的種類很多,第一類是Record對象,適合字段的數(shù)量、類型、名字都是動態(tài)生成的情況,Record雖然很靈活但面向?qū)ο蟮某潭容^低,用法比較麻煩,比如要通過getValue(M)來獲得第M個字段。
第二類是Record[N]對象,N從1到22,比如Record3,適合字段類型和字段數(shù)量已知但不超過22個,而字段名是動態(tài)生成的情況,Record[N]靈活性差些但面向?qū)ο蟮某潭壬愿撸梅ǚ奖阈?,比如可以通過valueM取得第M個字段。
第三類記錄對象由JOOQ的代碼工具根據(jù)庫表結(jié)構(gòu)生成,有幾個表就有幾個對象,字段的數(shù)量、類型、名字都和庫表字段嚴(yán)格對應(yīng),比如OrdersRecord、EmployeesRecord,這種記錄對象不靈活但面向?qū)ο蟮某潭群芨撸梅ㄒ埠芊奖?,可以直接通過字段名取字段。
第三類對應(yīng)庫表,可稱之為固定數(shù)據(jù)結(jié)構(gòu)的記錄對象,前兩類通常來自對庫表的查詢計算,可稱之為動態(tài)數(shù)據(jù)結(jié)構(gòu)的記錄對象。這三類比較常用,還有些不常用的記錄對象,比如用戶自定義記錄類型UDT,這里就不展開說了。JOOQ的記錄對象種類繁多,用法差異較大,增加了開發(fā)的難度,這主要因為業(yè)務(wù)邏輯存在大量動態(tài)數(shù)據(jù)結(jié)構(gòu),而Java是編譯型語言,只擅長表達固定數(shù)據(jù)結(jié)構(gòu),如果硬要表達動態(tài)數(shù)據(jù)結(jié)構(gòu),就只能設(shè)計復(fù)雜的接口規(guī)則,或者根據(jù)字段數(shù)量預(yù)定義大量對象。
JOOQ記錄集合的種類相對較少,常用的有原生對象Result,及其父類ArrayList,有時候也會用Stream。
SPL的結(jié)構(gòu)化數(shù)據(jù)對象同樣由記錄(Record)和記錄集合(序表)組成。SPL的記錄對象只有一種,主要因為SPL是解釋型語言,動態(tài)數(shù)據(jù)結(jié)構(gòu)和固定數(shù)據(jù)結(jié)構(gòu)表達起來同樣方便,接口都很簡單,沒必要分成多個。此外,記錄對象與單記錄集合雖然本質(zhì)不同,但業(yè)務(wù)意義相似,用起來容易混淆。SPL是解釋型語言,可以通過靈活的接口使兩者的外部用法保持一致,從而進一步提高易用性。相反,JOOQ是編譯型語言,很難設(shè)計出這種靈活的接口,只能提供兩類不同的接口,分別用來處理記錄對象和單記錄集合。
讀數(shù)據(jù)庫
JOOQ讀取外部數(shù)據(jù)庫表,生成固定記錄集合:
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);
查詢外部數(shù)據(jù)庫表,生成動態(tài)記錄集合:
Result<Record3<Integer,String,Double>>R2=context.select(ORDERS.SELLERID,ORDERS.CLIENT,ORDERS.AMOUNT).from(ORDERS).fetch();
動態(tài)記錄集合的后續(xù)用法稍顯麻煩,但可以兼容固定記錄集合,下面文章中主要用動態(tài)記錄集合。
SPL讀取或查詢外部數(shù)據(jù)庫表,生成序表:
| A | |
|---|---|
| 1 | =conn=connect("mysql8") |
| 2 | =conn.query("select * from Orders") |
| 3 | =conn.query("select SellerID,Client,Amount from Orders") |
SPL不分固定記錄集合或動態(tài)記錄集合,生成方法一致,后續(xù)用法相同。
寫數(shù)據(jù)庫
將處理后的結(jié)構(gòu)化數(shù)據(jù)對象持久化保存到數(shù)據(jù)庫,JOOQ提供了三種函數(shù),分別是insert、update、delete。修改記錄r,再更新到數(shù)據(jù)庫:
r.setValue(ORDERS.AMOUNT,r.getValue(ORDERS.AMOUNT).doubleValue()+100); r.update();
上面是單條記錄的更新。要注意的是,數(shù)據(jù)庫表必須有主鍵,自動生成的記錄類才會繼承UpdatableRecordImpl,只有繼承UpdatableRecordImpl的記錄類才支持update函數(shù)。
批量寫入數(shù)據(jù)庫是數(shù)據(jù)業(yè)務(wù)邏輯常見的場景,JOOQ也能實現(xiàn)。批量修改記錄集合T,并更新到數(shù)據(jù)庫:
R1.forEach(r->{
r.setValue(ORDERS.AMOUNT,r.getValue(ORDERS.AMOUNT).doubleValue()+100);});
R1.forEach(r->{
r.update();});
上面代碼循環(huán)記錄集合,手工更新每一條記錄,從而實現(xiàn)對整體集合的更新??梢钥吹?,JOOQ通過硬寫代碼實現(xiàn)批量寫入,沒有進行封裝,很多時候不方便。如果一批記錄既有修改又有新增還有刪除,就必須區(qū)分三類記錄,再用不同的函數(shù)循環(huán)寫入,常見的辦法是繼承記錄類,新加一個“標(biāo)識”屬性予以區(qū)分,或者保存一個未修改的原始記錄集合T,將修改后的集合NT與原始集合進行手工比對。無論哪種方法,手工實現(xiàn)的過程都很麻煩。
SPL對數(shù)據(jù)庫的寫入進行了封裝,只用一個update函數(shù)就實現(xiàn)單條和批量記錄的新增、修改、刪除,且支持混合更新。比如:原序表為T,經(jīng)過增刪改一系列處理后的序表為NT,將變化結(jié)果持久化到數(shù)據(jù)庫的orders表:
conn.update(NT:T,orders)
訪問字段
JOOQ讀取單條記錄的Client字段:
R1.get(0).getClient(); R1.get(0).get(ORDERS.CLIENT);
上面代碼體現(xiàn)了JOOQ的核心優(yōu)勢:支持純粹的面向?qū)ο蟮淖侄卧L問方式,不摻雜字符串、數(shù)字常量,或其他非Java的表達式,代碼風(fēng)格高度統(tǒng)一。遺憾之處在于,上面代碼只適用于固定結(jié)構(gòu)化數(shù)據(jù)對象。如果是查詢計算生成的動態(tài)記錄對象,就只能使用字符串字段名或數(shù)字序號訪問字段:
R2.get(0).get("Client");
R2.get(0).get(1);
動態(tài)記錄對象更加普遍,上面的字段訪問方式不算純粹的面向?qū)ο螅a風(fēng)格不一致,不支持自動補全,編寫時普遍麻煩。
SPL支持純粹的面向?qū)ο蟮淖侄卧L問方式,不分固定或動態(tài),編寫時普遍方便:
T(1).Client
當(dāng)然也支持字符串字段名或數(shù)字序號訪問字段:
T(1).field(2)
T(1).field("Client")
SPL在面向?qū)ο蠓矫娓蛹兇?,風(fēng)格更統(tǒng)一,編寫代碼更加方便。此外,SPL提供了很多JOOQ不支持的便利功能:默認字段名,可以用點號直接訪問,比如取第2個字段:T(1).#2;取多個字段,返回集合的集合:T.([Client,Amount])
有序訪問
有序訪問是業(yè)務(wù)邏輯開發(fā)的難點之一,JOOQ的記錄集合繼承自Java的有序集合ArrayList,具備一定的有序訪問能力,支持按下標(biāo)取記錄和按區(qū)間取記錄:
R.get(3) R.subList(3,5);
再進一步的功能,就需要硬編碼實現(xiàn)了,比如后3條:
Collections.reverse(R); R.subList(0,3);
至于按位置集合取記錄、步進取記錄等功能,硬編碼就更麻煩了。
SPL序表同樣是有序集合,提供了順序相關(guān)的基本功能,比如按下標(biāo)取、按區(qū)間?。?/p>
T(3) T.to(3,5)
序表是專業(yè)的結(jié)構(gòu)化數(shù)據(jù)對象,許多順序相關(guān)的高級功能JOOQ Result沒有支持,序表則直接提供了,比如按倒數(shù)序號取記錄,可以直接用負號表示:
T.m(-3) //倒數(shù)第3條 T.m(to(-3,-5)) //倒數(shù)區(qū)間
再比如按位置集合取記錄、步進取記錄:
T.m(1,3,5,7:10) //序號是1、3、5、7-10的記錄 T.m(-1,-3,-5) //倒數(shù)第1,3,5條 T.step(2,1) //每2條取第1條(等價于奇數(shù)位置)
結(jié)構(gòu)化數(shù)據(jù)計算
結(jié)構(gòu)化數(shù)據(jù)計算能力是數(shù)據(jù)業(yè)務(wù)邏輯的核心功能,下面從簡單到復(fù)雜選取幾個常見題目,比較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基本一致,可以達到用面向?qū)ο蟮姆绞侥MSQL的目的,這是JOOQ的重要優(yōu)點。相應(yīng)的也有缺點,JOOQ的一項運算需要多個函數(shù)的組合才能實現(xiàn),每個函數(shù)都有自己的參數(shù)和語法規(guī)則,學(xué)習(xí)和編寫難度較大。此外,很多函數(shù)里的字段名必須附帶表名,即使單表計算也是如此,這說明JOOQ的語法不夠?qū)I(yè),還有很大的改進空間。
SPL直接用面向?qū)ο蟮恼Z法實現(xiàn)計算,一項運算對應(yīng)一個函數(shù),引用字段不必附帶表名,語法更專業(yè),代碼更簡短。
條件查詢
//等價的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,但對條件表達式進行了過度封裝,函數(shù)數(shù)量過多,多層括號難閱讀,遠不如SQL好理解。SPL用一個函數(shù)實現(xiàn)條件查詢,條件表達式簡短易讀。
分組匯總
//等價的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使用了很多函數(shù),規(guī)則很復(fù)雜,導(dǎo)致代碼過長。SPL直接用面向?qū)ο蟮恼Z法,規(guī)則簡單,代碼更短。
前面都是較簡單計算,類似的計算還包括排序、去重、關(guān)聯(lián)、集合交并差等計算,這里不再一一列舉,總的來說,JOOQ進行簡單計算時比SQL和SPL代碼長,很多時候不易理解,開發(fā)效率較低。
各組前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,但使用了很多函數(shù),代碼長度遠超SQL,語法也越來越不像SQL,編寫理解更加困難。SPL先對客戶分組,再求各組(即~)的前3名,最后合并各組計算結(jié)果,不僅代碼更簡短,且更易理解。
JOOQ使用了窗口函數(shù),只適合特定版本的數(shù)據(jù)庫,比如MySQL8,不能通用于其他版本的數(shù)據(jù)庫,要想在MySQL5下實現(xiàn)同樣的計算,代碼改動非常麻煩。SPL有獨立計算能力,代碼可通用于任何數(shù)據(jù)庫。
某支股票最大連續(xù)上漲天數(shù)
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很難直接表達連續(xù)上漲的概念,只能使用技巧變相實現(xiàn),即通過累計不漲天數(shù)來計算連續(xù)上漲天數(shù)。具體是,先按時間順序給每條記錄打漲跌標(biāo)記risingflag,如果下跌,則標(biāo)為1,如果上漲,則標(biāo)為0;再按時間順序累計每條記錄的不漲天數(shù)norisingdays,只有當(dāng)前記錄下跌時,這個數(shù)字才會變大,如果當(dāng)前記錄上漲,則這個數(shù)字不變;再按不漲天數(shù)norisingdays分組,求各組記錄數(shù),顯然,連續(xù)下跌的一批記錄的norisingdays不同,每條記錄都會分到不同的組,該組計數(shù)為1,這個值不是解題目標(biāo),而連續(xù)上漲的一批記錄的norisingdays相同,可以分到同一組,該組計數(shù)即連續(xù)上漲的天數(shù),這個值是解題目標(biāo);最后用max函數(shù)求出最大的連續(xù)上漲天數(shù)。
JOOQ的編程過程是先寫SQL,再翻譯成JOOQ,對于簡單計算來說,SQL比較好寫,翻譯也不會太難,但對于本題這種綜合性計算來說,計算邏輯的技巧性比較強,SQL不好寫,翻譯的難度更大。此外,JOOQ表面上是方便調(diào)試的Java,但本質(zhì)卻是SQL,和SQL一樣難以調(diào)試,這又為將來的維護工作埋下了大坑。
SPL代碼簡單多了:
APPL.sort(day).group@i(price<price[-1]).max(~.count())
這條SPL語句的計算邏輯和JOOQ是相同的,也是將連漲記錄分到同一組中再求最大的組成員數(shù),但表達起來要方便很多。group@i()表示遍歷序表,如果符合條件則開始新的一組(并使之前的記錄分到同一組),price<price[-1]這個條件表示股價下跌,則之前股價上漲的記錄會分到同一組。[-1]表示上一條,是相對位置的表示方法,price[-1]表示上一個交易日的股價,比整體移行(lag.over)更直觀。
相對位置屬于有序計算,SPL是專業(yè)的結(jié)構(gòu)化計算語言,支持有序計算,代碼因此更簡單。除了有序集合,SPL還可以簡化多種復(fù)雜計算,包括多步驟計算、集合計算、復(fù)雜的關(guān)聯(lián)計算。相反,這幾類計算都是JOOQ不擅長的,通常要通過特殊技巧實現(xiàn),代碼很難寫。
SPL函數(shù)選項和層次參數(shù)
值得一提的是,為了進一步提高開發(fā)效率,SPL還提供了獨特的函數(shù)語法。有大量功能類似的函數(shù)時,JOOQ只能用不同的名字或者參數(shù)進行區(qū)分,使用不太方便。而SPL提供了非常獨特的函數(shù)選項,使功能相似的函數(shù)可以共用一個函數(shù)名,只用函數(shù)選項區(qū)分差別。比如,select函數(shù)的基本功能是過濾,如果只過濾出符合條件的第1條記錄,可使用選項@1:
T.select@1(Amount>1000)
對有序數(shù)據(jù)用二分法進行快速過濾,使用@b:
T.select@b(Amount>1000)
函數(shù)選項還可以組合搭配,比如:
Orders.select@1b(Amount>1000)
有些函數(shù)的參數(shù)很復(fù)雜,可能會分成多層。JOOQ對此并沒有特別的語法方案,只能拆成多個函數(shù)互相嵌套,盡力模擬成SQL語法,導(dǎo)致代碼冗長繁瑣。而SPL創(chuàng)造性地發(fā)明了層次參數(shù)簡化了復(fù)雜參數(shù)的表達,通過分號、逗號、冒號自高而低將參數(shù)分為三層。比如關(guān)聯(lián)兩個表:
join(Orders:o,SellerId ; Employees:e,EId)
流程處理
JOOQ支持部分存儲過程語法,包括循環(huán)語句和判斷語句,但這屬于商業(yè)版功能,且權(quán)限要求高、安全隱患大,難以移植,一般很少用到。除了存儲過程,JOOQ還可以利用Java實現(xiàn)流程處理,對數(shù)據(jù)庫沒有權(quán)限要求,安全隱患小,且可無縫移植。比如,根據(jù)規(guī)則計算獎金:
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循環(huán)函數(shù)針對JOOQ的結(jié)構(gòu)化數(shù)據(jù)對象進行了優(yōu)化,可以通過Lambda表達式簡化循環(huán)結(jié)構(gòu)的定義,可以方便地處理集合對象的每個成員(代碼中的循環(huán)變量r)。forEach函數(shù)配合Lambda語法,整體代碼要比傳統(tǒng)循環(huán)語句簡單些。但也應(yīng)該注意到,forEach函數(shù)里使用字段需要附帶循環(huán)變量名,對單表計算來說是多余的,同樣使用Lambda語法的SQL就可以省略變量名。此外,定義循環(huán)變量名也是多余的,SQL就不用定義。這些缺點都說明JOOQ在流程處理方面還不夠?qū)I(yè),代碼還有很大的優(yōu)化空間。
SPL也有針對結(jié)構(gòu)化數(shù)據(jù)對象進行優(yōu)化的循環(huán)函數(shù),直接用括號表示。同樣根據(jù)規(guī)則計算獎金:
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的循環(huán)函數(shù)同樣支持Lambda表達式,而且接口更簡單,不必定義循環(huán)變量,使用字段時不必引用變量名,比JOOQ更方便,專業(yè)性也更強。除了循環(huán)函數(shù),SPL還有更多專業(yè)的流程處理功能,比如:每輪循環(huán)取一批而不是一條記錄;某字段值變化時循環(huán)一輪。
SPL專業(yè)的流程處理功能,配合專業(yè)的結(jié)構(gòu)化數(shù)據(jù)對象和結(jié)構(gòu)化數(shù)據(jù)計算能力,可大幅提高數(shù)據(jù)業(yè)務(wù)邏輯的開發(fā)效率。一個完整的例子:計算出獎金,并向數(shù)據(jù)庫插入新記錄。JOOQ需要生成多個文件,編寫大段代碼才能實現(xiàn),SPL就簡單多了:
| A | B | C | |
|---|---|---|---|
| 1 | =db=connect@e("dbName") | /連接數(shù)據(jù)庫,開啟事務(wù) | |
| 2 | =db.query@1("select sum(Amount) from sales where sellerID=? and year(OrderDate)=? and month(OrderDate)=?", p_SellerID,year(now()),month(now())) | /查詢當(dāng)月銷售額 | |
| 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) | /創(chuàng)建訂單的數(shù)據(jù)結(jié)構(gòu) | |
| 7 | =A6.record([p_OrderID,p_Client,p_SellerID,p_Amount,BONUS,date(now())]) | /生成一條訂單記錄 | |
| 8 | >db.update@ik(A7,sales;ORDERID) | /嘗試寫入庫表 | |
| 9 | =db.error() | /入庫結(jié)果 | |
| 10 | if A9==0 | >A1.commit() | /成功,則提交事務(wù) |
| 11 | else | >A1.rollback() | /失敗,則回滾事務(wù) |
| 12 | >db.close() | /關(guān)閉數(shù)據(jù)庫連接 | |
| 13 | return A9 | /返回入庫結(jié)果 |
利用SPL的流程處理語句,可以實現(xiàn)存儲過程的所有能力,包括游標(biāo)的循環(huán)和判斷。SPL不依賴數(shù)據(jù)庫,不需要數(shù)據(jù)庫權(quán)限,沒有安全隱患,相當(dāng)于庫外的存儲過程,同時,這些功能也都是開源的。
應(yīng)用結(jié)構(gòu)
Java集成
JOOQ本身就是Java,可被其他Java代碼直接調(diào)用。
SPL是基于JVM的數(shù)據(jù)計算語言,提供了易用的JDBC接口,可被JAVA代碼無縫集成。比如,將業(yè)務(wù)邏輯代碼存為腳本文件,在JAVA中以存儲過程的形式調(diào)用文件名:
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)是編譯型語言,不支持熱部署,修改代碼后需要重新編譯并重啟整個應(yīng)用,加大了維護難度,降低了系統(tǒng)穩(wěn)定性。
SPL是解釋型語言,代碼以腳本文件的形式外置于JAVA,支持熱部署,修改后不必編譯,也不必重啟應(yīng)用。由于SPL代碼不依賴JAVA,業(yè)務(wù)邏輯和前端代碼物理分離,耦合性也更低。
代碼移植
JOOQ的部分代碼可以移植,比不可移植的MyBatis方便。比如業(yè)務(wù)邏輯中常用于分頁的limit(M).offset(N),在Oracle11g數(shù)據(jù)庫下會被翻譯為rownum子查詢;如果數(shù)據(jù)庫改為MSSQL2012,只要重新生成并部署實體類,不必修改業(yè)務(wù)邏輯,同樣的代碼就會翻譯成offset next語句。
能夠移植的代碼畢竟是少數(shù),大部分JOOQ代碼都是不可以移植的,比如前面例子里的窗口函數(shù)。移植時需要讀懂原JOOQ代碼,反翻譯成原SQL,再改成新SQL,最后翻譯成新JOOQ代碼,過程較繁難度較高。業(yè)務(wù)邏輯普遍具有復(fù)雜性,移植工作就更難了。
SPL具有獨立計算能力,不必借用SQL,憑借豐富的內(nèi)置函數(shù)庫就能實現(xiàn)復(fù)雜的結(jié)構(gòu)化數(shù)據(jù)計算,計算代碼可在數(shù)據(jù)庫間無縫移植。在數(shù)據(jù)庫取數(shù)代碼中,SPL也要執(zhí)行方言SQL生成序表,雖然取數(shù)SQL比較簡單,手工移植不難,但仍有一定工作量,為了使取數(shù)代碼便于移植,SPL專門提供了不依賴特定數(shù)據(jù)庫的通用SQL,可在主流數(shù)據(jù)庫間無縫移植。
通過多方面的比較可知:JOOQ可以進行較簡單的查詢統(tǒng)計,但對于較復(fù)雜的業(yè)務(wù)邏輯開發(fā)就顯得比較繁瑣,尤其是有序計算、多步驟計算、集合計算、復(fù)雜的關(guān)聯(lián)查詢,存在翻譯SQL的工作量大,代碼冗長,難以修改,難以移植等問題。SPL語法簡練、表達效率高、代碼移植方便,結(jié)構(gòu)化數(shù)據(jù)對象更專業(yè),函數(shù)更豐富且計算能力更強,流程處理更方便,開發(fā)效率遠高于JOOQ。
以上就是Java 下數(shù)據(jù)業(yè)務(wù)邏輯開發(fā)技術(shù) JOOQ 和 SPL的詳細內(nèi)容,更多關(guān)于Java數(shù)據(jù)業(yè)務(wù)邏輯JOOQ SPL的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java字符串?dāng)?shù)組進行大小排序的簡單實現(xiàn)
下面小編就為大家?guī)硪黄猨ava字符串?dāng)?shù)組進行大小排序的簡單實現(xiàn)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-09-09
SpringCloud微服務(wù)開發(fā)基于RocketMQ實現(xiàn)分布式事務(wù)管理詳解
分布式事務(wù)是在微服務(wù)開發(fā)中經(jīng)常會遇到的一個問題,之前的文章中我們已經(jīng)實現(xiàn)了利用Seata來實現(xiàn)強一致性事務(wù),其實還有一種廣為人知的方案就是利用消息隊列來實現(xiàn)分布式事務(wù),保證數(shù)據(jù)的最終一致性,也就是我們常說的柔性事務(wù)2022-09-09
詳解java JDK 動態(tài)代理類分析(java.lang.reflect.Proxy)
這篇文章主要介紹了詳解java JDK 動態(tài)代理類分析(java.lang.reflect.Proxy)的相關(guān)資料,需要的朋友可以參考下2017-06-06
Java并發(fā)系列之CountDownLatch源碼分析
這篇文章主要為大家詳細介紹了Java并發(fā)系列之CountDownLatch源碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-03-03

