這是Jerry 2020年的第67篇文章,也是汪子熙公眾號總共第249篇原創文章。
最近Jerry的處境可以用本世紀初,八零後剛上大學時校園內風靡的一款FPS遊戲名稱來形容: 《半條命》. 為了避免讓汪子熙這個公眾號成為神經外科前中顱底亞專業醫學知識的普及號,咱們還是繼續聊SAP技術吧。
隨著ABAP 7.40 SP05的發布,SAP ABAP引入了一種新的應用開發範式,即所謂的Code Pushdown.
傳統的ABAP應用開發方式,即下圖左邊的Data to Code, 資料庫僅僅作為數據的靜態存儲倉庫,ABAP應用開發人員通過Open SQL等方式將數據從資料庫層讀取到ABAP應用層,再在ABAP應用層進行數據處理。
Code Pushdown意味著一種編程理念的轉變,即上圖右邊所示,將密集的數據計算從ABAP應用層下推到HANA資料庫層,從而充分發揮HANA資料庫高性能的數據處理能力。
要實現Code Pushdown,SAP HANA資料庫必須提供一種技術,能夠允許ABAP開發人員在HANA資料庫層直接編寫應用邏輯。這些應用邏輯可以實現在所謂的資料庫過程(Database Procedure)裡,實現語言為SAP HANA SQLScript.
我們可以把資料庫過程簡單地類比成一個只能進行資料庫讀寫及相關處理的函數,能接收任意多個輸入參數和定義輸出參數,存放資料庫操作的結果。參數的類型既可以是標量式類型,比如integer, double, varchar等,也可以是table類型。
使用ABAP Development Tool, 我們可以選擇兩種不同的方式來實現資料庫過程。第一種,即本文標題提及的AMDP, 第二種為資料庫過程代理(Database Procedure Proxy), 不在本文討論範圍之內。
AMDP, 顧名思義,即在ABAP層進行HANA資料庫過程的實現和生命周期(lifecycle)的管理。開發人員通過位於ABAP層的ABAP Development Tool, 編寫HANA SQLScript作為AMDP的實現體,以此達到在ABAP層直接消費HANA資料庫層原生功能的目的。
具體到實現環節,在ABAP層內何種類型的開發對象裡編寫HANA SQLScript呢?答案仍然是ABAP類的方法內,只不過是在一種聲明了特定接口的ABAP類,用AMDP特定的ABAP關鍵字修飾的方法內。繼續沿用ABAP類方法來開發AMDP, 使得傳統ABAP應用開發人員除了熟悉HANA SQLScript語法外,無需付出額外的學習成本。這種特殊的ABAP類方法,作為HANA SQLScript的承載容器,使得AMDP同其他普通的ABAP開發對象一樣,採取統一的ABAP傳輸管理,生命周期管理,代碼缺陷修復和升級管理方式。
除了AMDP之外,資料庫過程代理是另一種HANA資料庫過程的實現方式。這種方式首先在HANA repository裡創建一個HANA原生的資料庫過程,再到ABAP層創建一個代理指向前者,在ABAP應用裡通過使用該代理對象,消費HANA倉庫裡的原生資料庫過程。同AMDP相比,這種方式需要在HANA層進行原生開發,因此在實際的開發場景中,SAP更推薦使用AMDP.
實際上AMDP不是一個新事物,早在2013年這個功能剛剛隨著ABAP 7.40 SP05發布之時,我就第一時間試用並且寫了一篇學習筆記:
https://blogs.sap.com/2013/12/10/an-example-of-amdp-abap-managed-database-procedure-in-740/
看個具體的例子,還是使用廣大ABAP開發人員喜聞樂見的SFLIGHT系列模型。
本文例子的完整代碼,可以通過點擊文末的「閱讀原文」獲得。
首先,AMDP類只能在ABAP Development Tool裡進行開發,在SAP GUI裡可以用只讀的方式瀏覽原始碼,但無法修改:
前面概述章節裡提到,AMDP是實現在一個特殊的ABAP類之內,這個ABAP類的特殊之處就體現在,它需要聲明一個Marker Interface(標記接口,有的文檔又稱之為Tag Interface, 標籤接口):IF_AMDP_MARKER_HDB.
這個標記接口扮演了現代Java開發中的Annotation的角色,即作為元數據,告知ABAP編譯期和運行時,這個ABAP類作為容器,存放AMDP的實現。
順便提一句,除了IF_AMDP_MARKER_HDB,ABAP還有很多其他的標記接口,比如表明一個ABAP類支持序列化操作的接口,IF_SERIALIZABLE_OBJECT:
以及標註一個接口需要被BAdI Definition使用的IF_BADI_INTERFACE. 我們在創建或修改ABAP新式BAdI時,任何定義在BAdI Definition中的接口,如果沒有聲明接口IF_BADI_INTERFACE,會無法通過ABAP語法檢查。
因為ABAP缺乏像Java那樣能夠從語言級別直接使用註解(Annotation)進行元數據定義的特性,因而採用了標記接口這種方式。
在Java基於Spring框架的開發裡,Annotation幾乎隨處可見。JDK1.5之後引入的Annotation,能聲明在Java包、類、欄位、方法、局部變量、方法參數等資源之上,達到維護元數據的目的,既靈活又方便。然而Java誕生之初,在JDK1.5之前,標記接口也是Java唯一能夠從語言層級進行元數據維護的方式。
下圖是Java用來定義一個類能夠支持序列化操作的標記接口Serializable, 對應著ABAP的標記接口IF_SERIALIZABLE_OBJECT.
再回到本文的例子ZCL_JERRY_AMDP_DEMO, main方法裡就執行一個邏輯:從SFLIGHTS系列的模型裡讀取數據。
方法get_flights從資料庫表裡讀取數據,然後調用另一個方法convert_currency,直接在HANA資料庫層面進行貨幣轉換,再把結果返回給ABAP層的輸出參數result.
前面提到過,AMDP實現在一個特殊ABAP類的特殊方法裡。特殊的ABAP類,前文已經介紹過,該類必須聲明標記接口IF_AMDP_MARKER_HDB. 而AMDP方法同普通ABAP方法相比的特殊之處,體現在BY DATABASE PROCEDURE FOR HDB LANGUAGE SQLSCRIPT這些ABAP關鍵字上:
上圖紅色區域的ABAP關鍵字,表明被修飾的ABAP方法是一個AMDP的容器,AMDP的實現語言為HANA SQLScript,實現體內以只讀方式訪問了兩張資料庫表/dmo/flight和/dmo/carrier, 以及另一個實現貨幣轉換功能的名為convert_currency的AMDP.
這個AMDP的邏輯主要是接收之前從/dmo/flight和/dmo/carrier兩張表做內連接後得到的數據作為輸入,同時消費HANA SQLScript裡一個內置函數convert_currency, 將資料庫裡的機票價格轉換成以歐元EUR為單位的值。
convert_currency函數的參數定義:
https://help.sap.com/viewer/4fe29514fd584807ac9f2a04f6754767/2.0.03/en-US/d22d746ed2951014bb7fb0114ffdaf96.html
執行結果: