原文地址:http://Java.sun.com/developer/technicalArticles/J2SE/Desktop/javadb/
原作者:John O'Conner
日期:2006-03
摘要
學習如何在Java桌面應用中部署基於Apache Derby的Java DB。該文章使用住址名冊例子來向你演示一個嵌入式數據庫Java DB是如何工作的。
正文
Sun Microsystems最近公布發行支持基於100%Java技術的開源數據庫Java DB——Apache Derby數據庫。Derby之前是以Cloudscape的名字存在並被人使用,它是由Cloudscape、Informix和IBM共同所有。後來,IBM把Derby產品源代碼捐贈給Apache基金會作為一個開源項目。Sun、IBM 其他企業和個人作為Apache Derby社區的一部分也積極參與該關系數據庫的開發。Java DB遍布在Sun許多產品中,包括Sun Java Enterprise System和Sun Java System Application Server。NetBeans集成開發環境(IDE) 5.0也支持Java DB。
Java DB是一個只有2MB的輕量級數據庫,並可嵌入到Java技術的桌面應用中。目前桌面應用可以訪問帶有觸發器、存儲過程和支持SQL語句的強大數據庫存儲器,Java Database Connectivity(JDBC)和Java Platform, Enterprise Edition(Java EE,以前稱為J2EE),都嵌入了同樣的Java虛擬機(JVM)。(見腳注)
這篇文章描述了如何下載、安裝、集成和在桌面應用中部署Java DB。住址名冊這個例子將演示一個嵌入式數據庫Java DB是如何工作的。
內容
-創建住址名冊示例
-安裝Java DB
-在NetBeans IDE 5.0裡集成Java DB
-裝載數據庫驅動
-連接Java DB數據庫
-創建數據庫
-使用數據庫
-發布你的應用程序
-概要
創建住址名冊示例
住址名冊示例使用Java DB來存儲地址信息。這個示例存儲名字、電話號碼、email地址和郵政地址。它答應你建立一個新地址條目並可以存儲、編輯和刪除它們。這個應用程序在用戶的主目錄下名為.addressbook的子目錄下創建它的數據庫。這個數據庫嵌入在應用程序中的,所以這裡不需要設立和治理一個分開的服務器或系統。要發布這個嵌入式數據庫應用程序,我們僅僅需要應用程序JAR文件和數據庫類JAR文件。插圖1展示該示例的用戶界面(UI)。
插圖1:住址名冊使用嵌入式的數據庫Java DB
住址名冊的主框架窗口是AddressFrame類,它繼續於Java Foundation Classes/Swing(JFC/Swing)JFrame。AddressFrame類是一個放置其他圖形組件的容器,同時也擔當控制和處理子組件產生的不同事件。這些子組件是JPanel的子類,每個都有不同的職責:
l AddressPanel顯示地址記錄。它也提供編輯存在的記錄和創建新記錄的UI。它包含顯示Address對象的所有主要屬性的文本域。
l AddressActionPanel設置該程序所需的按鈕。AddressFrame必須處理這個面板所產生的事件。例如:當用戶點擊Save按鈕,這個面板產生一個事件。AddressFrame監聽並處理這個面板的所有重要事件。
l AddressListPanel設定一個帶滾動條的列表來列出名冊姓名,顯示在AddressFrame的左邊。這個列表控制一個ListEntry對象。ListEntry存儲數據庫記錄的唯一標識。這個記錄標識(ID)答應應用程序找到該記錄的全部信息並顯示在AddressPanel上。
該應用程序是用Data Access Object(DAO)去分離數據庫特定的代碼。DAO封裝了數據庫connections和statements。一個DAO是一個有益的設計模式,它答應在應用程序和持久化存儲機制之間的松耦合。應用程序的AddressDao類是一個DAO的例子。當AddressFrame編輯、保存或修改Address對象時,它總是使用一個AddressDao類的實例。雖然住址名冊應用程序使用的是Java DB,你也可以改變它,並使用一個完全不同的數據庫,僅僅需要修改這個類而已。
安裝Java DB
獲取Java DB最簡單的方法就是從Sun Developer Network的Java DB站點去下載。二進制版本文件包提供你編寫嵌入式數據庫應用程序所需要的文件。當你下載完該文件後,你將得到Java DB目錄結構,它包含下面一些子目錄:
l Demo子目錄有兩個示例程序。一個例子顯示如何創建一個普通的嵌入式應用。另一個例子則顯示如何在客戶端-服務器環境下使用Java DB。
l Frameworks子目錄包含的功能有環境變量設置和建立和啟動數據庫。對於住址名冊的示例,這個類是沒有用的,因為我們的應用程序將是完全獨立的。沒有外部的功能被使用。
l Javadoc子目錄包含API文檔。這個目錄非常有用的,當你配置你的IDE時,都要指明Java DB API javadoc的位置。
l Docs子目錄包含關於Java DB產品的一些文檔:安裝、治理和參考指南。
l 最後,lib子目錄包含Java DB庫打包成的JAR文件。閱讀Java DB文檔可以找到不同的庫。對於一個嵌入式數據庫應用程序,我們們只需要derby.jar庫文件。
安裝Java DB只需要在你的應用程序環境變量裡加入derby.jar文件。它是如此的簡單。你可以在你的Solaris,Linux,Windows裡設置環境變量,或者在其他主環境裡導入這個JAR文件,或者你可以在編譯和運行的時候通過命令行參數導入這個文件。假如你使用ANT,住址名冊示例的ANT腳本將告訴你在發布工程時如何導入這些JAR文件。另外,某些IDE,包括NetBeans IDE 5.0,答應你設置工程的環境變量。
在NetBeans IDE 5.0裡集成Java DB
大多數IDE都提供了添加庫文件到開發環境的方法。下面將指導你如何在NetBean IDE5.0裡添加Java DB庫文件:
1. 在Tools菜單裡選擇庫治理器,如插圖2。
插圖2:庫治理器答應你添加第三方庫文件到你的工程裡
2. 在庫治理器窗口,創建一個名為JavaDBEmbedded的新庫,如插圖3。點擊OK。
插圖3:設置你工程需要的庫的名字
3. 添加derby.jar文件到JavaDBEmbedded庫,在庫治理器窗口裡點擊Add JAR/Folder…。通過文件選擇框選擇derby.jar文件。如插圖4。
插圖4:添加derby.jar文件到JavaDBEmbedded庫
4. 在同樣的庫治理器窗口的JavaDBEmbedded庫裡,選擇Javadoc標簽。從你安裝的Java DB目錄裡添加javadoc子目錄。現在,當你在NetBeans IDE工程裡使用JavaDBEmbedded庫時,就可用的Java DB API javadoc。
你現在可以使用工程的屬性設置來為NetBeans IDE 5.0工程添加JavaDBEmbedded庫文件。當你在IDE下編譯,調試和運行該應用程序時,IDE將能找到所需要的derby.jar文件。
為了適用用戶使用其他的IDE,我將derby.jar文件放在提供下載的地址名冊工程的lib子目錄裡。也附帶了只使用NetBeans IDE就能直接構建和運行該示例的ANT腳本。
裝載數據庫驅動
裝載JDBC技術驅動啟動數據庫治理系統。Java DB的驅動來自於derby.jar文件,因此你不需要再下載任何東西。裝載JDBC驅動通過引用Class.forName方法。嵌入式驅動名是org.apache.derby.jdbc.EmbeddedDriver,你也可以使用其他的JDBC驅動裝載它。
Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
地址名冊示例從配置屬性文件裡讀取驅動名,並傳遞該名字到loadDriver方法。另外,之前提到的,地址名冊封裝所有的數據庫功能到Data Access Object(DAO),core Java EE design pattern習慣訪問數據從多樣的來源。在Java SE應用程序中,就像地址名冊一樣,DAO模式工作得相當好。下面的代碼片段表示AddressDao文件如何讀取驅動名和裝載驅動:
private Properties bProperties = null;
public AddressDao(String addressBookName) {
this.dbName = addressBookName;
setDBSystemDir();
dbProperties = loadDBProperties();
String driverName = dbProperties.getProperty("derby.driver");
loadDatabaseDriver(driverName);
...
}
private Properties loadDBProperties() {
InputStream dbPropInputStream = null;
dbPropInputStream =
AddressDao.class.getResourceAsStream("Configuration.properties");
dbProperties = new Properties();
try {
dbProperties.load(dbPropInputStream);
} catch (IOException ex) {
ex.printStackTrace();
}
return dbProperties;
}
private void loadDatabaseDriver(String driverName) {
// Load the Java DB driver.
try {
Class.forName(driverName);
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
}
連接Java DB數據庫
JDBC技術連接定義一個獨立的數據庫並答應你執行治理任務。任務包括啟動,停止,復制,甚至刪除數據庫。驅動治理器提供所有數據庫連接。從驅動治理器獲得一個連接,倘若定義了URL字符串和一組屬性值,將改變數據庫連接的交互性。一個非常普通的方法是在連接時關聯用戶名和密碼屬性。
所有連接的URL都使用下面的格式:
jdbc:derby:<dbName>[propertyList]
dbName是定義一個獨立數據庫的URL。一個數據庫可以有一個或多個位置:在當前工作目錄裡,在classpath裡,在JAR文件裡,在一個獨有的Java DB數據庫主目錄裡,或者是你的文件系統裡的絕對位置。治理數據庫位置的最簡單方法是在你嵌入式環境裡設置derby.system.home系統屬性。這個屬性告訴Java DB裡所有數據庫的默認主位置。通過設置這些屬性,地址名冊示例確保Java DB總是能找到正確的應用程序數據庫。應用程序數據庫名字為DefaultAddressBook,它將存在於derby.system.home屬性所指示的目錄裡。連接該數據庫的URL應該像這樣:
jdbc:derby:DefaultAddressBook
可選值propertyList是一組屬性,你可以傳遞給數據庫系統。你可以傳遞屬性到Java DB系統,要麼是URL它本身,或是是分開的屬性對象。假如屬性是URL的一部分,應該用分號來隔開每個屬性值。最常用的屬性是:
l create=true
l databaseName=nameOfDatabase
l user=username
l passWord=userPassword
l shutdown=true
要連接DefaultAddressBook數據庫,該示例必須首先設置derby.system.home系統屬性。該示例使用的是用戶主目錄的.addressbook子目錄。使用System類去找到用戶的主目錄。然後使用該類來設置derby.system.home屬性:
private void setDBSystemDir() {
// Decide on the db system Directory: <userhome>/.addressbook/
String userHomeDir = System.getProperty("user.home", ".");
String systemDir = userHomeDir + "/.addressbook";
// Set the db system directory.
System.setProperty("derby.system.home", systemDir);
}
一旦應用程序有了明確的指示,所有的數據庫都將存在,它可以獲得一個數據庫連接。在這個例子裡,注重,我添加了連接屬性到數據庫URL。
Connection dbConnection = null;
String strUrl = "jdbc:derby:DefaultAddressBook;user=dbuser;password=dbuserpwd";
try {
dbConnection = DriverManager.getConnection(strUrl);
} catch (SQLException sqle) {
sqle.printStackTrace();
}
或者,你可以把這些屬性放在Properties對象裡。當獲取連接時,使用這個Properties對象做參數:
Connection dbConnection = null;
String strUrl = "jdbc:derby:DefaultAddressBook";
Properties props = new Properties();
props.put("user", "dbuser");
props.put("password", "dbuserpwd");
try {
dbConnection = DriverManager.getConnection(strUrl, props);
} catch(SQLException sqle) {
sqle.printStackTrace();
}
創建數據庫
地址名冊示例應用程序沒有現成的數據庫。換句話說,當該應用程序啟動時,必須創建數據庫。在這個應用程序中使用嵌入式數據庫最大的好處是不需要用戶關心數據庫設置的細節。應用程序可以控制數據庫存在的地方,存在那些表和如何進行處理。
地址名冊創建了一個名為DefaultAddressBook的數據庫,在用戶主目錄的一個子目錄裡,它不會告訴用戶任何附加的信息。當獲得一個數據庫連接後,通過使用create=true屬性,你可以在Java DB裡創建一個新的數據庫。由於我們的數據庫將使用DefaultAddressBook數據庫,我們首先應該創建這個數據庫。假設我們在之前討論時已經設置了derby.system.home屬性值,應用程序創建數據庫和連接如下:
Connection dbConnection = null;
String strUrl = "jdbc:derby:DefaultAddressBook;create=true";
try {
dbConnection = DriverManager.getConnection(strUrl);
} catch (SQLException ex) {
ex.printStackTrace();
}
因為create=true屬性值被包含在裡面,Java DB首先會嘗試創建數據庫文件。創建數據庫並不是實際創建任何應用表。然而,你應該能在你的主目錄下找到一個名為.addressbook/DefaultAddressBook的子目錄。
在數據庫被創建後,應用程序將創建表。該示例僅使用一個ADDRESS表在默認應用程序APP計劃。下面是創建ADDRESS表的SQL代碼:
CREATE table APP.ADDRESS (
ID INTEGER NOT NULL
PRIMARY KEY GENERATED ALWAYS AS IDENTITY
(START WITH 1, INCREMENT BY 1),
LASTNAME VARCHAR(30),
FIRSTNAME VARCHAR(30),
MIDDLENAME VARCHAR(30),
PHONE VARCHAR(20),
EMAIL VARCHAR(30),
ADDRESS1 VARCHAR(30),
ADDRESS2 VARCHAR(30),
CITY VARCHAR(30),
STATE VARCHAR(30),
POSTALCODE VARCHAR(20),
COUNTRY VARCHAR(30) )
每個記錄有一個記錄標識或ID域。Java DB為每條新記錄產生這些值,它將添加到數據。在每條地址記錄的ID域是一個要害值。
其他的地址記錄域都是不同長度的varchar類型。例如,LASTNAME域能容納最大30個varchar字符。Varchar類型等價於UTF-16 Java char類型。
Java技術編碼使用下面的代碼用上面的SQL語句去創建ADDRESS表。dbConnection是與先前提到的代碼相同的。我們把它傳遞到createTable方法中,創建一個新的Statement,並在新建的數據庫裡調用execute方法運行SQL代碼。strCreateAddressTable實例變量保存SQL語句文本。
private boolean createTables(Connection dbConnection) {
boolean bCreatedTables = false;
Statement statement = null;
try {
statement = dbConnection.createStatement();
statement.execute(strCreateAddressTable);
bCreatedTables = true;
} catch (SQLException ex) {
ex.printStackTrace();
}
return bCreatedTables;
}
現在,數據庫和ADDRESS表都存在於主目錄下名為.addressbook/DefaultAddressBook子目錄裡。雖然你可以浏覽這個子目錄,避免修改任何文件。假如你直接編輯或刪除這些數據庫文件,你就破壞了你數據庫的完整性。
使用數據庫
一旦數據庫和它的表被創建,你的應用程序可以創建一個新的連接和聲明去增加,編輯,刪除,或獲得記錄。在地址名冊中,這些響應是由AddressActionPanel裡的按鈕所控制。插圖5顯示了這幾個可選項。
l New.創建一個新的地址記錄
l Delete.刪除當前顯示的地址記錄
l Edit.編輯當前的地址記錄
l Save.保存新的和編輯過的地址記錄
l Cancel.取消任何編輯或任何嘗試新建的記錄
插圖5:地址名冊有幾個按鈕與記錄交互
應用程序的主窗口是AddressFrame,它同時擔當控制和顯示。它用AddressActionPanel注冊它自己去接收通知,當用戶點擊響應區的按鈕時。
New按鈕清除地址條目面版,並答應用戶去編輯所有文本框。這裡沒有使用SQL命令,不過UI應該答應你登錄一個新地址。
Delete按鈕嘗試刪除當前選種的地址記錄。AddressFrame從AddressPanel獲取當前選擇的Address標識,並使用AddressDao刪除該記錄。該面板調用它自己的deleteAddress方法。該方法用正確的ID做參數調用DAO的deleteRecord方法。在刪除數據庫記錄後,應用程序必須刪除AddressListPanel的ListEntry。
private void deleteAddress() {
int id = addressPanel.getId();
if (id != -1) {
db.deleteRecord(id);
int selectedIndex = addressListPanel.deleteSelectedEntry();
...
}
...
}
在AddressDao裡,deleteRecord方式實際上是從數據庫刪除一條記錄。當第一次創建數據庫連接時,AddressDao就創建了一個PreparedStatement。
stmtDeleteAddress = dbConnection.prepareStatement(
"DELETE FROM APP.ADDRESS " +
"WHERE ID = ?");
PreparedStatement可以使用多次,每次只需要一個參數就能確定刪除哪條記錄。在設置了ID參數後,deleteRecord方法執行Update。
public boolean deleteRecord(int id) {
boolean bDeleted = false;
try {
stmtDeleteAddress.clearParameters();
stmtDeleteAddress.setInt(1, id);
stmtDeleteAddress.executeUpdate();
bDeleted = true;
} catch (SQLException sqle) {
sqle.printStackTrace();
}
return bDeleted;
}
Edit按鈕答應用戶在AddressPanel裡編輯當前選擇的Address記錄。在這個例子中,你可以修改記錄的名字,城市或電話號碼。
Save按鈕要麼是在AddressPanel裡創建和編輯新的Address,要麼就嘗試編輯更新存在的記錄。假如用戶是編輯一個記錄。Save按鈕將使用新的信息來更新記錄。假如用戶是創建一個新的記錄,Save按鈕將插入一個新的記錄到數據庫。新的還沒有被保存。這時,它的ID域仍然設置的是默認值-1。一旦你保存這條記錄,這個值是自動生成的,並成為該記錄的唯一標識。
下面是AddresFrame裡的代碼是通過調用DAO的editRecord或saveRecord方法來保存編輯後或新建的地址記錄。當然,當你創建一條新記錄,應用程序也必須更新AddressListPanel。
private void saveAddress() {
if (addressPanel.isEditable()) {
Address address = addressPanel.getAddress();
int id = address.getId();
if (id == -1) {
id = db.saveRecord(address);
address.setId(id);
String lname = address.getLastName();
String fname = address.getFirstName();
String mname = address.getMiddleName();
ListEntry entry = new ListEntry(lname, fname, mname, id);
addressListPanel.addListEntry(entry);
} else {
db.editRecord(address);
}
addressPanel.setEditable(false);
}
}
DAO的editRecord方法必須更新Address記錄裡更改的域。由於該應用程序示例不能分辨修改和未修改的域,它就更新了記錄的所有域。下面是PreparedStatement對象和editRecord方法:
stmtUpdateExistingRecord = dbConnection.prepareStatement(
"UPDATE APP.ADDRESS " +
"SET LASTNAME = ?, " +
" FIRSTNAME = ?, " +
" MIDDLENAME = ?, " +
" PHONE = ?, " +
" EMAIL = ?, " +
" ADDRESS1 = ?, " +
" ADDRESS2 = ?, " +
" CITY = ?, " +
" STATE = ?, " +
" POSTALCODE = ?, " +
" COUNTRY = ? " +
"WHERE ID = ?");
...
public boolean editRecord(Address record) {
boolean bEdited = false;
try {
stmtUpdateExistingRecord.clearParameters();
stmtUpdateExistingRecord.setString(1, record.getLastName());
stmtUpdateExistingRecord.setString(2, record.getFirstName());
stmtUpdateExistingRecord.setString(3, record.getMiddleName());
stmtUpdateExistingRecord.setStri