Java 8 徹底改變資料庫訪問

類別: IT
標籤: java

Java 8終於到來了! 經過幾年的等待, java程式員終於能在java中得到函數語言程式設計的支援了. 函數語言程式設計的支援能流程化現有的程式碼並且為java提供強大的能力.在這些新特性中最矚目的是java程式員對資料庫的操作方式.函數語言程式設計帶來了令人激動的簡便高效的資料庫API. Java 8 將會支援可與像C#的LINQ等語言競爭的新的資料庫訪問方式.

處理資料的函式式方式

Java 8 不僅僅新增了函式式支援,它也通過新的函式式處理資料的方式擴充套件了集合(Collection)類. 而通常情況下java處理大量資料時需要大量的迴圈和迭代器.

例如, 假設你有一個儲存客戶(Customer)物件的collection:

Collection<Customer> customers;

如果你只對來自Belgium的客戶感興趣, 你將不得不迭代所有的customer物件並只儲存你需要的.

Collection<Customer> belgians = new ArrayList<>();
for (Customer c : customers) {
    if (c.getCountry().equals("Belgium"))
        belgians.add(c);
}

這不僅花費了5行程式碼,而且它也不怎麼抽象.假使你有1千萬個物件時會怎樣呢?你會通過兩個執行緒併發過濾所有物件來提速麼?那你將不得不使用大量危險的多執行緒程式碼來重寫所有程式碼.

而通過Java 8,僅僅只需要一行程式碼就能實現相同的功能.通過對函數語言程式設計的支援, Java 8 能讓你只寫一個函式表明你對哪些客戶(物件)感興趣然後使用那個函式對集合做過濾就可以了. Java 8 的新 Steams API 支援你這樣做:

customers.stream().filter(
    c -> c.getCountry().equals("Belgium")
);

上面Java 8 版本的程式碼不僅更短,而且更容易理解.它幾乎沒有什麼 陳詞濫調(迴圈或迭代器等).程式碼呼叫了filter()方法,那很明顯這段程式碼是用來過濾客戶(物件)的.你不需要再把時間浪費在解讀迴圈中的程式碼來理解它在對它的資料做什麼.

假使你想併發執行這段程式碼該怎麼辦呢?你只需使用另一個型別的stream

customers.parallelStream().filter(
    c -> c.getCountry().equals("Belgium")
);

更另人激動的是這種函式式風格的程式碼也同樣適用於資料庫

在資料庫上使用函式式方式

傳統上來說, 程式員需要用特殊資料庫查詢語句去訪問資料庫的資料. 例如,下面就是用 JDBC 程式碼去查詢來自Belgium的客戶:

PreparedStatement s = con.prepareStatement(
      "SELECT * "
    + "FROM Customer C "
    + "WHERE C.Country = ? ");
s.setString(1, "Belgium");
ResultSet rs = s.executeQuery();

大部分這些程式碼都是字串, 這樣會使編譯器不能發現錯誤而且這草率的程式碼會導致安全問題. 還有這些大量的樣板程式碼使得寫資料訪問程式碼變得十分冗餘. 一些工具例如 jOOQ ,通過使用特殊的java庫去提供資料庫查詢語言可以解決錯誤檢查和安全問題。 或者使用物件關係對映工具可以免去大量的無趣的程式碼,可它們只能用在通用訪問查詢, 如果需要複雜的查詢,還是需要用特殊的資料庫查詢語言。

使用Java 8,藉助流式API就可以用函式式方式去查詢資料庫了。例如, Jinq 是一個開源的專案,它探索怎樣的未來資料庫API可以令函數語言程式設計成為可能。這裡就是一個使用Jinq的資料庫查詢:

customers.where(
    c -> c.getCountry().equals("Belgium")
);

這程式碼幾乎跟跟使用流式API的程式碼一樣. 事實上,未來的Jinq版本可以讓你用流式API直接寫資料庫查詢。 當程式碼執行的時候,Jinq將自動翻譯成資料庫查詢程式碼,正如之前JDBC查詢一樣。

這樣的話,就算沒有學過一些新的資料庫查詢語言,你也可以寫出有效率的資料庫查詢。你可以用同樣樣式的程式碼用在java集合上。你也不需要特殊的java編譯器或者虛擬機器。所有的程式碼編譯和執行在普通的java 8 JDK上。如果你的程式碼有錯誤,編譯器將找出它們並且報告給你,就像普通的java程式碼。

Jinq 支援跟SQL92一樣的複雜查詢. Selection(選擇), projection(投影), joins(連線), 和子查詢 它都支援。翻譯java程式碼成資料庫查詢的演算法是十分靈活的,只要是它能接受的,都能翻譯。例如,Jinq能夠翻譯下面的資料庫查詢,儘管它很複雜。

