本文分析了在 Java 平台上可用的兩個數據管理策略:Java 對象序列化和 Java 數據庫連接(JDBC)。盡管本質上這兩種數據管理策略並不存在孰優孰劣的問題,但在管理企業信息系統時,JDBC 輕而易舉地得以勝出。在本文中,Java 開發人員 G.V.B. Subrahmanyam 和 Shankar Itchapurapu 對序列化和 JDBC都進行了介紹,並通過討論和實例來向您展示了 JDBC 是您的最佳選擇的原因。
當您正在建立企業信息系統時,需要確保以某種有效的方式存儲、檢索和顯示企業數據。對於所有業務而言,數據都是獨一無二的最大資產。所有軟件系統都涉及數據,因此,數據的重要性是無論如何強調都不過分的。
應用程序的數據管理功能包括四個基本操作,通常也需要對企業數據執行這四個操作,它們是: 建立、檢索、更新和 刪除(即 CRUD)。管理在企業系統的數據涉及在很長一段時間范圍之內,始終如一地、成功地執行 CRUD 操作,而不必頻繁地更改實際執行這些操作的代碼。換句話說,管理數據意味著開發穩健的、可擴展和可維護的軟件系統,以確保成功地進行 CRUD 操作,在軟件的生命期中能夠以一致的方式執行操作。
本文討論了 J2EE 中的兩種可用數據管理策略:Java 對象序列化和 Java 數據庫連接(JDBC)。我們將查看這兩種方法的優缺點。這兩種數據管理策略實質上不存在孰優孰劣。在特定實現中,策略的可用性取決於項目的 范圍(出現在系統環境中的活動的活動范圍),系統的 上下文(驅動系統/子系統運行時的值的集合),以及其他的外部因素。然而,Java 序列化並不適合於企業系統,其數據需要用一種定義良好的結構(如RDBMS)來組織。我們首先將快速浏覽 Java 對象序列化,然後查看 JDBC 更重要的一些方面,從而了解後者是如何實現前者所缺乏的一些關鍵特性的。
本文並不打算對 Java 對象序列化或者 JDBC 進行全面介紹。有關這兩項技術的更多信息,請回顧 參考資料小節。
Java 對象序列化
對象序列化是最簡單的 Java 持久性策略。對象序列化是一個將 對象圖平面化為一個字節的線性序列的過程。對象圖是作為對象繼承、關聯和聚合的結果而實現的一些關系式。對象的非暫態實例屬性以字節的形式被寫入到持久存儲中。實例屬性的值就是執行時間序列化時內存中的值。如果一個 Java 對象是可序列化的,那麼它至少必須實現 java.io.Serializable 接口,該接口具有如下所示的結構:
package java.io;
public interface Serializable
{}
您可以看到, java.io.Serializable 接口並沒有聲明任何方法。它是一個 記號或者 標記接口。它告訴 Java 運行時環境,該實現類是可序列化的。列表 1 顯示實現該接口的一個示例類。
列表 1. MySerializableObject.java
import java.io.Serializable;
public class MySerializableObject extends MySuperClass implements Serializable
{
private String property1 = null;
private String property2 = null;
public String getProperty1()
{
return property1;
}
public void setProperty1(String val)
{
property1 = val;
}
public String getProperty2()
{
return property2;
}
public void setProperty2(String val)
{
property2 = val;
}
private void writeObject(ObjectOutputStream out)
throws IOException
{
out.writeObject (getProperty1 ());
out.writeObject (getProperty2 ());
}
private void readObject (ObjectInputStream in)
throws IOException, ClassNotFoundException
{
setProperty1 ((String) in.readObject ());
setProperty2 ((String) in.readObject ());
}
}
無需自己實現 writeObject(...) 和 readObject(...) 方法來執行序列化;Java 運行時環境具有使這些方法可用的默認實現。然而,您可以重寫這些方法,提供如何存儲對象狀態的您自己的實現。
關於序列化,您需要記住一些要點。首先,在序列化期間,整個對象圖(即,所有父類和被引用類)都將被序列化。其次, Serializable 類的所有實例變量自身都應該是可序列化的,除非已經特別聲明它們為暫態,或者已經重寫 writeObject(...) 和 readObject(...) 來只序列化那些可序列化的實例變量。如果違反了後一規則,在運行時將出現一個異常。
每個後續 J2SE 版本都對對象序列化系統進行少量的增加。J2SE 1.4 也相應地向 ObjectOutputStream and ObjectInputStream 增加 writeUnshared() and readUnshared() 方法。通常,一個序列化的流只包含任何給定對象的一個序列化實例,並且共享對該對象引用的其他對象可以對它進行後向引用。通常期望序列化一個對象獨立於其他對象可能維護的任何引用。非共享的讀寫方法允許對象作為新的、獨一無二的對象被序列化,從而獲得一個類似於對象克隆但開銷更少的效果。
Java 對象序列化存在的問題
序列化涉及到將對象圖從內存具體化到持久存儲(例如硬盤)中。這涉及到大量 I/O 開銷。通常,對應用程序而言,序列化並不是最佳選擇:
管理幾十萬兆字節的存儲數據
頻繁地更新可序列化對象
對存儲企業數據而言,序列化是一個錯誤選擇,因為:
序列化的字節流只對 Java 語言是可讀的。這是一個重大缺陷,因為企業系統通常是異構的,許多應用程序要與其他應用程序共同處理相同的數據。
對象檢索涉及大量的 I/O 開銷。
沒有一個用來從序列化對象圖中檢索獲取數據的查詢語言。
序列化沒有內置的安全機制。
序列化本身並不提供任何事務控制機制,因此不能在那些需要並發訪問從而不使用輔助 API 的應用程序中使用它。
Java 數據庫連接(JDBC)
Java 數據庫連接(JDBC)是一個標准的 API,它使用 Java 編程語言與數據庫進行交互。諸如 JDBC 的調用級接口是編程接口,它們允許從外部訪問 SQL 命令來處理和更新數據庫中的數據。通過提供與數據庫連接的庫例程,它們允許將 SQL 調用集成到通用的編程環境中。特別是,JDBC 有一個使接口變得極其簡單和直觀的例程的豐富收集。
在下面幾個小節中,我們將查看通過 JDBC 與數據庫連接所涉及的一些步驟。我們將特別關注與 Java 對象序列化相比,JDBC 是如何成為一個企業數據管理策略的。
建立一個數據庫連接
在利用 JDBC 做任何其他事情之前,需要從驅動程序供應商那裡獲取數據庫驅動程序,並且將該庫添加到類路徑中。一旦完這項工作,就可以在 Java 程序中使用類似於下面所示的代碼來實現實際的連接。
Class.forName(<your driver class Name>);
Java.sql.Connection conn = DriverManager.getConnection(<connection URL>);
Java 對象序列化並不需要這個該步驟,因為使用序列化來執行持久性操作並不需要 DBMS。 序列化是一個基於文件的機制;因此,在序列化一個對象之前,需要在目標文件系統中打開一個 I/O 流。
創建 JDBC Statement 和 PreparedStatement
可以用 JDBC Statement 對象將 SQL 語句發送到數據庫管理系統(DBMS),並且不應該將該對象與 SQL 語句混淆。 JDBC Statement 對象是與打開連接有關聯,而不是與任何單獨的 SQL 語句有關聯。可以將 JDBC Statement 對象看作是位於連接上的一個通道,將一個或多個(您請求執行的)SQL 語句傳送給 DBMS。
為了創建 Statement 對象,您需要一個活動的連接。通過使用我們前面所創建的 Connection 對象 con ——下面的代碼來完成這項工作。
Statement stmt = con.createStatement();
到目前為止,我們已經有了一個 Statement 對象,但是還沒有將對象傳遞到 DBMS 的 SQL 語句。
當數據庫接收到語句時,數據庫引擎首先會分析該語句並查找句法錯誤。一旦完成對語句的分析,數據庫就必須計算出執行它的最有效方法。在計算上,這可能非常昂貴。數據庫會檢查哪些索引可以提供幫助,如果存在這樣的索引的話,或者檢查是否應該完全讀取表中的所有行。數據庫針對數據進行統計,找出最佳的執行方式。一旦創建好 查詢計劃,數據庫引擎就可以執行它。
生成這樣一個計劃會占用 CPU 資源。理想情況是,如果我們兩次發送相同的語句到數據庫,那麼我們希望數據庫重用第一個語句的訪問計劃,我們可以使用 PreparedStatement 對象來獲得這種效果。
這裡有一個主要的特性是,將 PreparedStatement 與其超類 Statement 區別開來:與 Statement 不同,在創建 PreparedStatement 時,會提供一個 SQL 語句。然後了立即將它發送給 DBMS,在那裡編譯該語句。因而, PreparedStatement 實際上是作為一 個通道與連接和被編譯的 SQL 語句相關聯的。
那麼,它的優勢是什麼呢?如果需要多次使用相同的查詢或者不同參數的類似查詢,那麼利用 PreparedStatement ,語句,只需被 DBMS 編譯和優化一次即可。與使用正常的 Statement 相比,每次使用相同的 SQL 語句都需要重新編譯一次。
還可以通過 Connection 方法創建 PreparedStatement 。下面代碼顯示了如何創建一個帶有三個輸入參數的參數化了的 SQL 語句。
PreparedStatement prepareUpdatePrice = con.prepareStatement(
"UPDATE Sells SET price = ? WHERE bar = ? AND beer = ?");
注意,Java 序列化不支持類似於 SQL 的查詢語言。使用 Java 序列化訪問對象屬性的惟一途徑就是反序列化該對象,並調用該對象上的 getter/accessor 方法。反序列化一個完整的對象在計算上可能很昂貴,尤其是在程序的生命期中,應用程序需要重復執行它。
在執行 PreparedStatement 之前,需要向參數提供值。通過調用 PreparedStatement 中定義的 setXXX() 方法可以實現它。最常使用的方法是 setInt() , setFloat() , setDouble() ,以及 setString() 。每次執行已准備的聲明之前,都需要設置這些值。
執行語句和查詢
執行 JDBC 中的 SQL 語句的方式是根據 SQL 語句的目的而變化的。DDL(數據定義語言)語句(例如表建立和表更改語句)和更新表內容的語句都是通過使用 executeUpdate() 執行的。列表 2 中包含 executeUpdate() 語句的實例。
列表 2. 實際運行中的 executeUpdate()
Statement stmt = con.createStatement();
stmt.executeUpdate("CREATE TABLE Sells " +
"(bar VARCHAR2(40), beer VARCHAR2(40), price REAL)" );
stmt.executeUpdate("INSERT INTO Sells " +
"VALUES ('Bar Of Foo', 'BudLite', 2.00)" );
String sqlString = "CREATE TABLE Bars " +
"(name VARCHAR2(40), address VARCHAR2(80), license INT)" ;
stmt.executeUpdate(sqlString);
我們將通過先前插入的參數值(如上所示)執行 PreparedStatement ,然後在這之上調用 executeUpdate() ,如下所示:
int n = prepareUpdatePrice.executeUpdate() ;
相比之下, 查詢期望返回一個行作為它的結果,並且並不改變數據庫的狀態。這裡有一個稱為 executeQuery() 的相對應的方法,它的返回值是 ResultSet 對象,如列表 3 所示。
列表 3. 執行一個查詢
String bar, beer ;
float price ;
ResultSet rs = stmt.executeQuery("SELECT * FROM Sells");
while ( rs.next() ) {
bar = rs.getString("bar");
beer = rs.getString("beer");
price = rs.getFloat("price");
System.out.println(bar + " sells " + beer + " for " + price + " Dollars.");
}
由於查詢而產生的行集包含在變量 rs 中,該變量是 ResultSet 的一個實例。集合對於我們來說並沒有太大用處,除非我們可以訪問每一個行以及每一個行中的屬性。 ResultSet 提供了一個光標,可以用它依次訪問每一個行。光標最初被設置在正好位於第一行之前的位置。每個方法調用都會導致光標向下一行移動,如果該行存在,則返回 true ,或者如果沒有剩余的行,則返回 false 。
我們可以使用適當類型的 getXXX() 來檢索某一個行的屬性。在前面的實例中,我們使用 getString() 和 getFloat() 方法來訪問列值。注意,我們提供了其值被期望用作方法的參數的列的名稱;我們可以指定用列號來代替列名。檢索到的第一列的列號為 1,第二列為 2,依次類推。
在使用 PreparedStatement 時,可以通過先前插入的參數值來執行查詢,然後對它調用 executeQuery() ,如下所示:
ResultSet rs = prepareUpdatePrice.executeQuery() ;
關於訪問 ResultSet 的注釋
JDBC 還提供一系列發現您在結果集中的位置的方法: getRow() , isFirst() , isBeforeFirst() , isLast() ,以及 isAfterLast() 。
這裡還有一些使可滾動光標能夠自由訪問結果集中的任意行的方法。在默認情況下,光標只向前滾動,並且是只讀的。在為 Connection 創建 Statement 時,可以將 ResultSet 的類型更改為更為靈活的可滾動或可更新模型,如下所示:
Statement stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
ResultSet rs = stmt.executeQuery("SELECT * FROM Sells");
不同的類型選項: TYPE_FORWARD_ONLY 、 TYPE_SCROLL_INSENSITIVE 和 TYPE_SCROLL_SENSITIVE 。可以通過使用 CONCUR_READ_ONLY 和 CONCUR_UPDATABLE 選項來選擇光標是只讀的還是可更新的。對於默認光標,可以使用 rs.next() 向前滾動它。對於可滾動的光標,您有更多的選項,如下所示:
rs.absolute(3); // moves to the third retrieved row
rs.previous(); // moves back one row in the retrieved result set
rs.relative(2); // moves forward two rows in the retrieved result set
rs.relative(-3); // moves back three rows in the retrieved result set
對於可滾動光標的工作方式,這裡有更多的詳細描述。盡管可滾動光標對於特定應用程序是有用的,但是它導致極大的性能損失,所以應該限制和謹慎使用。可以在 參考資料小節中找到關於可滾動 ResultSet 的更多信息。
在序列化中不存在與 JDBC 的 ResultSet 相對應的機制。序列化和 JDBC 觀察底層的數據的角度不同。JDBC (通常)假定底層數據是關系型結構的;而序列化假定底層數據是一個對象圖。兩種技術的底層數據結構存在顯著差異。JDBC 的 Set 結構並不能自然地映射到序列化的對象圖結構,反之亦然。當通過使用序列化語義將一個 Java 對象持久化時,數據的底層結構變成了一個字節流,該字節流展示了已經序列化了的核心對象的各種內部對象之間的關聯。
JDBC 中的 ResultSet 導航是從一個 Set 元素移動到其他元素的過程,而在對象序列化中,這是不可能的,因為序列化涉及到對象關聯,而不是將一組行封裝到一個實體集合中。因此,Java 對象序列化無法向您提供用這種方式訪問數據單獨某個部分的能力。
事務
JDBC 允許將 SQL 語句組合到單獨一個事務中。因此,我們可以通過使用 JDBC 事務特性來確保 ACID 屬性。
Connection 對象執行事務控制。當建立連接時,在默認情況下,連接是自動提交模式下。這意味著每個 SQL 語句自身都被看作是一個事務,並且一完成執行就會被提交。
可以用以下方法開啟或關閉自動提交模式:
con.setAutoCommit(false) ;
con.setAutoCommit(true) ;
一旦關閉了自動提交,除非通過調用 commit() 顯式地告訴它提交語句,否則無法提交 SQL 語句(即,數據庫將不會被持久地更新)。在提交之前的任何時間,我們都可以調用 rollback() 回滾事務,並恢復最近的提交值(在嘗試更新之前)。
我們還可以設置期望的事務隔離等級。例如,我們可以將設置事務隔離等級為 TRANSACTION_READ_COMMITTED ,這使得在提交值之前,不允許對它進行訪問。並且禁止髒讀。在 Connection 接口中為隔離等級提供了五個這樣的值。默認情況下,隔離等級是可序列化的。JDBC 允許我們發現數據庫所設置的是什麼事務隔離等級(使用 Connection 的 getTransactionIsolation() 方法)以及設置適當的等級(使用 Connection 的 setTransactionIsolation() 方法)。
回滾通常與 Java 語言的異常處理能力結合在一起使用。這種結合為處理數據完整性提供一個簡單高效的機制。在下一節中,我們將研究如何使用 JDBC 進行錯誤處理。
注意,Java 對象序列化並不直接支持事務管理。如果您正在使用序列化,則將需要借助其他的 API,例如 JTA,來獲得這個效果。然而,為了獲得事務隔離的效果,可以選擇在執行一個更新操作時同步該序列化對象,如下所示:
Synchronized(my_deserialized_object) {
//Perform the updates etc...
}
利用異常處理錯誤
軟件程序中總是出現一些錯誤。通常,數據庫程序是關鍵性應用程序,而且適當地捕獲和處理錯誤是有必要的。程序應該恢復並且讓數據庫處於某種一致的狀態下。將回滾與 Java 異常處理程序結合使用是達到這種要求的一種簡便方法。
訪問服務器(數據庫)的客戶(程序)需要能夠識別從服務器返回的所有錯誤。JDBC 通過提供兩種等級的錯誤條件來訪問這種信息: SQLException 和 SQLWarning 。 SQLException 是 Java 異常,它(如果未被處理)將會終止該應用程序。 SQLWarning 是 SQLException 的子類,但是它們代表的是非致命錯誤或意想不到的條件,因此,可以忽略它們。
在 Java 代碼中,希望拋出異常或者警告的語句包含於 try 塊中。如果在 try 塊中的語句拋出異常或者警告,那麼可以在對應的某個 catch 語句中捕獲它。每個捕獲語句都指出了它准備捕獲的異常。
換句話說,如果數據類型是正確的,但是數據庫大小超出其空間限制並且不能建立一個新表,則可能會拋出一個異常。 可以從 Connection , Statement ,以及 ResultSet 對象中獲取 SQLWarning 。每個對象都只是存儲最近 SQLWarning 。因此,如果通過 Statement 對象執行其他語句,則將放棄所有早期的警告。列表 4 舉例說明了 SQLWarning 的使用。
列表 4. 實際運行中的 SQLWarnings
ResultSet rs = stmt.executeQuery("SELECT bar FROM Sells") ;
SQLWarning warn = stmt.getWarnings() ;
if (warn != null)
System.out.println("Message: " + warn.getMessage()) ;
SQLWarning warning = rs.getWarnings() ;
if (warning != null)
warning = warning.getNextWarning() ;
if (warning != null)
System.out.println("Message: " + warn.getMessage()) ;
實際上, SQLWarning 在某種程度上比 SQLException 更為罕見。最常見的是 DataTruncation 警告,它表示在從數據庫讀或寫數據時存在問題。
Java 並沒有提供序列化所使用的特定的異常類。使用序列化時發生的大多數異常都與執行的 I/O 操作有關,因此,在這些情況中 I/O 異常類將滿足要求。
批處理
JDBC 2.0 提供一個用於批處理的強大API。批處理允許積累一組 SQL 語句,並且將它們一起發送並處理。一個典型的批處理就是銀行應用程序,該應用程序每隔一刻鐘就要更新許多賬號。在減少從 Java 代碼到數據庫的往返次數方面,批處理是一個強大功能。
Statement 接口提供 addBatch(String) 方法,將 SQL 語句添加到一個批處理中。一旦已經將所有的 SQL 語句都增加到該批處理中,就可以使用 executeBatch() 方法一起執行它們。
然後,用 executeBatch() 方法執行 SQL 語句,並返回 int 值的一個數組。該數組包含受每條語句影響的行數。將 SELECT 語句或者其他返回 ResultSet 的 SQL 語句放在一個批處理中會導致 SQLException 。
列表 5 中列出了利用 java.sql.Statement 進行批處理的一個簡單實例。
列表 5. 實際運行中的批處理
Statement stmt = conn.createStatement();
stmt.insert("DELETE FROM Users");
stmt.insert("INSERT INTO Users VALUES('rod', 37, 'circle')");
stmt.insert("INSERT INTO Users VALUES('jane', 33, 'triangle')");
stmt.insert("INSERT INTO Users VALUES('freddy', 29, 'square')");
int[] counts = stmt.executeBatch();
在您不知道特定語句將運行的次數時,批處理是一個處理 SQL 代碼的好方法。例如,如果在不使用批處理的情況下試圖插入 100 條記錄,那麼性能可能會受到影響。如果編寫一個腳本,增加 10000 條記錄,那麼情況會變得更糟。添加批處理可以幫助提高性能,後者甚至能夠提高代碼的可讀性。
Java 對象序列化並不支持批處理。通常,會在某個對象的范圍(聯系圖)上運用序列化,在這種情況下,批處理沒有意義。因此,批處理在數據更新的定時和分組方面為您提供一定的靈活性,而這些對於序列化來說不一定是可用的。
從 Java 代碼調用存儲過程
存儲過程是一組 SQL 語句,它們建立了一個邏輯單元,並執行特定任務。可以用存儲過程來封裝一個操作或者查詢的集合,這些操作或查詢都將在一個數據庫服務器上執行。存儲過程是在數據庫服務器中被編譯和存儲的。因此,每次調用存儲過程時,DBMS 都將重用已編譯的二進制代碼,因此執行速度會更快。
JDBC 允許您從 Java 應用程序中調用數據庫存儲過程。第一步是創建 CallableStatement 對象。與 Statement 和 PreparedStatement 對象一樣,這項操作是用一個打開的 Connection 對象完成的。 CallableStatement 對象包含對存儲過程的調用;但它並不包含存儲過程自身。列表 6 中的第一行代碼使用 con 連接建立了對存儲過程 SHOW_ACCOUNT 的調用。波形括號中括住的部分是存儲過程的轉義語法。當驅動程序遇到 {call SHOW_ACCOUNT} 時,它將該轉義語法翻譯成數據庫所使用的本地 SQL,從而調用名為 SHOW_ACCOUNT 的存儲過程。
列表 6. 實際運行中的存儲過程
CallableStatement cs = con.prepareCall("{call SHOW_ACCOUNT(?)}");
cs.setInt(1,myaccountnumber);
ResultSet rs = cs.executeQuery();
假設 Sybase 中的存儲過程 SHOW_ACCOUNT 包含列表 7 中所示的代碼。
Listing 7. SHOW_ACCOUNT stored procedure
CREATE PROCEDURE SHOW_ACCOUNT (@Acc int)
AS
BEGIN
Select balance from USER_ACCOUNTS where Account_no = @Acc
END
ResultSet rs 看起來類似於:
balance
----------------
12000.95
注意,用來執行 cs 的方法是 executeQuery() ,由於 cs 調用的存儲過程只包含一個查詢,所以只產生一個結果集。如果該過程只包含一個更新或者一個 DDL 語句,則將使用 executeUpdate() 方法。然而,有時候存在存儲過程包含多個 SQL 語句的情況,在這種情況下,它將產生多個結果集、多個更新計數,或者結果集和更新計數的某種結合。因此,應該使用 execute() 方法執行 CallableStatement 。
CallableStatement 類是 PreparedStatement 的子類,因此 CallableStatement 對象可以接受與 PreparedStatement 對象相同的參數。而且, CallableStatement 對象可以接受輸出參數,並將該參數用於輸入和輸出。 INOUT 參數和 execute() 方法通常很少使用。要想處理 OUT 參數,需要通過使用 registerOutParameter(int, int) 方法將 OUT 參數注冊到存儲過程。
舉例說明,我們假設 GET_ACCOUNT 過程包含列表 8 中的代碼。
列表 8. GET_ACCOUNT
CREATE PROCEDURE GET_ACCOUNT (@Acc int, @balance float OUTPUT)
AS
BEGIN
Select @balance = balance from USER_ACCOUNTS where Account_no = @Acc
END
在這個實例中,參數 balance 被聲明是一個 OUT 參數。現在,調用該過程的 JDBC 代碼如列表 9 所示。
列表 9. 調用一個存儲過程的 JDBC 代碼
CallableStatement csmt = con.prepareCall("{GET_ACCOUNT(?,?)");
csmt.setInt(1,youraccountnumber);
csmt.registerOutParamter(2,java.sql.Types.FLOAT);
csmt.execute();
正使用 Java 序列化時,並不需要訪問任何外部的系統,如 DBMS。換句話說,序列化是一個純 Java 語言現象,它不涉及執行一個外部環境中的已編譯代碼。因此,在序列化中不存在與 CallableStatement 對象相對應的機制。這意味著您不能將數據處理轉移到外部系統或者組件中,盡管這些系統或者組件可能更適合它。
包裝
在讀完本文之後,我們希望您贊同:對於數據管理和持久化而言, JDBC 是比 Java 對象序列化要好得多的方法。
JDBC 是一個用來訪問數據存儲的極好的 API。 JDBC 最好的東西是它提供單一的 API 集合來訪問多種數據源。用戶只需要學習一個 API 集合,就可以訪問任何數據源,這些數據源可以是關系型的、層次型的或者任何其他格式。您需要的只是一個 JDBC 驅動程序,用它連接到目標數據源。JDBC 做了大量工作,將所有技術細節都封裝到了一個實現軟件包中,從而將程序員從供應商特定的桎梏中解放出來。
表 1 對比了 JDBC 和 Java 對象序列化的各種特性。
表 1. JDBC 對 Java 序列化
對象序列化 JDBC 數據管理 使用文件系統存儲序列化對象格式。這些系統中不包括特定的數據管理系統。序列化對象(存儲在普通文件中的)通常是以自己的特殊方式通過底層 OS 來管理的。 使用一個 EAI/數據庫來存儲數據。EAI 或者數據庫具有一個用來管理數據源中的數據指定的數據庫管理系統(DBMS)。JDBC 是將請求發送到 DBMS 的 JVM 和 DBMS 之間的接口。JDBC 自身並不具有任何數據管理功能。 數據結構 底層的數據結構是一個對象圖。序列化將 Java 對象的狀態寫入到文件系統。 底層的數據結構可以是關系型的、層次型的,或者是網絡形狀。但是數據的邏輯視圖通常是一個表。 數據定義 數據定義涉及到使用序列化語義建立一個可序列化對象並持久化該對象。 數據定義涉及到在目標數據存儲中建立必要的表,並且提供實體集之間的域級關系的明確定義。這一般是通過使用目標 DBMS 所提供的軟件來完成的。 數據檢索 數據檢索涉及反序列化對象,並使用訪問者方法讀取對象的屬性。 DBMS 提供一個特殊的數據子語言來檢索數據。通過 JDBC API 可以將以這種數據子語言編寫的語句傳遞給目標數據源。DBMS 負責驗證、執行和返回該語句的結果。 安全 沒有可以使用的定義良好的安全機制。然而,底層的 OS 可以提供已序列化文件的安全。 DBMS 提供一個廣泛的安全特性集合。它可以完成認證和授權的工作。在可以訪問或者操作 DBMS 上的數據之前,JDBC API 需要給目標 DBMS發送證書。 事務 沒有可以使用的特定的事務控制機制。通過使用其他的 J2EE API,例如 JTA 或者 JTS,可以在程序上維護事務。 DBMS 提供復雜的事務管理。JDBC API 提供有用的方法來提交和回滾事務。 並發控制 沒有可以使用的特定的並發控制機制。不過,通過使用 Java 語言中的同步技術可以獲得並發控制的效果。 DBMS 提供多種等級的事務隔離。可以使用 JDBC API 方法來選擇一個特定等級的隔離。
Java 對象序列化和 JDBC 是 Java 技術領域中許多數據持久化機制中的兩種。在需要在多個 JVM 之間以 Java 語言特定格式共享數據(例如用 RMI 的按值傳遞機制共享數據)時,序列化最適合不過。然而,Java 序列化並不適用於企業數據,需要以一種定義良好的結構對這些數據進行組織。在這樣的企業系統中,需要在多個系統和子系統之間共享數據,而這些系統並不一定都與 Java 語言兼容。在這種情況中,對象序列化根本不能工作。
JDBC 提供一個公用 API來訪問異構的數據存儲。它是 JVM 和目標 DBMS 之間的粘合劑。它提供了一個使用 Java 平台訪問數據存儲和維護企業數據的綱領性方法。然而,執行 CRUD 操作所需的所有代碼都是由開發人員編寫。
為了在企業環境中最有效地使用 JDBC,架構設計人員需要分析其企業中的數據,並開發一個用於數據持久性的框架。由於使用 JDBC 持久化數據的機制與系統想要解決的商業問題無關,因此強烈建議將數據持久性層與應用程序的商業邏輯相分離。設計模式對設計這種框架非常有幫助。