CachedRowSet

CachedRowSet 對象是一個數據行的容器,可在記憶體中快取其各行,這使得進行操作時無需總是連線到數據源。此外,它還是一個 JavaBeansTM 組件,是可滾動、可更新、可序列化的。CachedRowSet 對象通常包含結果集中的行,但它也可以包含任何具有表格式的檔案(如電子表格)中的行。參考實現只支持從 ResultSet 對象中獲取數據,但是開發人員可以擴展 SyncProvider 實現,以提供對其他表格數據源的訪問。
應用程式可以修改 CachedRowSet 對象中的數據,這些修改隨後可以被傳播回數據源。
CachedRowSet 對象是一個非連線 rowset,這意味著它只會短暫地連線其數據源。連線數據源發生在讀取數據以用各行填充自身,以及將更改傳播回其底層數據源時。其餘時間 CachedRowSet 對象是非連線的,包括修改它的數據時。非連線使 RowSet 對象更為簡潔,因此更容易傳遞給另一個組件。例如,非連線 RowSet 對象可以被序列化並通過導線傳遞到瘦客戶端 (thin client),如個人數字助理(personal digital assistant,PDA)。
1.0 創建 CachedRowSet 對象
以下代碼行使用參考實現 (RI) 中提供的默認 CachedRowSet 構造方法來創建默認的 CachedRowSet 對象。
CachedRowSetImpl crs = new CachedRowSetImpl();
這一新 CachedRowSet 對象的屬性設定為 BaseRowSet 對象的默認屬性,此外,它將 RIOptimisticProvider 對象作為其同步提供者。RIOptimisticProvider(RI 中包含的兩個 SyncProvider 實現之一)是在沒有指定同步提供者時,SyncFactory 單件 (singleton) 將提供的默認提供者。
SyncProvider 對象提供了帶有 reader 的(RowSetReader 對象)的 CachedRowSet 對象,用於從數據源讀取數據以便用該數據填充自身。可以實現 reader 從 ResultSet 對象或者表格式的檔案中讀取數據。SyncProvider 對象還提供了 writer(RowSetWriter 對象),用於同步在與底層數據源中的數據下線時對 CachedRowSet 對象數據所做的任何更改。
可以實現 writer 以在檢查和避免衝突方面實施不同程度的關注。(如果使用某個值填充 rowset 後更改了數據源中的該值,則會發生衝突。)RIOptimisticProvider 實現假定衝突很少或沒有衝突,因此不設定鎖定。僅在沒有衝突時,它才使用取自 CachedRowSet 對象的值更新數據源。也可以實現其他 writer,使其始終可將修改後的數據寫入數據源,這可以通過不檢查衝突來實現,或者從另一個方面著手,即通過設定足夠的鎖定來防止對數據源中的數據進行更改。這兩種 writer 之間還可以有其他 writer 實現。
CachedRowSet 對象可以使用任何已向 SyncFactory 單件註冊的 SyncProvider 實現。通過調用以下代碼行,應用程式可以找到已註冊的 SyncProvider 實現。
java.util.Enumeration providers = SyncFactory.getRegisteredProviders();
CachedRowSet 對象可使用兩種方式來指定它將使用的 SyncProvider 對象。 向構造方法提供實現名
以下代碼行創建 CachedRowSet 對象 crs2,使用默認值初始化該對象,但其 SyncProvider 對象是指定的。
CachedRowSetImpl crs2 = new CachedRowSetImpl(
"com.fred.providers.HighAvailabilityProvider");
使用 CachedRowSet 方法 setSyncProvider 設定 SyncProvider
以下代碼行為 crs 重置 SyncProvider 對象,該 CachedRowSet 對象是使用默認構造方法創建的。
crs.setSyncProvider("com.fred.providers.HighAvailabilityProvider");
SyncFactory 和 SyncProvider 的注釋。
2.0 從 CachedRowSet 對象獲取數據
從 CachedRowSet 對象獲取數據可使用繼承自 ResultSet 接口的獲取方法。以下示例(其中 crs 是一個 CachedRowSet 對象)演示了如何在各行中進行疊代,獲取每行中的列值。第一個示例使用以列號為參數的獲取方法;第二個示例使用以列名為參數的獲取方法。當 RowSet 對象的命令是 SELECT * FROM TABLENAME 形式時通常使用列號;當命令通過名稱指定列時則通常使用列名。
while (crs.next()) {
String name = crs.getString(1);
int id = crs.getInt(2);
Clob comment = crs.getClob(3);
short dept = crs.getShort(4);
System.out.println(name + " " + id + " " + comment + " " + dept);
}
while (crs.next()) {
String name = crs.getString("NAME");
int id = crs.getInt("ID");
Clob comment = crs.getClob("COM");
short dept = crs.getShort("DEPT");
System.out.println(name + " " + id + " " + comment + " " + dept);
}
2.1 獲取 RowSetMetaData
通過在 RowSetMetaData 對象上調用 ResultSetMetaData 和 RowSetMetaData 的方法,應用程式可以獲得有關 CachedRowSet 對象中各列的信息。以下代碼片斷(其中 crs 是一個 CachedRowSet 對象)展示了該過程。第一行使用關於 crs 中各列的信息創建一個 RowSetMetaData 對象。繼承自 ResultSet 接口的方法 getMetaData 返回一個 ResultSetMetaData 對象,將該對象分配給變數 rsmd 前會將其強制轉換為 RowSetMetaData 對象。第二行查明 jrs 的列數,第三行獲得存儲在 jrs 第二列中 JDBC 類型的值。
RowSetMetaData rsmd = (RowSetMetaData)crs.getMetaData();
int count = rsmd.getColumnCount();
int type = rsmd.getColumnType(2);
RowSetMetaData 接口與 ResultSetMetaData 接口有兩方面不同。
它包括設定方法:當使用取自不同 ResultSet 對象的數據填充 RowSet 對象時,該 RowSet 對象在內部使用這些方法。
它包含較少的獲取方法:某些 ResultSetMetaData 方法無法套用到 RowSet 對象。例如,不會套用那些獲取某個列值是可寫入的還是唯讀的方法,因為 RowSet 對象的所有列要么是可寫入的,要么是唯讀的,這取決於該 rowset 是否可更新。
註:要返回 RowSetMetaData 對象,實現必須重寫 java.sql.ResultSet 中定義的 getMetaData() 方法返回 RowSetMetaData 對象。
3.0 更新 CachedRowSet 對象
更新 CachedRowSet 對象與更新 ResultSet 對象類似,但是因為更新 rowset 時它並未連線到其數據源,所以必須執行額外的步驟才能使更改在底層數據源中生效。調用方法 updateRow 或 insertRow 後,CachedRowSet 對象還必須調用方法 acceptChanges 使更新寫入數據源。以下示例(其中指針在 CachedRowSet 對象 crs 中的行上)顯示了更新當前行中兩個列值並同樣更新 RowSet 對象的底層數據源所需的代碼。
crs.updateShort(3, 58);
crs.updateInt(4, 150000);
crs.updateRow();
crs.acceptChanges();
下一個示例演示了移至插入行、在插入行上構建新行、將新行插入 rowset,然後調用方法 acceptChanges 將新行添加到底層數據源。注意,與獲取方法一樣,更新方法可以採用列索引或列名來指定所操作的列。
crs.moveToInsertRow();
crs.updateString("Name", "Shakespeare");
crs.updateInt("ID", 10098347);
crs.updateShort("Age", 58);
crs.updateInt("Sal", 150000);
crs.insertRow();
crs.moveToCurrentRow();
crs.acceptChanges();
註:insertRow() 方法在何處插入 CachedRowSet 對象的插入行內容是由實現定義的。CachedRowSet 接口的參考實現緊隨當前行插入新行,但也可以實現為在任何其他位置插入新行。
有關這些示例的另一個注意點是它們使用方法 acceptChanges 的方式。通過內部調用 RowSet 對象的 writer 將這些更改寫入數據源,從而將 CachedRowSet 對象中的更改傳播回底層數據源的正是此方法。為此,writer 不得不承受建立到數據源的連線所帶來的開銷。上述兩個代碼片斷在調用 updateRow 或 insertRow 後立即調用方法 acceptChanges。但是,如果更改了多個行,則更高效的做法是在調用所有 updateRow 和 insertRow 後再調用 acceptChanges。如果只調用 acceptChanges 一次,則只需要建立一個連線。
4.0 更新底層數據源
執行 acceptChanges 方法時,在後台調用 CachedRowSet 對象的 writer(一個 RowSetWriterImpl 對象),以便將對 rowset 所作的更改寫入底層數據源。實現該 writer 以建立到數據源的連線並寫入更新。
可通過 SyncProvider 接口的實現提供 writer,這已第 1 部分“創建 CachedRowSet 對象”中討論。默認的參考實現提供者 RIOptimisticProvider 會實現其 writer 使用樂觀並發控制 (optimistic concurrency control) 機制。也就是說,在 rowset 與資料庫斷開時它不對底層資料庫維持任何鎖定,在將數據寫入數據源之前它只是檢查是否有衝突。如果存在衝突,則不向數據源寫入任何內容。
SyncProvider 類提供的 reader/writer 設施是可插入的,允許自定義數據的獲取和更新。如果需要其他的並發控制機制,可使用方法 setSyncProvider 插入其他 SyncProvider 實現。
要使用樂觀並發控制例程,RIOptismisticProvider 要同時維護其當前值及其原始值(剛好位於當前值之前的值)。注意,如果沒有對 RowSet 對象中的數據進行任何更改,則其當前值和原始值相同,都是最初填充 RowSet 對象時使用的值。但是,一旦更改了 RowSet 對象中的任何值,當前值和原始值就不同了,儘管此時原始值仍是最初的值。隨著後續對 RowSet 對象中的數據進行更改,其原始值和當前值仍保持不同,但是其原始值將是前一個當前值。
關注原始值允許 writer 對 RowSet 對象的原始值和資料庫中的值進行比較。如果資料庫中的值與 RowSet 對象的原始值不同,則意味著資料庫中的值已經更改,出現了衝突。writer 是否檢查衝突、檢查的程度如何,以及它如何處理衝突都取決於它的實現方式。
5.0 註冊和通知偵聽器
作為 JavaBeans 組件,參與 JavaBeans 事件模型的所有 rowset 都繼承了用來註冊偵聽器和用來通知這些偵聽器 BaseRowSet 類中發生更改的各種方法。CachedRowSet 對象的偵聽器是一個組件,只要 rowset 中發生更改,它就應得到通知。例如,如果 CachedRowSet 對象包含查詢的結果,並且這些結果將以表格和條形圖之類的形式顯示,則可以向 rowset 將該表格和條形圖註冊為偵聽器,這樣它們可以更新以反映各種更改。要成為偵聽器,表格和條形圖類必須實現 RowSetListener 接口。然後可將它們添加到 CachedRowSet 對象的偵聽器列表,如以下代碼行所示。
crs.addRowSetListener(table);
crs.addRowSetListener(barGraph);
每個移動指針或更改數據的 CachedRowSet 方法也將更改通知已註冊的偵聽器,所以當 crs 中發生更改時 table 和 barGraph 將得到通知。
6.0 向瘦客戶端傳遞數據
使用 CachedRowSet 對象的主要原因之一是要在應用程式的不同組件之間傳遞數據。因為 CachedRowSet 對象是可序列化的,所以可使用它(舉例來說)將運行於伺服器環境的企業 JavaBeans 組件執行查詢的結果通過網路傳送到運行於 web 瀏覽器的客戶端。
由於 CachedRowSet 對象是非連線的,所以和具有相同數據的 ResultSet 對象相比更為簡潔。因此,它特別適於向瘦客戶端(如 PDA)傳送數據,這種瘦客戶端由於資源限制或安全考慮而不適於使用 JDBC 驅動程式。所以 CachedRowSet 對象可提供一種“獲取各行”的方式而無需實現全部 JDBC API。
7.0 滾動和更新
CachedRowSet 對象的第二個主要用途是為那些本身不提供滾動和更新的 ResultSet 對象提供這些功能。換句話說,當 DBMS 不提供對滾動和更新的完全支持時,可使用 CachedRowSet 對象擴充啟用 JDBC 技術的驅動程式(以下稱為“JDBC 驅動程式”)的功能。要使不可滾動和唯讀的 ResultSet 對象變得可滾動和可更新,程式設計師只需創建一個使用該 ResultSet 對象的數據所填充的 CachedRowSet 對象即可。以下代碼片斷演示了這一過程,其中 stmt 是一個 Statement 對象。
ResultSet rs = stmt.executeQuery("SELECT * FROM EMPLOYEES");
CachedRowSetImpl crs = new CachedRowSetImpl();
crs.populate(rs);
現在對象 crs 與對象 rs 一樣,也包含了取自表 EMPLOYEES 的數據。不同的是 crs 的指針可以向前、向後移動,或者移動到特定行,即使 rs 的指針只能向前移動也是如此。此外,即使 rs 是不可更新的,crs 也將是可更新的,因為在默認情況下,CachedRowSet 對象是可滾動和可更新的。
總之,可將 CachedRowSet 對象簡單地看成是一個非連線的行集合,這些行將快取在數據源外部。由於它比較小並且是可序列化的,所以它可以輕鬆地通過導線傳送,並且非常適合於向瘦客戶端傳送數據。但是 CachedRowSet 對象也有局限性:它的大小限制在它一次可在記憶體中存儲的數據量範圍內。
8.0 獲得通用數據訪問
CachedRowSet 類的另一個優勢在於它能夠從關係資料庫以外各種數據源獲取並存儲數據。可以實現 rowset 的 reader 讀取任何表格數據源(包括電子表格或平面檔案)的數據,並用該數據填充其 rowset。因為 CachedRowSet 對象及其元數據都可以從頭創建,所以充當 rowset 工廠的組件可以使用此功能來創建一個包含非 SQL 數據源數據的 rowset。但是,大部分情況下,希望 CachedRowSet 對象包含使用 JDBC API 從 SQL 資料庫中獲取的數據。
9.0 設定屬性
所有 rowset 都維護一個屬性集,通常使用某種工具來設定這些屬性。rowset 具有的屬性的數量和種類各不相同,這取決於 rowset 的用途及其獲得數據的方式。例如,從 ResultSet 對象獲得其數據的 rowset 需要設定那些建立資料庫連線所需的屬性。如果某個 rowset 使用 DriverManager 設施建立連線,則它需要設定一個標識合適驅動程式的 JDBC URL 屬性,還需要設定那些提供用戶名和密碼的屬性。另一方面,如果 rowset 使用 DataSource 對象建立連線(這是首選的方法),則它無需設定 JDBC URL 屬性。但是它需要設定用於數據源邏輯名、用戶名和密碼的屬性。
註:要使用 DataSource 對象建立連線,該 DataSource 對象必須已經向使用 Java Naming and Directory InterfaceTM (JNDI) API 的命名服務註冊。通常由具有系統管理員資格的人員完成此註冊。
為了能夠使用資料庫的數據填充,rowset 需要設定 command 屬性。此屬性是一種 PreparedStatement 對象的查詢,該對象允許查詢具有在運行時(而不是設計時)設定的參數占位符。要用各種值設定這些占位符參數,rowset 要為設定每種數據類型的值提供設定方法,類似於 PreparedStatement 接口提供的設定方法。
以下代碼片斷展示了如何設定 CachedRowSet 對象 crs 的 command 屬性。注意,如果使用某種工具設定屬性,則這就是該工具應使用的代碼。
crs.setCommand("SELECT FIRST_NAME, LAST_NAME, ADDRESS FROM CUSTOMERS " +
"WHERE CREDIT_LIMIT > ? AND REGION = ?");
用於設定該命令占位符參數的值被包含在 RowSet 對象的 params 欄位中,該欄位是一個 Vector 對象。CachedRowSet 類為設定其 params 欄位中的元素提供了一組設定方法。以下代碼片斷演示了如何設定前一個示例查詢中的兩個參數。
crs.setInt(1, 5000);
crs.setString(2, "West");
params 欄位現在包含兩個元素,每個元素都是一個兩元素長的數組。第一個元素是參數號;第二個元素是要設定的值。在這種情況下,params 的第一個元素是 1,5000,第二個元素是 2,"West"。當應用程式調用方法 execute 時,它會依次調用此 RowSet 對象的 reader,該 reader 會依次調用其 readData 方法。作為實現的一部分,readData 將獲得 params 中的值並使用這些值設定命令的占位符參數。以下代碼片斷說明了在獲得 Connection 對象 con 後 reader 如何執行此操作。
PreparedStatement pstmt = con.prepareStatement(crs.getCommand());
reader.decodeParams();
// decodeParams figures out which setter methods to use and does something
// like the following:
// for (i = 0; i < params.length; i++) {
// pstmt.setObject(i + 1, params[i]);
// }
這裡用於 crs 的命令是查詢 "SELECT FIRST_NAME, LAST_NAME, ADDRESS FROM CUSTOMERS WHERE CREDIT_LIMIT > 5000 AND REGION = "West"。readData 方法使用以下代碼行執行此命令後,它會獲得 rs 的數據,該數據用於填充 crs。
ResultSet rs = pstmt.executeQuery();
上述代碼片斷說明了在後台進行的操作;這些操作不會出現在應用程式中,因為應用程式不會調用 readData 和 decodeParams 之類的方法。相反,以下代碼片斷展示了應用程式可能執行的操作。它設定 rowset 的命令、設定 command 屬性並執行該命令。只需調用 execute 方法,就可使用從表 CUSTOMERS 請求的數據生成 crs。
crs.setCommand("SELECT FIRST_NAME, LAST_NAME, ADDRESS FROM CUSTOMERS" +
"WHERE CREDIT_LIMIT > ? AND REGION = ?");
crs.setInt(1, 5000);
crs.setString(2, "West");
crs.execute();
10.0 分頁數據
因為 CachedRowSet 對象在記憶體中存儲數據,所以它在任一時間可以包含的數據量是由可用的記憶體量決定的。要避開此限制,CachedRowSet 對象可以數據塊(稱為頁)的形式從 ResultSet 對象中獲取數據。要利用此機制,應用程式應使用方法 setPageSize 設定一頁中要包括的行數。換句話說,如果頁大小設定為 5,則一次從數據源中獲取一個 5 行的數據塊。應用程式也可選擇設定一次可獲取的最大行數。如果最大行數設定為 0,或者未設定最大行數,則對一次獲取的行數沒有限制。
設定各個屬性後,必須使用方法 populate 或方法 execute 用數據填充 CachedRowSet 對象。以下代碼行演示了如何使用方法 populate。注意,該方法的這種形式採用兩個參數,ResultSet 句柄和 ResultSet 對象中的行,從該行開始獲取各行。
CachedRowSet crs = new CachedRowSetImpl();
crs.setMaxRows(20);
crs.setPageSize(4);
crs.populate(rsHandle, 10);
運行此代碼時,將使用 rsHandle 中從第 10 行開始的 4 行數據填充 crs。
下一個代碼片斷展示了如何使用方法 execute 填充 CachedRowSet 對象,該方法可以採用 Connection 對象作為一個參數,也可以不採用。此代碼向 execute 傳遞 Connection 對象 conHandle。
注意,以下代碼片斷和上述代碼片斷有兩處差別。首先,沒有調用方法 setMaxRows,所以沒有對 crs 可以包含的行數設定限制。(記住,對於 crs 在記憶體中可以存儲的數據量,總是有一個最高限制。)第二個差別是不能向方法 execute 傳遞 ResultSet 對象中起始獲取行的行號。此方法始終從第一行開始獲取。
CachedRowSet crs = new CachedRowSetImpl();
crs.setPageSize(5);
crs.execute(conHandle);
運行此代碼後,crs 將包含由 crs 的命令所生成的 ResultSet 對象中的 5 行數據。crs 的 writer 將使用 conHandle 連線數據源並執行 crs 的命令。然後應用程式就能夠在 crs 中的數據上進行操作,方式與在任何其他 CachedRowSet 對象的數據上進行操作的方式相同。
要訪問下一頁(數據塊),應用程式可調用方法 nextPage。此方法創建新的 CachedRowSet 對象並用下一頁的數據填充。例如,假定 CachedRowSet 對象的命令返回一個具有 1000 行數據的 ResultSet 對象 rs。如果頁大小設定為 100,則首次調用方法 nextPage 將創建一個包含 rs 前 100 行的 CachedRowSet 對象。在使用這前 100 行的數據執行完所需的操作後,應用程式可以再次調用方法 nextPage 創建另一個帶有 rs 第二個 100 行數據的 CachedRowSet 對象。第一個 CachedRowSet 對象中的數據不再存在於記憶體中,因為它已被第二個 CachedRowSet 對象的數據替換了。調用方法 nextPage 10 次後,第十個 CachedRowSet 對象將包含 rs 最後 100 行存儲在記憶體中的數據。在任意給定時間,記憶體中僅存儲一個 CachedRowSet 對象的數據。
只要當前頁不是各行的最後一頁,方法 nextPage 就返回 true,沒有其他頁時,則返回 false。因此,可在 while 循環中使用它來獲取所有頁,正如在以下代碼行中所演示的。
CachedRowSet crs = CachedRowSetImpl();
crs.setPageSize(100);
crs.execute(conHandle);
while(crs.nextPage()) {
while(crs.next()) {
. . . // operate on chunks (of 100 rows each) in crs,
// row by row
}
}
運行此代碼片斷後,應用程式會遍歷所有 1000 行,但是每次記憶體中的數據只有 100 行。
CachedRowSet 接口還定義了方法 previousPage。正如方法 nextPage 類似於 ResultSet 方法 next,方法 previousPage 也類似於 ResultSet 方法 previous。與方法 nextPage 類似,previousPage 創建一個 CachedRowSet 對象,包含作為頁大小設定的行數。因此,(舉例來說)方法 previousPage 可用在上述代碼片斷末尾的 while 循環中,以從最後一頁開始逆向遍歷到第一頁。方法 previousPage 也與 nextPage 類似,因為它也可以用在 while 循環中,不同之處在於它是在前面還有頁時返回 true,前面沒有頁時返回 false。
通過將指針定位於每頁最後一行的後面(如以下代碼片斷所執行的),方法 previous 就可以在每頁中從最後一行遍歷到第一行。代碼也可將指針置於每頁第一行的前面,然後在 while 循環中使用 next 方法,以在每頁中從最第一行遍歷到最後一行。
以下代碼片斷假定是前一個代碼片斷的繼續,這意味著第十個 CachedRowSet 對象的指針位於最後一行。代碼將指針移到最後一行的後面,這樣第一次調用方法 previous 會將指針放回到最後一行上。遍歷最後一頁(CachedRowSet 對象 crs)的所有行後,代碼接著會進入 while 循環以獲得第九頁、向後遍歷各行、轉至第八頁、向後遍歷各行,依此類推,直到第一頁的第一行為止。
crs.afterLast();
while(crs.previous()) {
. . . // navigate through the rows, last to first
{
while(crs.previousPage()) {
crs.afterLast();
while(crs.previous()) {
. . . // go from the last row to the first row of each page
}
}

相關詞條

熱門詞條

聯絡我們