customers
    .where( c -> c.getCountry().equals("Belgium") )
    .where( c -> {
        if (c.getSalary() < 100000)
            return c.getSalary() < c.getDebt();
        else
            return c.getSalary() < 2 * c.getDebt();
        } );

正如你看到的,java 8 的函數語言程式設計非常適合資料庫查詢。而且查詢緊湊,甚至複雜的查詢也能夠勝任。

內部運作

但這都是如何工作的呢?怎麼能讓普通的Java編譯器將Java程式碼轉換成資料庫查詢?Java 8 有什麼特別之處使這個成為可能?

支援這些函式性風格的新的資料庫PI的關鍵是一種叫做“象徵性執行”的位元組碼分析手段。雖然你的程式碼是被一個普通的Java編譯器編譯的並執行在一個普通的Java虛擬機器中,但 Jinq 能夠在你被編譯的Java程式碼執行時進行分析並從中構建資料庫查詢。使用 Java 8 Streams API 時,常會發現分析短小的函式時,象徵性執行的工作效果最好。

要了解這個象徵性執行是如何工作的,最簡單的方法是用一個例子。讓我們檢查一下下面的查詢是如何被 Jinq 轉換為SQL查詢語言的:

customers
    .where( c -> c.getCountry().equals("Belgium") )

初始時, 變數 customers 是一個集合,其對應的資料庫查詢是:

SELECT *
  FROM Customers C

然後,where() 方法被呼叫,一個函式被傳遞給它。在 where() 方法中,Jinq 開啟這個函式的 .class 檔案,得到這個函式被編譯成的位元組碼進行分析。在這個例子中,不使用真正的位元組碼,讓我們用一些簡單的指令來代表這個函式的位元組碼:

  1. d = c.getCountry()

  2. e = &#8220;Belgium&#8221;

  3. e = d.equals(e)

  4. return e

在這裡,我們假設函式已被Java編譯器編譯成這四條指令。當呼叫 where() 方法時,Jinq 看到的就是這些。如何才能使Jinq理解這些程式碼呢?

Jinq 通過執行程式碼來分析。但 Jinq 不直接執行程式碼。它是“抽象”地執行程式碼:不使用真實的變數和真實的值,Jinq 使用符號來表示執行程式碼時的所有值。這就是這個分析為什麼被稱為“象徵性執行”。

Jinq 執行每條指令,並跟蹤所有的副作用或程式碼在程式狀態時改變的所有東西。下面是一個圖表,顯示出 Jinq 用象徵性執行方式執行這四行程式碼時發現的所有副作用。

象徵性執行的例子

在圖中,你可以看到第一條指令執行後,Jinq 發現了兩個副作用:變數d已經發生了變化,方法 Customer.getCountry() 被呼叫。由於是象徵性執行,變數d沒有給出一個真正的比如是“USA”或“Denmark”的值,它被分配為 c.getCountry() 的象徵性的值。

在所有這些指令被象徵性執行之後,Jinq 對副作用作精簡。由於變數 d 和 e 是區域性變數,它們的任何變化在函式退出後都會被丟棄,所以這些副作用可以忽略不計。Jinq也知道 Customer.getCountry() and String.equals() 方法沒修改任何變數或顯示任何輸出,因此這些方法呼叫也可以被忽略。由此,Jinq 可以得出這樣的結論:執行這個函式只會產生一個作用,它會返回 c.getCountry().equals("Belgium")。

一旦Jinq已明白在 where()方法中傳遞給它的函式,它可以混合資料庫查詢方面的知識,優先於 customers 集合來建立一個新的資料庫查詢。

生成資料庫查詢

這就是 Jinq 如何從你的程式碼生成資料庫查詢的。象徵性執行的使用意味著,這種方法對於不同的Java編譯器輸出的不同的程式碼模式都是相當強大的。如果 Jinq 遇到的程式碼有不能轉化為資料庫查詢的副作用,Jinq 將保持你的這些程式碼不變。因為一切都是用正常的Java程式碼寫的,Jinq 可以直接執行那些程式碼,您的程式碼將產生預期的結果。

這個簡單的翻譯例項應該讓你明白了怎樣查詢翻譯作品。你可以確信,這些演算法可以正確地從你的程式碼生成資料庫查詢。

美好前景

我希望我已經讓你品嚐到了Java 8帶來的在Java中進行資料庫工作的新方式。Java 8 支援的函數語言程式設計允許你用和為Java集合編寫程式碼同樣的方式來為資料庫寫程式碼。希望不久現有的資料庫API都能被擴充套件以支援這些型別的查詢。

Java 8 徹底改變資料庫訪問原文請看這裡