本文示例源代碼或素材下載
對於多數開發人員 — 尤其是各種 Java 版本的用戶 — 數據綁定已經和閉包、單例(singletons)、AJax 一樣變為常用詞匯表中的一部分了。而且和其他術語一樣,數據綁定也常常被錯誤的定義。
特別是大部分程序員在聽到數據綁定 的時候,實際上想的是 XML 數據綁定。加上這個小小的單詞 XML,造成大多數程序員忽略了相當多的功能和靈活性,尤其是如果使用 Castor API 的話。這是因為對於 Castor 而言,XML 數據綁定僅僅是其中的一部分。除了綁定到 XML 文檔之外,Castor 還提供了將 Java 數據綁定到 SQL 數據庫的能力。這就是所謂的 SQL 數據綁定。
定義 SQL 數據綁定
SQL 數據綁定 也許是一個新的術語,概念其實非常簡單。實際上,最好從一個更熟悉的術語 XML 數據綁定 出發來考察。XML 數據綁定就是在 XML 文檔數據 — 通常存儲在元素和屬性中 — 和 Java 對象模型的屬性之間建立映射的過程。可以通過編組程序和解組程序在兩者之間移動數據。編組程序從 Java 對象模型獲得數據存儲到 XML 文檔中,解組程序從 XML 文檔獲取數據存入 Java 對象模型的屬性中。
在這個基礎上,我們說 SQL 數據綁定是在 SQL 數據庫數據 — 存儲在模式、表、列等中 — 和 Java 對象之間建立映射的過程就毫不奇怪了。編組和解組的過程一樣,只不過轉換是在 Java 對象和 SQL 而不是 XML 之間進行的。實際上,多數數據綁定文章中如果將 XML 替換為 SQL,元素數據 更改為表記錄,討論的內容就變成了 SQL 數據綁定了。
SQL 數據綁定的意義
Java 技術剛誕生的時候基本上是一種玩具語言,很大程度上是因為它的 API 非常簡單,而且主要關注於圖像(還記得 AWT 嗎?)。Java 技術走向成熟的標志是 Java 數據庫連接(JDBC),能夠持久到 SQL 數據庫。JDBC 過去(現在也是)惟一的問題是用起來很笨重。並不是說多復雜,而是對於大多數程序而言增加了太多的額外工作。
使用 Castor 和 SQL 數據綁定可以避免大部分復雜性。更妙的是,這種 API 對於 XML 和 SQL 上下文功能基本相同。而且使用數據綁定,應用程序需要關心的細節也少了。處理 JDBC ResultSet 和行數不再是您的代碼的問題了,幾次簡單的編組解組調用就能處理 Java 對象和 SQL 數據庫之間的轉換。
關於 SQL 數據綁定可能最有意思的是它沒有受到過多 的壓力和關注。特別是考慮到一大批程序員對 XML 心存芥蒂,說它冗長笨拙,或者他們更願意使用二進制序列化。但是這些人對 SQL 雙手歡迎。事實上,如果發現有一大批程序員甚至真正的程序員也不認為 SQL 是一種合法的數據存儲和持久技術,您可能會非常吃驚(看看你周圍有多少人認為關系數據庫臃腫笨拙、誇大其詞或者已經落伍)。
既然如此,SQL 數據綁定和它的 XML 兄弟相比無疑更有意義。企業應用程序中必然需要持久(或存儲)數據,而編寫數據訪問和檢索代碼是痛苦的。SQL 數據綁定把一種熟悉的 API(假設您已經閱讀過本系列前面的文章)用於 SQL 數據存儲。幾行命令就能把數據寫入表(或者多個表)或者讀出來。
仍然是映射
SQL 數據綁定特別重要的一點是,能夠建立 Java 對象到 SQL 數據模式的映射而不需要綁定 Java 或者 SQL 名稱。和 XML 相比,關系數據庫的結構通常和 Java 對象的結構相距甚遠。表是數據的集合,對象通常代表一部分(可能是一行)數據。對象之間的關系必須跟蹤到其他對象,就像表之間的關系要跟蹤到其他表。但是 Java 對象模型中沒有一對多的連接表,當然也沒有多對多連接。
即便是中等復雜程度的關系數據庫,設計的東西也和對象模型不同。SQL 和 Java 對象之間的映射,大量的工作就是定義對象和表之間的映射。雖然映射可能很復雜,本文中將使用幾個簡單的映射說明 SQL 數據綁定中映射的基本原理。
這就是 JDO,對嗎?
是。但也不全對。有幾分對。這裡有點讓人費解。
Sun 有一個規范叫 Java 數據對象(Java Data Objects,JDO)。Java Specification Request (JSR) 12(JDO 1.0)和 JSR 243(JDO 2.0)定義了一種非常具體的 SQL 數據綁定方法(雖然從未明確地稱為 “SQL 數據綁定”)。如果讀過 JDO 然後再讀本文開始的介紹部分,就會發現是一件事。但是,Castor 的 JDO(不錯,Castor 也叫 JDO,事情更加混亂)不 同於 Sun 的 JDO,甚至沒有任何關系,只不過兩者的基本目標相同而已。
鑒於可能造成很大混淆,再強調一次:如果使用 Castor 的 JDO,那麼它不是 Sun 的標准 API。但問題並不像看起來這麼糟糕或者簡單明了。實際上,Castor 的開發者正努力將其 API 的很多特性納入 Sun 數據綁定和 JDO API。因此,雖然 Castor 現在還不是標准 API,但很多特性 — 包括 API 自身 — 可能有一天會成為標准化的 JDO API。
建立實驗環境
在 XML 數據綁定中,兩個端點分別是 Java 對象模型和 XML 文檔(實際上是約束文檔的 XML 模式)。對於 SQL 數據綁定來說,仍然有 Java 對象模型,不過另一端變成了 SQL 模式:一個或多個表以及表列。本文的例子使用了一組相對比較簡單的端點。
本系列文章一直使用 Java 對象模型表示圖書和作者,本文仍然使用同樣的數據模型。清單 1 顯示了 Book 類的代碼。
清單 1. Book 類
package ibm.XML.castor;
import Java.util.LinkedList;
import Java.util.List;
public class Book {
/** The book's ISBN */
private String isbn;
/** The book's title */
private String title;
/** The authors' names */
private List<Author> authors;
public Book() { }
public Book(String isbn, String title, List<Author> authors) {
this.isbn = isbn;
this.title = title;
this.authors = authors;
}
public Book(String isbn, String title, Author author) {
this.isbn = isbn;
this.title = title;
this.authors = new LinkedList<Author>();
authors.add(author);
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public String getIsbn() {
return isbn;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setAuthors(List<Author> authors) {
this.authors = authors;
}
public List<Author> getAuthors() {
return authors;
}
public void addAuthor(Author author) {
authors.add(author);
}
}
清單 2 是 Author 類的代碼。
清單 2. 作者類
package ibm.XML.castor;
public class Author {
private String firstName, lastName;
public Author() { }
public Author(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
前面的文章中我們建立了這些類的部分實例並持久到 XML 文檔。前兩期文章我們關注的是直接從對象模型轉換到 XML 文檔,兩邊使用相同的屬性名。第三期文章突出 Java 對象和 XML 之間的來回轉換,但是允許改變轉換前後的命名約定。
這裡我們需要取得類中的所有數據並將其存儲到 SQL 數據庫。不再是元素和屬性,數據要放到列、行和表中。
建立 SQL 數據庫模式
在某些方面,SQL 數據模式和 Java 對象模型的映射更密切,尤其是如果模型非常簡單的話(比如這裡的例子)。有兩個表:dw_books 和 dw_authors。
實際上最簡單的辦法是先建立 dw_authors 表,因為 dw_books 表要引用它。該表包括三列:作者 ID、作者的姓氏和名字。如果按照本文操作的話,可以用 清單 3 中的代碼在 MySQL 中建立該表。
清單 3. 作者表
CREATE TABLE 'dw_authors' (
'id' INT NOT NULL ,
'first_name' VARCHAR( 50 ) NOT NULL ,
'last_name' VARCHAR( 50 ) NOT NULL ,
PRIMARY KEY ( 'id' ) ,
INDEX ( 'first_name' , 'last_name' )
);
請注意,本文中所有的表都使用了 dw_ 前綴,以便於其他表區分開來,特別是 authors 和 books 都是很常見的表名。雖然可能有點羅嗦,但如果按照本文實驗(希望如此)的話這樣可以避免與系統中其他數據庫的命名沖突。
接下來是 dw_books 表。這個表也很簡單,在 SQL 中創建結構使用的數據定義語言(DDL)如 清單 4 所示。
清單 4. dw_books 表的 DDL
CREATE TABLE 'dw_books' (
'isbn' VARCHAR( 13 ) NOT NULL ,
'title' VARCHAR( 200 ) NOT NULL ,
PRIMARY KEY ( 'isbn' ) ,
INDEX ( 'title' )
);
最後還需要將這兩個表聯系起來。這就出現了一個問題。一本書可能有多位作者,就是說 dw_books 和 dw_authors 表之間存在多對多的關系。換句話說,一本書可以有多位作者,一位作者可以出版多本書。因此需要一個表連接圖書和作者,而且這種連接必須是多對多的。
在 SQL 中這是很常見的:需要一個連接表,每行包括圖書 ISBN 和作者 ID。清單 5 顯示了表的結構。
清單 5. 作者和圖書的連接表(多對多)
CREATE TABLE 'dw_books_authors' (
'book_isbn' VARCHAR( 13 ) NOT NULL ,
'author_id' INT NOT NULL ,
PRIMARY KEY ( 'book_isbn' , 'author_id' )
);
理解引用完整性
引用完整性 是一個美妙的 SQL 術語,它指的是保持數據庫中的所有數據都是干淨的,沒有錯誤或者多余的數據,表之間的連接准確合理。對於這個例子來說,它意味著如果刪除一本書,那麼 dw_books_authors 表中關於該書 ISBN 的所有記錄都將被刪除。一旦這本書不存在了,它的 ISBN 也不應該有記錄。對於作者來說情況一樣,刪除一位作者,關於其 ID 的所有記錄也將被刪除。
為此需要使用外鍵。因此,dw_books_authors 表中的 book_isbn 以 dw_books 表中的 isbn 作為外鍵,dw_books_authors 中的 author_id 和 dw_authors 中的 id 也存在同樣的關系。這樣就保證了表間的引用完整性。
不過多數數據庫對引用完整性的處理方式不同,而且 MySQL 很多版本沒有提供完整的支持。因此為了簡化起見,本文沒有使用外鍵。如果願意創建外鍵,當然很好,請參閱數據庫廠商提供的文檔或者咨詢數據庫管理員(DBA)。
設置 SQL 數據綁定
雖然已經使用 Castor 進行 XML 數據綁定,為了能運行 SQL 數據綁定可能仍然需要稍微修改一下(至少增加一些)編程環境。
向類路徑添加 Castor JDO 文件
首先需要把 Castor JDO 類放到類路徑中。如果已經按照本系列第一期文章(鏈接參見 參考資料)的說明進行了安裝,那麼所有的 Castor Java ARchive (JAR) 文件已經在您的機器上了。但是,可能還需要將這些專用的 JDO JAR 文件添加到類路徑中。在 Castor 目錄下找到 castor-1.1.2.1-jdo.jar 並添加到類路徑(您的版本也許更高,但意思一樣)。
這是除了您可能已經放入類路徑的其他 Castor JAR 文件之外的,比如 castor-1.1.2.1.jar、castor-1.1.2.1-XML.jar、Xerces 和 Commons Logging JAR 文件。如果使用應用服務器或者 IDE 運行代碼,要確保適當修改類路徑。
添加 JDBC 驅動程序
連接到 SQL 數據庫需要 JDBC 驅動程序。用於 Oracle、MySQL、PostgreSQL 等的 JDBC 驅動程序都可免費獲得。如果已經存儲訪問數據庫的應用程序,可能已經足夠了。否則,用 Google 搜索針對所用數據庫的 JDBC 驅動程序,下載 JAR 並添加到類路徑中。
對於本文使用的 MySQL 數據庫,需要的 MySQL-connector-Java-3.0.17-ga-bin.jar 可從 MySQL 網站(詳見 參考資料)免費下載。
將類映射到表
最簡單的 SQL 數據綁定任務:把沒有引用其他任何對象的對象映射到一個表。這是 SQL 數據綁定中最基本的操作,其他更高級的功能都是由此演變而來。
確定映射
首先將 Author 類映射到 dw_authors 表。需要的映射如表 1 所示。
表 1. Author 對象和 dw_authors 表之間的映射
Java 屬性 SQL 列 firstName first_name lastName last_name
太簡單了,不過有一個突出的問題:dw_authors 表還有 id 列。事實上這是 dw_authors 表的主鍵,因此必須有。所以需要稍微修改 Author 類,如 清單 6 所示。
清單 6. 為 Author.Java 增加 ID
package ibm.XML.castor;
public class Author {
private String firstName, lastName;
private int id;
public Author() { }
public Author(int id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
現在可以修改顯示 Author 實例和 dw_authors 表行映射的表格了,如 表 2 所示。
表 2. 添加 ID 字段
Java 屬性 SQL 列 id id firstName first_name lastName last_name
建立 Castor 映射
現在需要告訴 Castor 哪個 Java 屬性映射到哪個 SQL 列。這裡使用映射文件,類似於本系列第三期中用於 XML 的映射文件。清單 7 顯示了用於將 Author 實例轉化為 dw_authors 行的映射文件。
清單 7. 把 Author 對象映射到 dw_authors 表
<mapping>
<class name="ibm.XML.castor.Author" identity="id">
<map-to table="dw_authors" />
<fIEld name="id" type="int">
<sql name="id" type="integer" />
</fIEld>
<fIEld name="firstName" type="string">
<sql name="first_name" type="varchar" />
</fIEld>
<fIEld name="lastName" type="string">
<sql name="last_name" type="varchar" />
</fIEld>
</class>
</mapping>
沒有什麼新鮮之處,看起來和用於 Java 對於與 XML 的映射文件沒有什麼兩樣。將該文件保存為 sql-mapping.XML,現在告訴 Castor 如何將 Java 對象屬性映射到數據庫表。
標識符屬性
與 Java/XML 映射相比,映射文件中惟一有變化的是使用了 identity 屬性,用於標識 dw_authors 表的主鍵字段。以前可能沒有使用過 identity 屬性,因為 Castor 常常自己推斷或者根本不需要。但是在 SQL 數據綁定中,該屬性讓 Castor 知道哪個字段惟一定義了一個對象;該例中是作者的 id 字段。
配置 Castor 進行 SQL 訪問
在 XML 數據綁定中,我們已經介紹了編寫代碼把 XML 編組為 Java 對象以及反向操作,但是對於 SQL 數據綁定,還有一個軟件需要應付:數據庫自身。需要告訴 Castor 數據庫在哪兒、如何登錄以及使用哪個映射文件。這就需要用到配置文件了,通常命名為 jdo-conf.XML。清單 8 顯示了可用於 MySQL 的配置文件。
清單 8. 為 Castor 定義 MySQL 連接方案
<?XML version="1.0" ?>
<!DOCTYPE jdo-conf PUBLIC "-//EXOLAB/Castor JDO Configuration DTD Version 1.0//EN"
"http://castor.org/jdo-conf.dtd">
<jdo-conf>
<database name="YOUR-DATABASE-NAME" engine="MySQL">
<driver class-name="com.MySQL.jdbc.Driver"
url="jdbc:MySQL://YOUR-DATABASE-SERVER-HOSTNAME/YOUR-DATABASE-NAME">
<param name="user" value="YOUR-DATABASE-USERNAME"/>
<param name="password" value="YOUR-DATABASE-PASSWord"/>
</driver>
<mapping href="sql-mapping.XML"/>
</database>
<transaction-demarcation mode="local"/>
</jdo-conf>
添加、查找和刪除記錄
現在已經做了很多設置工作,終於可以和數據庫交互了。利用這些配置文件,需要加載配置、打開數據庫然後訪問它。清單 9 完成了這些工作,它創建了一個新的 Author 實例,保存到數據庫,查找該實例然後刪除。
清單 9. 對作者表使用 SQL 數據綁定
import ibm.XML.castor.Author;
import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.JDOManager;
public class SQLTester {
public static void main(String args[]) {
try {
JDOManager.loadConfiguration("jdo-conf.XML");
JDOManager jdoManager = JDOManager.createInstance("bmclaugh");
Database database = jdoManager.getDatabase();
database.begin();
Author author = new Author(1001, "Joseph", "Conrad");
database.create(author);
Author lookup = (Author)database.load(Author.class,
new Integer(1001));
System.out.println("Located author is named " +
author.getFirstName() + " " + author.getLastName());
database.remove(lookup);
database.commit();
database.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
我們來詳細的觀察一下每個步驟。
首先需要進行一些初始化工作,加載連接信息並連接到數據庫。這個步驟基本上和任何使用 SQL 數據綁定的應用程序一樣,因此只需要記住這些命令(或者把本文放入書簽):
JDOManager.loadConfiguration("jdo-conf.XML");
JDOManager jdoManager = JDOManager.createInstance("bmclaugh");
Database database = jdoManager.getDatabase();
database.begin();
這裡的目的是獲得 Database 對象實例,它可用於創建、查找和刪除記錄。得到該實例後就可以創建新的 Author 並存儲到數據庫:
Author author = new Author(1001, "Joseph", "Conrad");
database.create(author);
調用 create() 的時候,Castor 將使用映射文件(sql-mapping.XML)來確定如何將傳遞給該方法的對象存儲為 SQL 數據。數據進入數據庫,然後就能訪問了。
接下來根據標識符(這就用到了 identity 屬性)查找對象:
Author lookup = (Author)database.load(Author.class,
new Integer(1001));
System.out.println("Located author is named " +
author.getFirstName() + " " + author.getLastName());
使用 load() 方法並傳遞需要加載的類類型以及記錄的標識符值。load() 返回一個新實例,可以像其他 Java 對象使用,同樣用到了 sql-mapping.XML 定義的映射。
這個例子刪除了新添加的記錄,保持數據庫原來的狀態:
database.remove(lookup);
最後還需要提交修改並關閉數據庫:
database.commit();
database.close();
添加關系數據
可以看到,處理單一的類到表的映射並不難。但是一旦需要處理更加實際的情況,情況就復雜了,比如 Book 與 Author 實例關聯的 Java 對象模型。
定義基本的圖書 SQL 映射
和 Author 一樣,也需要在 sql-mapping.XML 中定義將 Book 類屬性持久到關系數據庫的映射。首先增加 清單 10 中的代碼,它們用於處理圖書的基本屬性.
清單 10. 向映射文件添加圖書信息
<mapping>
<class name="ibm.XML.castor.Book" identity="isbn">
<map-to table="dw_books"/>
<fIEld name="isbn" type="string">
<sql name="isbn" type="varchar" />
</fIEld>
<fIEld name="title" type="string">
<sql name="title" type="varchar" />
</fIEld>
</class>
<class name="ibm.XML.castor.Author" identity="id">
<map-to table="dw_authors" />
<fIEld name="id" type="int">
<sql name="id" type="integer" />
</fIEld>
<fIEld name="firstName" type="string">
<sql name="first_name" type="varchar" />
</fIEld>
<fIEld name="lastName" type="string">
<sql name="last_name" type="varchar" />
</fIEld>
</class>
</mapping>
沒有奇怪的地方,僅僅確定了 Book 和映射表(dw_books)以及如何映射對象的屬性(isbn 和 title)。
定義多對多關系
現在情況復雜了:告訴 Castor 圖書和作者之間的關系。要記住,關系是這樣建立的:
每本書都有惟一的 ISBN。
每位作者都有惟一的 ID。
如果某位作者寫了一本書,在 dw_books_authors 建立一條記錄包含圖書 ISBN 和作者 ID。
一本書可能有多位作者(dw_books_authors 多條記錄的圖書 ISBN 相同但作者 ID 不同)。
一位作者可以寫多本書(dw_books_authors 多條記錄的作者 ID 相同但圖書 ISBN 不同)。
首先,從 Book 類這一側分析這種關系。對於一個 Book 實例,需要用該書的作者填充 authors 屬性。反之亦然。如果 Book 實例的 authors 屬性有數據,需要將這些作者通過 ID 存入數據庫。必須映射該屬性— authors —到 dw_books_authors 中的記錄。
為此首先添加另一個 Castor fIEld 元素,如 清單 11 所示。
清單 11. 使用 fIEld 元素將 authors 映射為 Books 中的數組
<fIEld name="authors" type="ibm.XML.castor.Author"
collection="arraylist">
</fIEld>
這很好理解,映射 authors 屬性,但其類型是類而不再是 string 或 int。使用 collection="arraylist" 告訴 Castor 該屬性是一個集合而不是單值。
但是現在需要說明該字段要存儲作者 ID 以及該字段要存儲到哪個表,因為作者 ID 在 dw_books 表中不存在。清單 12 給出了多對多表的名稱 — 即 dw_books_authors — 以及該表中圖書 ISBN 的鍵。
清單 12. 定義多表和多對多關系的 sql 元素
<fIEld name="authors" type="ibm.XML.castor.Author"
collection="arraylist">
<sql name="author_id"
many-table="dw_books_authors"
many-key="book_isbn" />
</fIEld>
小心地注意其中的邏輯。首先,要記住該元素用於 Book 實例的 authors 屬性值。映射的實際字段是 Author 實例,需要找到該對象的標識符部分 — id — 並將其映射到列 author_id。但這不是在 dw_books 表中,因此使用 many-table 表明該表為 dw_books_authors。最後,many-key 屬性表明要存儲這個 Book 實例的 ISBN 到多對多表中。
清單 13 顯示了包含這部分的完整 sql-mapping.XML 文件。
清單 13. 為 Books 增加多對多支持
<mapping>
<class name="ibm.XML.castor.Book" identity="isbn">
<map-to table="dw_books"/>
<fIEld name="isbn" type="string">
<sql name="isbn" type="varchar" />
</fIEld>
<fIEld name="title" type="string">
<sql name="title" type="varchar" />
</fIEld>
<fIEld name="authors" type="ibm.XML.castor.Author"
collection="arraylist">
<sql name="author_id"
many-table="dw_books_authors"
many-key="book_isbn" />
</fIEld>
</class>
<class name="ibm.XML.castor.Author" identity="id">
<map-to table="dw_authors" />
<fIEld name="id" type="int">
<sql name="id" type="integer" />
</fIEld>
<fIEld name="firstName" type="string">
<sql name="first_name" type="varchar" />
</fIEld>
<fIEld name="lastName" type="string">
<sql name="last_name" type="varchar" />
</fIEld>
</class>
</mapping>
測試映射
清單 14 顯示了修改後的 SQLTester,它創建了一個新的 Author 和相關的 Book。
清單 14. 建立新的作者和圖書
import Java.util.Iterator;
import ibm.XML.castor.Author;
import ibm.XML.castor.Book;
import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.JDOManager;
public class SQLTester {
public static void main(String args[]) {
try {
JDOManager.loadConfiguration("jdo-conf.XML");
JDOManager jdoManager = JDOManager.createInstance("bmclaugh");
Database database = jdoManager.getDatabase();
database.begin();
Author author = new Author(1001, "Joseph", "Conrad");
Book book = new Book("1892295490", "Heart of Darkness", author);
database.create(author);
database.create(book);
database.commit();
database.begin();
Book lookup = (Book)database.load(Book.class, "1892295490");
System.out.println("Located book is named " + lookup.getTitle());
System.out.println("Authors:");
for (Iterator i = lookup.getAuthors().iterator(); i.hasNext(); ) {
Author bookAuthor = (Author)i.next();
System.out.println(" " + bookAuthor.getFirstName() + " " +
bookAuthor.getLastName());
}
database.commit();
database.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
看起來再簡單不過了。連接到數據庫,創建新的作者和圖書,然後保存到數據庫(最後以 commit() 結束):
Database database = jdoManager.getDatabase();
database.begin();
Author author = new Author(1001, "Joseph", "Conrad");
Book book = new Book("1892295490", "Heart of Darkness", author);
database.create(author);
database.create(book);
database.commit();
剩下的就是檢查數據庫看看數據是否成功存入了。
為了更好地模擬實際的數據持久,提交了數據然後啟動了一個新事務(提交圖書和作者數據之後的第二個 database.begin())。這樣可以確保 Castor 數據的本地版本沒有被使用,程序的第二部分實際上是查詢數據庫。
檢驗數據
看看表中的數據是否和預期的一樣。表 dw_books 中應該包含 表 3 所示的數據。
表 3. 運行 SQLTester 後 dw_books 中的數據
isbn 名稱 1892295490 Heart of Darkness
表 4 顯示了插入之後的 dw_authors 表。
表 4. 運行 SQLTester 之後 dw_authors 表中的數據
id first_name last_name 1001 Joseph Conrad
表 5 顯示了圖書和作者之間的連接,從很多方面來說這是迄今您見過的最重要的數據。如果作者和圖書不能聯系起來,就沒有多少用處了。
表 5. 運行 SQLTester 後 dw_books_authors 多對多表中的數據
book_isbn author_id 1892295490 1001
刪除數據
清單 15 這個短小的實用程序刪除 SQLTester 插入的數據。利用 SQL 數據綁定可以添加圖書和作者(絕對應該這樣做),與此類似,也可通過更新類來刪除這些記錄。保持類的獨立便於在插入和刪除之間檢查數據庫,這是學習和調試可能出現的任何問題的重要組成部分。
清單 15. 刪除 SQLTester 創建的數據
import ibm.XML.castor.Author;
import ibm.XML.castor.Book;
import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.JDOManager;
public class SQLClean {
public static void main(String args[]) {
try {
JDOManager.loadConfiguration("jdo-conf.XML");
JDOManager jdoManager = JDOManager.createInstance("bmclaugh");
Database database = jdoManager.getDatabase();
database.begin();
try {
Book book = (Book)database.load(Book.class, "1892295490");
database.remove(book);
} catch (org.exolab.castor.jdo.ObjectNotFoundException ignored) { }
try {
Author author = (Author)database.load(Author.class, 1001);
database.remove(author);
} catch (org.exolab.castor.jdo.ObjectNotFoundException ignored) { }
database.commit();
database.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
其中嵌入了應對沒有可供刪除的數據這種情況的異常處理語句,使得程序更健壯一點。
Castor 自身不能持久關系
觀察 SQLTester(清單 14),值得注意的是 Author 和 Book 的實例都持久到了數據庫。實際上如果訪問其中一個就只能得到圖書或者作者,而看不到 dw_books_authors 中的任何數據。
對於 Java 程序員來說,這似乎有點奇怪。如果一個 Book 實例有 Author,這兩者難道沒有聯系在一起嗎?沒有作者的圖書也能持久嗎?不錯,Castor 允許這種情況,而且這是它的默認行為。但是,如果希望 Castor 保持這種聯系,可使用下列命令:
database.setAutoStore(true);
該命令必須在創建任何事務之前 發出,因此 清單 16 在 清單 14 的基礎上略加修改來完成這項操作。
清單 16. 自動存儲關系的測試程序
import Java.util.Iterator;
import ibm.XML.castor.Author;
import ibm.XML.castor.Book;
import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.JDOManager;
public class SQLTester {
public static void main(String args[]) {
try {
JDOManager.loadConfiguration("jdo-conf.XML");
JDOManager jdoManager = JDOManager.createInstance("bmclaugh");
Database database = jdoManager.getDatabase();
database.setAutoStore(true);
database.begin();
Author author = new Author(1001, "Joseph", "Conrad");
Book book = new Book("1892295490", "Heart of Darkness", author);
// We can persist just the book, and Castor will handle the author, as well
// database.create(author);
database.create(book);
database.commit();
database.begin();
Book lookup = (Book)database.load(Book.class, "1892295490");
System.out.println("Located book is named " + lookup.getTitle());
System.out.println("Authors:");
for (Iterator i = lookup.getAuthors().iterator(); i.hasNext(); ) {
Author bookAuthor = (Author)i.next();
System.out.println(" " + bookAuthor.getFirstName() + " " +
bookAuthor.getLastName());
}
database.commit();
database.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
試一下(如果數據庫中還有數據首先運行 SQLClean),就會得到和原來相同的結果 — 除了新增的 database.create() 調用之外。雖然這只是一個小小的改進,但設想一下添加數百(或數千)本書及其作者的情形。節省不必要的工作至關重要。
結束語
雖然 XML 數據綁定已經非常穩定,在很大程度上不是增加而是修改,JDO — 包括 Sun 和 Castor 版本 — 仍然是一種通用的 API。但是,這並不意味要避免使用它,因為 SQL 數據綁定和 JDO(無論哪個廠商)都可以簡化您的編程工作。以本文作為起點而不是權威性的指南來學習這一技術。
嘗試一下 SQL 數據綁定看看是否有助於您的應用程序開發(這基本上是必然的)。然後比較一下 Sun 和 Castor 的 JDO 看看喜歡哪一種。最後選擇一種使用。即便 Castor 的版本不是標准的,但是更加靈活,而且隨著更深地參與 Sun,將來有一天官方標准可能納入 Castor 的很多特性。
最重要的是,編程更加輕松。SQL 數據綁定可以幫助您提高效率,降低開發周期,最終提高您的工作質量以及所開發應用程序的質量。在同一個應用程序中使用 SQL 和 XML 數據綁定,兩者采用了同樣的映射范型。您將從中受益,也許最終會完全放棄 JDBC,沒有人認為這樣不好。