本文介紹如何開發(fā)將 HSQLDB 純 Java 關(guān)系數(shù)據(jù)庫服務(wù)器集成到 Eclipse Workbench 中的插件。盡管不如 DB2 功能強(qiáng)大,也不如 MySQL 流行,但 HSQLDB(超音速 SQL 數(shù)據(jù)庫)可以滿足很大范圍內(nèi) Java 應(yīng)用程序的需要,因?yàn)樗哂锌蓴U(kuò)展性,而且對(duì)內(nèi)存/處理器的要求不高。
超音速 SQL 數(shù)據(jù)庫后來正式更名為 HSQLDB,它是一類純 Java 撰寫的嵌入式關(guān)系數(shù)據(jù)庫服務(wù)器,您可以在單機(jī)模式(使用直接文件訪問)或客戶機(jī)/服務(wù)器模式中使用它,它支持大量的并發(fā)用戶。盡管不如 DB2 功能強(qiáng)大,也不如 MySQL 流行,但 HSQLDB(超音速SQL數(shù)據(jù)庫)可以滿足很大范圍內(nèi) Java 應(yīng)用程序的需要,因?yàn)樗哂锌蓴U(kuò)展性,而且對(duì)內(nèi)存/處理器的要求不高。
HSQLDB 是一類使用方便的 Java 開發(fā)數(shù)據(jù)庫,因?yàn)樗С?Structured Query Language(SQL)的豐富子集,并且 Java 程序員根本不需要在他們的開發(fā)工作站上安裝嚴(yán)重消耗處理器、內(nèi)存和磁盤空間的數(shù)據(jù)庫服務(wù)器。它對(duì)于集成到 Eclipse IDE 中來說是一種很理想的工具,既能為新手也能為經(jīng)驗(yàn)豐富的開發(fā)人員提供有用的工具。
本文及同一系列的后續(xù)文章將向您展示如何構(gòu)建一組 Eclipse 插件,以將 HSQLDB 嵌入到 Eclipse Workbench 中。您將看到一個(gè)現(xiàn)實(shí)世界中的例子,目的是說明如何在考慮到 API 和用戶接口(UI)的情況下開發(fā)這類插件,以及如何評(píng)估可供選擇的方法以給用戶帶來所需要的功能。本文假定您使用的是 Eclipse SDK 分布,而不是 Platform Runtime-Binary 加上 JDT。但如果只是為了開發(fā)常規(guī) Java 應(yīng)用程序,則后者更加適合。
在這個(gè)系列中,我們將根據(jù)“Levels of Integration” 一文中描述的基本原理(請(qǐng)參閱本文后面的 參考資料 中給出的鏈接),使用三個(gè)步驟創(chuàng)建并擴(kuò)展插件組:
- 運(yùn)行 Eclipse 中現(xiàn)有的工具, 以便從 Workbench 菜單容易地訪問所需要的預(yù)先存在的工具。
- 探討如何使用 Eclipse 的其他功能來向預(yù)先存在的工具集添加值,從而提高 Java 開發(fā)人員的生產(chǎn)力。
- 使用 SWT 來重寫工具,以實(shí)現(xiàn)與 Eclipse Workbench 的無縫集成。
了解 HSQLDB 您可以從 SourceForge(hsqldb.; 請(qǐng)參閱 參考資料 中給出的鏈接)下載 HSQLDB,其中包括源代碼和文檔。這里注意不要與已經(jīng)被凍結(jié)的原始 SourceForge 項(xiàng)目( hsql. )相混淆。
二進(jìn)制分布是一個(gè)標(biāo)準(zhǔn)的 ZIP 文件,而您要做的就是把這個(gè) ZIP 文件解壓縮到您硬盤上的某個(gè)地方。所有 HSQLDB 組件 ——數(shù)據(jù)庫引擎、服務(wù)器進(jìn)程、JDBC 驅(qū)動(dòng)程序、文檔以及一些實(shí)用工具——都放在一個(gè)單獨(dú)的 JAR 包中,這個(gè)包安裝在 lib/hsqldb.jar 中,大小在 260 KB 左右。運(yùn)行數(shù)據(jù)庫引擎只需 170 KB 的 RAM,這即使是 PDA (如 Sharp 生產(chǎn)的 Zaurus)也能夠滿足,而且包括源文件和文檔在內(nèi)的整個(gè)下載文件小到可以放到一張標(biāo)準(zhǔn)的 1.44 MB 軟盤上。
您可以從命令行啟動(dòng)數(shù)據(jù)庫服務(wù)器和實(shí)用工具,具體方法是調(diào)用像 org.hsqldb.Server 和 org.hsqldb.util.DatabaseManager 這樣的方便的類,這兩個(gè)類均可以接受為數(shù)不多的一組命令行選項(xiàng),如“-url”(用于遠(yuǎn)程連接)、“-database”(用于直接文件訪問)和“-user”。還有一種“-?”選項(xiàng)也可以被接受,其作用是提供關(guān)于有效命令行語法的幫助。
造成 HSQLDB 簡單性的關(guān)鍵因素是SQL語句執(zhí)行的順序化。也就是說,盡管許多并發(fā)用戶可以連接到數(shù)據(jù)庫上(當(dāng)數(shù)據(jù)庫以服務(wù)器模式運(yùn)行時(shí)),但是所有 SQL 語句都被放到一個(gè)隊(duì)列中,然后一次執(zhí)行一條。因此不需要實(shí)現(xiàn)復(fù)雜的鎖定及同步算法。盡管如此,HSQLB 還是實(shí)現(xiàn)了 ACID(Atomicity, Consistency, Isolation, and Durability,即原子性、一致性、隔離性和持久性) 語義。換句話說,它是一個(gè)事務(wù)性的數(shù)據(jù)庫,但僅僅處于讀未提交級(jí)別,還不具備事務(wù)隔離功能。HSQLDB 實(shí)際上是為嵌入式應(yīng)用程序而不是為共同數(shù)據(jù)中心而創(chuàng)建的。
如果您想要使用觸發(fā)器、聚合函數(shù)、外部聯(lián)接、視圖以及其他 SQL 功能,HSQLB 都可以滿足您的需要(大部分輕量級(jí)關(guān)系數(shù)據(jù)庫無法做到這一點(diǎn))。通過把您的 Java 類添加到 HSQLB 的類路徑中,您可以實(shí)現(xiàn)存儲(chǔ)過程。然后您發(fā)出一條 CREATE FUNCTION 語句即可大功告成。事實(shí)上,像 SQRT 和 ABS 之類的許多標(biāo)準(zhǔn) SQL 函數(shù)都被實(shí)現(xiàn)為到標(biāo)準(zhǔn) Java 類(比如 java.lang.Math )的直接映射 。
HSQLDB 的運(yùn)行模式 HSQLDB 引擎可以以多種模式運(yùn)行,以適應(yīng)不同的應(yīng)用場(chǎng)合:
駐留內(nèi)存模式 所有數(shù)據(jù)庫表和索引都放在內(nèi)存中,而且永遠(yuǎn)不會(huì)保存到磁盤上。在您發(fā)出為什么有人想要使用在應(yīng)用程序終止時(shí)就會(huì)丟失的數(shù)據(jù)庫這樣的疑問之前,請(qǐng)先考慮為您可以使用標(biāo)準(zhǔn) SQL 語句進(jìn)行查詢、排序、分組和更新的數(shù)據(jù)庫數(shù)據(jù)擁有一塊本地高速緩存。
單機(jī)模式 應(yīng)用程序使用 JDBC 創(chuàng)建一個(gè)數(shù)據(jù)庫連接,并且 HSQLDB 引擎運(yùn)行在該應(yīng)用程序中,這時(shí)允許直接訪問數(shù)據(jù)庫文件。不能存在并發(fā)用戶(應(yīng)用程序獨(dú)占地訪問數(shù)據(jù)庫文件),但因此也沒有額外的線程和 TCP 連接開銷。單機(jī)模式是許多嵌入式應(yīng)用程序的首選模式。
服務(wù)器模式 這是類似于其他關(guān)系數(shù)據(jù)庫的標(biāo)準(zhǔn)客戶機(jī)/服務(wù)器數(shù)據(jù)庫配置,允許出現(xiàn)使用 TCP 套接字的并發(fā)連接。大部分開發(fā)人員喜歡這種模式,因?yàn)樗试S任何 JDBC 客戶機(jī)在主應(yīng)用程序仍在運(yùn)行的情況下連接并查詢/更新表。
Web服務(wù)器模式 HSQLDB 可以用作 Web 服務(wù)器,可以通過 HTTP 接受 SQL 查詢;也能作為任何標(biāo)準(zhǔn) Web 容器中的 servlet 來運(yùn)行,可以穿過防火墻或者安裝在 Web 宿主服務(wù)上,而不用涉及到提供者支持小組(和昂貴的數(shù)據(jù)庫宿主選項(xiàng))。由于 HTTP 是無狀態(tài)的,所以本模式中不存在事務(wù)。
HSQLDB 數(shù)據(jù)庫文件結(jié)構(gòu) HSQLDB 將所有表和索引數(shù)據(jù)放在內(nèi)存中, 將所有發(fā)出的 SQL 語句保存到一個(gè)名為 database.script 的文件中,該文件同時(shí)也充當(dāng)著事務(wù)日志的角色。初始化引擎之后,該文件被讀取,然后其中所有的 SQL 語句都被運(yùn)行,從而完成整個(gè)數(shù)據(jù)庫的重建。停機(jī)期間,HSQLDB 引擎將生成一個(gè)新的 database.script 文件,其中只包含最少的語句,目的是讓數(shù)據(jù)庫可以快速啟動(dòng)。
除了默認(rèn)放在內(nèi)存中的表之外,HSQLDB 還支持“緩存”表和“文本”表。所有緩存表的數(shù)據(jù)放在一個(gè)名為 database.data 的文件中,而文本表的數(shù)據(jù)則放在由 set table source 非標(biāo)準(zhǔn) SQL 語句命名的任意分隔文本文件(像 CSV 文件)中。緩存表支持比可用 RAM 大的數(shù)據(jù)集,而文本表則可以作為一種導(dǎo)入導(dǎo)出數(shù)據(jù)的方便手段。
除了 database.script 和 database.data 文件之外,任何 HSQLDB 數(shù)據(jù)庫還可能包含一個(gè) database.properties 文件,管理員可以在該文件中設(shè)置許多影響到 ANSI SQL 兼容性的參數(shù)。所有數(shù)據(jù)庫文件(文本表數(shù)據(jù)文件除外)必須放在同一個(gè)目錄中。
不存在創(chuàng)建 HSQLDB 數(shù)據(jù)庫的顯式方法。如果您要求引擎打開一個(gè)目前不存在的數(shù)據(jù)庫文件(使用服務(wù)器模式的 -database 選項(xiàng)或單機(jī)模式的 JDBC URL),就會(huì)創(chuàng)建該文件及其所在目錄。所以,如果您肯定那個(gè)空數(shù)據(jù)庫中存在數(shù)據(jù),請(qǐng)檢查是否有錄入錯(cuò)誤。
現(xiàn)在讓我們開始開發(fā)插件!
創(chuàng)建 HSQLDB Eclipse 插件組 把現(xiàn)有的應(yīng)用程序放到 Eclipse 這樣功能強(qiáng)大的工具中去并不是一件容易的事情。值得慶幸的是,HSQLDB 和 Eclipse 均降低了上述任務(wù)的難度,因?yàn)?HSQLDB 本身可以嵌入到其他應(yīng)用程序中,而 Eclipse 提供了清晰而且易于理解的插件基礎(chǔ)設(shè)施以及用于創(chuàng)建新插件的健壯的開發(fā)環(huán)境 PDE。即使您以前從未接觸過 Eclipse 插件開發(fā),PDE 也可以讓您很容易上手。請(qǐng)參閱本文后面 參考資料 部分中講述 Eclipse 基礎(chǔ)知識(shí)的文章。
這些指導(dǎo)性內(nèi)容假定您使用的是 Eclipse SDK 分布,而不是 Platform Runtime-Binary 加 JDT。但如果您只是要開發(fā)常規(guī) Java 應(yīng)用程序,則后者更加適合。您可以使用您最喜歡的操作系統(tǒng),因?yàn)槲覀儗⒅皇褂?Java 代碼,而根本不會(huì)用到本機(jī)代碼。
為了創(chuàng)建有用的插件組并盡可能地少書寫代碼,我們將從最不費(fèi)力的工作開始。稍后,在這個(gè)系列的下一部分內(nèi)容中,我們將看到如何利用 Eclipse 功能為 HSQLDB 提供增值。
在本文中,我們將集中講述我們代碼的下列功能:
- 以服務(wù)器模式啟動(dòng) HSQLDB 引擎,這樣用戶應(yīng)用程序和 SQL 控制臺(tái)(像 HSQLDB 自帶的 DatabaseManager 實(shí)用工具)都可以運(yùn)行 SQL 語句。
- 完全停止 HSQLDB 服務(wù)器。
- 調(diào)用 DatabaseManager 實(shí)用工具,這樣開發(fā)人員可以從 Workbench 交互式地輸入 SQL 語句。
- 使用 HSQLDB ScriptTool 實(shí)用工具運(yùn)行 SQL 腳本文件。這些年來,為了創(chuàng)建數(shù)據(jù)庫表和插入測(cè)試數(shù)據(jù),我已經(jīng)在我的項(xiàng)目文件夾中放入了大量 *.sql 文件,而且長久以來,我一直希望能夠有一種容易的方式來運(yùn)行它們。
- 配置 HSQLDB 連接屬性,比如 TCP 端口和管理員密碼。
如何才能使得這些函數(shù)可以為 Workbench 所用呢?完成前面三步最容易的方式是提供一個(gè) actionSet ,它被添加到新的頂級(jí)菜單中并且可從它自己的工具欄訪問。運(yùn)行 SQL 腳本文件的操作必須只被綁定到擴(kuò)展名為“*.sql” 的文件上,所以這將是一個(gè) objectContribution , 它被 Workbench 添加到顯示這些文件的任意視圖上的彈出式菜單中。最后,連接參數(shù)要能很好地符合插件的參數(shù)選擇頁面,從 Workbench Window 菜單中可以訪問這個(gè)頁面。
圖 1 顯示了新的菜單和工具欄,而圖 2 顯示了 Navigator 視圖的彈出式菜單中的新項(xiàng),圖 3 則顯示了屬性頁面,這樣您就可以看到我們的插件組的第一個(gè)版本是什么樣子。
圖 1.HSQLDB 菜單和相關(guān)工具欄

圖 2. 添加到 *.sql 文件的彈出式菜單項(xiàng)

圖 3. HSQLDB 連接屬性

把 HSQLDB 變成一個(gè) Eclipse 插件 構(gòu)建我們的插件組的第一步是把 HSQLDB 本身包裝成一個(gè) Eclipse 插件。這個(gè)插件將只包含 hsqldb.jar 和 Workbench 要求的必要的plugin.xml 文件。如果您已經(jīng)瀏覽了標(biāo)準(zhǔn) Eclipse 插件目錄,那么您可能已經(jīng)發(fā)現(xiàn),JUnit, Xerces, Tomcat,以及其他常見的 Java 包被隔離在它們各自的插件中,未曾改變,而且所有的 Eclipse 細(xì)節(jié)都被封裝在其他插件中。這種劃分方式使得這些第三方工具易于更新,而不一定要求改變 Eclipse 本身。另外一個(gè)好處就是與許多插件共享這些常見的庫很容易。
打開您的 Eclipse SDK 安裝,并創(chuàng)建一個(gè)新的插件項(xiàng)目;將其命名為 hsqldb.core(我知道推薦使用的名稱是 org.hsqldb.core,但是我不愿意假裝使用了 HSQLDB 名稱空間?;蛟S HSQLDB 開發(fā)人員閱讀至此會(huì)贊同這個(gè)想法,并推薦它為“正式的”Eclipse 集成插件;這樣的話該名稱極有可能被改掉)。確保選中的是“Empty Plugin” 選項(xiàng), 而不是任何插件模板;否則,您將得到一個(gè)毫無用處的頂級(jí)插件類,如果您創(chuàng)建了該類之后希望刪掉它,那么它可以被安全地刪除。把 hsqldb.jar 從您的 HSQLDB 安裝拷貝到項(xiàng)目目錄中,并將其添加到項(xiàng)目的 Runtime。您可以使用 PDE Plugin Manifest Editor或者簡單地拷貝清單 1 中的內(nèi)容來完成這項(xiàng)工作。 清單 1. 用于 hsqldb.core 插件的 plugin.xml 清單文件
<?xml version="1.0" encoding="UTF-8"?>
<plugin
id="hsqldb.core"
name="Hsqldb Core Plug-in"
version="0.0.1"
provider-name="Fernando Lozano (www.lozano.eti.br)">
<runtime>
<library name="hsqldb.jar">
<export name="*"/>
</library>
</runtime>
</plugin>
|
添加 Workbench 擴(kuò)展 接下來,創(chuàng)建名為 hsqldb.ui 的第二個(gè)插件項(xiàng)目。這個(gè)項(xiàng)目將為插件組的第一修訂本包含所有的 Eclipse 擴(kuò)展:包含三個(gè)操作的一個(gè)操作集、與 *.sql 文件相關(guān)的對(duì)象作用,以及一個(gè)屬性頁面。將其主類(Plugin 類)命名為 PluginUi ,并接受包的默認(rèn)名稱 hsqldb.ui 。
使用 Plugin Manifest Editor 打開 plugin.xml 文件,并選擇 Extensions 選項(xiàng)卡。將所分配菜單更名為 HSQLDB 并改變示范操作,使其顯示標(biāo)簽“Runs HSQLDB Database Manager”。向帶有標(biāo)簽“Stops HSQLDB database server” 和“Starts HSQLDB database server”的同一個(gè) actionSet 添加另外兩個(gè)操作,并為每個(gè)操作提供惟一的操作 ID 和實(shí)現(xiàn)類。請(qǐng)注意,上述操作將以與創(chuàng)建時(shí)相反的順序(即與在插件清單文件中出現(xiàn)的順序相反)顯示在菜單和工具欄中。
單擊 Add 按鈕,從而使用 Extension 模板、彈出式菜單和屬性頁面添加兩個(gè)新的擴(kuò)展。彈出式菜單應(yīng)該與 *.sql 文件模式相關(guān)聯(lián),但是屬性頁面字段應(yīng)該通過編程進(jìn)行設(shè)置。
圖 4 顯示了 Plugin Manifest 編輯器的最終外觀,并顯示了所有的插件擴(kuò)展;圖 5 顯示了對(duì)象分配的屬性(注意針對(duì) *.sql 文件的過濾器),而圖 6 顯示了文件資源彈出式菜單中的“Run SQL Script”操作的屬性。 圖標(biāo)在本文的源代碼中給出(請(qǐng)參閱 參考資料),但是我敢肯定您認(rèn)識(shí)有比這畫得更好的圖形專家!
圖 4. 清單編輯器上的 HSQLDB 操作

圖 5. 清單編輯器上的 HSQLDB 對(duì)象分配

圖 6. 清單編輯器上的 Run SQL Script 操作

在能夠給我們的操作添加代碼之前,我們需要將這種 UI 插件與相應(yīng)的核心插件區(qū)別開來,否則它將不能訪問 HSQLDB 類。轉(zhuǎn)到 Plugin Manifest Editor 上的“Dependencies” 頁面,并添加一個(gè)插件依賴性,如圖 7 所示。某些依賴性,像“eclipse.ui”,由 PDE 自動(dòng)進(jìn)行配置,而其他依賴性,像 “org.eclipse.debug.core”,則在對(duì)操作進(jìn)行編碼時(shí)添加。如果您情愿直接編輯 XML 代碼,清單 2 顯示了 hsqldb.ui 插件的完整 plugin.xml 文件。
要完成插件的安裝工作,請(qǐng)?zhí)砑右粋€(gè)新類到 hsqldb.ui 包中,然后將其命名為 HsqldbUtil 。這個(gè)類將包含所有直接處理 HSQLDB 的代碼,并使分配的擴(kuò)展代碼保持簡單。
圖 7. hsqldb.ui 插件依賴性
 清單 2. hsqldb.ui 插件的 plugin.xml 清單文件
<?xml version="1.0" encoding="UTF-8"?>
<plugin
id="hsqldb.ui"
name="Hsqldb Ui Plug-in"
version="0.0.1"
provider-name="Fernando Lozano (www.lozano.eti.br)"
class="hsqldb.ui.PluginUi">
<runtime>
<library name="ui.jar"/>
</runtime>
<requires>
<import plugin="org.eclipse.core.resources"/>
<import plugin="org.eclipse.ui"/>
<import plugin="hsqldb.core"/>
<import plugin="org.eclipse.debug.core"/>
<import plugin="org.eclipse.jdt.launching"/>
<import plugin="org.eclipse.debug.ui"/>
</requires>
<extension
point="org.eclipse.ui.actionSets">
<actionSet
label="Hsqldb"
visible="true"
id="hsqldb.ui.actionSet">
<menu
label="Hsql&db"
id="hsqldbMenu">
<separator
name="dbServerGroup">
</separator>
</menu>
<action
label="Run Hsql &Database Manager"
icon="icons/dbman.gif"
tooltip="Runs the Hsql database manager"
class="hsqldb.ui.actions.HsqldbDatabaseManagerAction"
menubarPath="hsqldbMenu/dbServerGroup"
toolbarPath="dbServerGroup"
id="hsqldb.ui.actions.HsqldbDatabaseManagerAction">
<enablement>
<pluginState
value="activated"
id="hsqldb.ui">
</pluginState>
</enablement>
</action>
<action
label="S&top Hsqldb"
icon="icons/stop.gif"
tooltip="Stops the Hsql database server"
class="hsqldb.ui.actions.HsqldbStopAction"
menubarPath="hsqldbMenu/dbServerGroup"
toolbarPath="dbServerGroup"
id="hsqldb.ui.actions.HsqldbStopAction">
<enablement>
<pluginState
value="activated"
id="hsqldb.ui">
</pluginState>
</enablement>
</action>
<action
label="&Start Hsqldb"
icon="icons/start.gif"
tooltip="Starts the Hsql database server"
class="hsqldb.ui.actions.HsqldbStartAction"
menubarPath="hsqldbMenu/dbServerGroup"
toolbarPath="dbServerGroup"
id="hsqldb.ui.actions.HsqldbStartAction">
<enablement>
<pluginState
value="installed"
id="hsqldb.ui">
</pluginState>
</enablement>
</action>
</actionSet>
</extension>
<extension
point="org.eclipse.ui.perspectiveExtensions">
<perspectiveExtension
targetID="org.eclipse.ui.resourcePerspective">
<actionSet
id="hsqldb.ui.actionSet">
</actionSet>
</perspectiveExtension>
</extension>
<extension
point="org.eclipse.ui.popupMenus">
<objectContribution
objectClass="org.eclipse.core.resources.IFile"
nameFilter="*.sql"
id="hsqldb.ui.SQLScriptFiles">
<action
label="Run SQL Script"
class="hsqldb.ui.popup.actions.HsqldbRunScript"
menubarPath="additions"
enablesFor="1"
id="hsqldb.ui.HsqldbRunScript">
<enablement>
<pluginState
value="activated"
id="hsqldb.ui">
</pluginState>
</enablement>
</action>
</objectContribution>
</extension>
<extension
id="hsqldb.ui.preferences"
point="org.eclipse.ui.preferencePages">
<page
name="HSQLDB Server"
class="hsqldb.ui.preferences.HSQLDBPreferencePage"
id="hsqldb.ui.preferences.HSQLDBPreferencePage">
</page>
</extension>
</plugin>
|
啟動(dòng) HSQLDB
使用類 org.hsqldb.Server 以服務(wù)器模式啟動(dòng) HSQLDB 引擎相當(dāng)容易。以HSQLDB 數(shù)據(jù)庫名稱(路徑+數(shù)據(jù)庫文件的基本名稱)、用于監(jiān)聽連接請(qǐng)求的TCP 端口以及一個(gè)用于判別停機(jī)時(shí)它是否應(yīng)該調(diào)用 System.exit() 的標(biāo)志 作為命令行參數(shù)。下面的命令行是運(yùn)行 HSQLDB 時(shí)的典型情況:
java -cp /opt/hsqldb/hsqldb.jar org.hsqldb.Server -database /tmp/bd -port 9001 -system_exit=true
上面這一行命令創(chuàng)建了 /tmp/db.script、 /tmp/db.properties 和 /tmp/db.data 三個(gè)數(shù)據(jù)庫文件。
我們可以使用 Server 類主方法并傳遞一個(gè)字符串?dāng)?shù)組作為其參數(shù)。但是我們必須在一個(gè)新線程中做這項(xiàng)工作;否則,我們將鎖定整個(gè) Workbench。惟一的插件類維持一個(gè)到這個(gè)線程的引用,這樣它就能夠在連接到某臺(tái)服務(wù)器之前檢查該服務(wù)器是否已經(jīng)啟動(dòng),并且還可以檢查它是否正在運(yùn)行,因?yàn)槿魏慰蛻魴C(jī)均可連接到這臺(tái)服務(wù)器上,然后提交 SHUTDOWN 語句終止它的運(yùn)行。
清單 3 中的代碼顯示了 hsqldb.ui.actions.HsqldbStartAction 的 run 方法,而清單 4 顯示了 hsqldb.ui.HsqldbUtil 中的 startHsqldb 的代碼,這些代碼實(shí)際上啟動(dòng)了服務(wù)器。稍后我們將討論管理用戶反饋的技術(shù)。
上述代碼最有趣的部分是我們?nèi)绾握业揭粋€(gè)位置來放置數(shù)據(jù)庫文件。一個(gè)名為 .hsqldb 的項(xiàng)目如果不存在就會(huì)被創(chuàng)建,而在該項(xiàng)目中引擎將查找 database.script 和其他數(shù)據(jù)庫文件。
Eclipse 在一個(gè)可以保存任何配置文件的標(biāo)準(zhǔn)目錄中提供了所有插件??梢酝ㄟ^調(diào)用 plugin.getStateLocation 來得到這樣一個(gè)目錄,但是我并不覺得數(shù)據(jù)庫文件實(shí)際上是插件配置文件。它們看起來更像是用戶數(shù)據(jù)文件,而且照此說來,它們應(yīng)該位于開發(fā)人員工作區(qū)中的項(xiàng)目內(nèi)。 清單 3. 以服務(wù)器模式啟動(dòng) HSQLDB 的操作,來自 hsqldb.ui.actions.HsqldbStartAction
public void run(IAction action) {
// check a database was really started by the plug-in
PluginUi plugin = PluginUi.getDefault();
if (plugin.getHsqldbServer() != null) {
((ApplicationWindow)window).setStatus(
"HSQLDB Server already running.");
}
else {
Cursor waitCursor = new Cursor(window.getShell().getDisplay(),
SWT.CURSOR_WAIT);
window.getShell().setCursor(waitCursor);
try {
HsqldbUtil.startHsqldb();
((ApplicationWindow)window).setStatus("HSQLDB Server started.");
}
catch (CoreException e) {
MessageDialog.openError(window.getShell(),
"Hsqldb Plugin",
"Could not create HSQLDB database project.");
e.printStackTrace(System.err);
}
finally {
window.getShell().setCursor(null);
waitCursor.dispose();
}
}
}
|
清單 4. 真正以服務(wù)器模式啟動(dòng) HSQLDB 的代碼,來自 hsqldb.ui.HsqldbUtil
public static void startHsqldb() throws CoreException {
PluginUi plugin = PluginUi.getDefault();
// finds project local path for database files
IWorkspaceRoot root = PluginUi.getWorkspace().getRoot();
IProject hsqldbProject = root.getProject(".hsqldb");
if (!hsqldbProject.exists()) {
hsqldbProject.create(null);
}
hsqldbProject.open(null);
IPath dbPath = hsqldbProject.getLocation();
final String database = dbPath.toString() + "/database";
// starts a new thread to run the database server
final HsqldbParams params = getConnectionParams();
Thread server = new Thread() {
public void run() {
String[] args = { "-database", database,
"-port", String.valueOf(params.port),
"-no_system_exit", "true" };
Server.main(args);
}
};
plugin.setHsqldbServer(server);
server.start();
}
|
停止 HSQLDB 要停止 HSQLDB 服務(wù)器,所需要的只是一個(gè)作為 SQL 語句的 SHUTDOWN 命令。如清單 5 所示,來自 hsqldb.ui.HsqldbUtil 的 stopHsqldb 方法 完成了這個(gè)任務(wù)。相應(yīng)操作對(duì)應(yīng)的代碼和啟動(dòng)服務(wù)器時(shí)描述的代碼幾乎完全相同,所以這里沒有列出。清單 5 中拋出了 ClassNotFoundException (來自 Class.forName )和 SQLException 異常,所以操作代碼能夠提供足夠的反饋給用戶。 清單 5. 用于停止 HSQLDB 服務(wù)器的代碼
public static void stopHsqldb() throws ClassNotFoundException,
SQLException {
PluginUi plugin = PluginUi.getDefault();
HsqldbParams params = getConnectionParams();
// submits the SHUTDOWN statement
Class.forName("org.hsqldb.jdbcDriver");
String url = "jdbc:hsqldb:hsql://127.0.0.1:" + params.port;
Connection con = DriverManager.getConnection(url, params.user,
params.passwd);
String sql = "SHUTDOWN";
Statement stmt = con.createStatement();
stmt.executeUpdate(sql);
stmt.close();
// no need to close a dead connection!
plugin.setHsqldbServer(null);
}
|
運(yùn)行 HSQL Database Manager 運(yùn)行 HSQLDB 中的 Database Manager 實(shí)用工具比運(yùn)行服務(wù)器本身還要棘手一點(diǎn)。因?yàn)榉?wù)器被創(chuàng)建為可以嵌入到其他應(yīng)用程序中,而上述實(shí)用工具則計(jì)劃作為單機(jī)應(yīng)用程序或 Java applet 來運(yùn)行。盡管兩種模式均接受命令行參數(shù)(或 applet 參數(shù)),我們還是不希望僅僅為了得到一個(gè)用戶可以在其中輸入 SQL 語句的窗口,就需要額外增加啟動(dòng)一個(gè)新的 Java VM 的開銷。直接調(diào)用類 static void main(String[] args) 不會(huì)如預(yù)期一樣工作,因?yàn)樗鼘⒄{(diào)用 System.exit() 終止 Workbench。
瀏覽 org.hsqldb.util.DatabaseManager 源代碼之后,我們發(fā)現(xiàn)實(shí)例化和初始化一個(gè) JDBC 連接很容易,但是所必需的方法不是公共的。所以我們可以在 HSQLDB 源樹中創(chuàng)建一個(gè)名為 org.hsqldb.util.PatchedDatabaseManager 的類,然后使用所提供的 Ant 構(gòu)建腳本生成一個(gè)新的 hsqldb.jar,其中包含我們打過補(bǔ)丁的 Database Manager。只有兩個(gè)方法的可見性需要改為 public: void main() 和 void connect(Connection con) 。清單 6 顯示了插件如何使用這兩個(gè)方法運(yùn)行補(bǔ)丁類。
Database Manager 是一個(gè) AWT 應(yīng)用程序 (參見圖 8), 它將創(chuàng)建自己的事件線程(獨(dú)立于 Eclipse SWT 線程),并且在調(diào)用 System.exit() 關(guān)閉 Workbench 之后就會(huì)終止??梢赃\(yùn)行該實(shí)用程序的多個(gè)實(shí)例而不會(huì)導(dǎo)致問題,除非沒有 Workbench 窗口可以依靠。對(duì)于我們的 HSQLDB 插件的第一修訂本來說,這很好,但是在這個(gè)系列結(jié)束之前,我們必須將其改為一個(gè) SWT 應(yīng)用程序,就像 Eclipse 視圖那樣嵌入其中。
圖 8. HSQLDB Database Manager
 清單 6. 啟動(dòng)打過補(bǔ)丁的 Database Manager 實(shí)用程序
public static void runDatabaseManager() throws ClassNotFoundException,
SQLException {
PluginUi plugin = PluginUi.getDefault();
HsqldbParams params = getConnectionParams();
// creates a connection to the internal database
String url = "jdbc:hsqldb:hsql://127.0.0.1:" + params.port;
Class.forName("org.hsqldb.jdbcDriver");
Connection con = DriverManager.getConnection(url, params.user,
params.passwd);
if (con != null) {
// needed to patch DatabaseManager so it could
// be initialized and use the supplied connection
PatchedDatabaseManager dm = new PatchedDatabaseManager();
dm.main();
dm.connect(con);
}
}
|
運(yùn)行 SQL 腳本 HSQLDB Script Tool 讀取純文本文件,并依靠一個(gè)給定的 JDBC URL 執(zhí)行文件中包含的 SQL 語句。SQL 語句批處理由 go 命令進(jìn)行分隔, 而且腳本也可以使用 print 命令在腳本中書寫消息。
熟悉其他數(shù)據(jù)庫系統(tǒng)的開發(fā)人員創(chuàng)建的腳本可能僅僅包含由分號(hào)(;)分隔開的 SQL 語句,但是這使得 HSQLDB 可以在單個(gè)批處理中運(yùn)行所有腳本,而只返回最后一條語句的結(jié)果。您需要在 SQL 語句之間包含 go 命令,以接受每一條語句的結(jié)果。
讓用戶瀏覽腳本結(jié)果最容易的方法是把它們放入一個(gè)控制臺(tái)視圖中,就像從 Workbench 調(diào)用的 Java 應(yīng)用程序和 Ant 構(gòu)建腳本一樣。這要求創(chuàng)建一個(gè) Java Launch 配置,而該配置又會(huì)創(chuàng)建另一個(gè) Java VM。因?yàn)?SQL 腳本存在時(shí)間較短,所以我們可以認(rèn)為其開銷是可以接受的,而且如果您認(rèn)為 Database Manager 也應(yīng)該在它自己的 VM 中被調(diào)用,您可以使用清單 7中給出的 runScriptTool 方法作為一個(gè)例子。
清單 7 是本文(這個(gè)系列的第一篇)中最長的清單,其中大部分內(nèi)容是關(guān)于建立一個(gè)包含正確的 JRE 自舉類(必須被顯式設(shè)定)和 hsqldb.core 插件中的 hsqldb.jar 的類路徑。查閱本文末尾的 參考資料 部分可以獲得更多關(guān)于運(yùn)行由 Eclipse 提供的框架以及由 JDT 提供的擴(kuò)展的信息。
對(duì)于熟悉其他 GUI 工具包的開發(fā)人員來說,如何獲取被選中 SQL 腳本文件的路徑并不是一件顯而易見的事情。IObjectActionDelegate 接口由擴(kuò)展資源彈出式菜單的對(duì)象分配實(shí)現(xiàn),調(diào)用它的 run 方法后收到的只是一個(gè)到操作本身的引用,就像一個(gè)頂級(jí)菜單操作一樣,沒有包含與被選中的資源或始發(fā)控件有關(guān)的信息。資源引用實(shí)際上被提供給 selectionChanged 方法,而且必須被保存為一個(gè)實(shí)例變量,以備稍后使用——請(qǐng)參見清單 8。 清單 7. 運(yùn)行 HSQLDB Script Tool
public static void runScriptTool(IFile currentScript) throws CoreException {
PluginUi plugin = PluginUi.getDefault();
// destroys any preexisting configuration and create a new one
ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager();
ILaunchConfigurationType type = manager.getLaunchConfigurationType(
IJavaLaunchConfigurationConstants.ID_JAVA_APPLICATION);
ILaunchConfiguration[] configurations = manager.getLaunchConfigurations(
type);
for (int i = 0; i > configurations.length; i++) {
ILaunchConfiguration config = configurations[i];
if (config.getName().equals("SQL Script")) {
config.delete();
}
}
ILaunchConfigurationWorkingCopy wc = type.newInstance(null,
"SQL Script");
// constructs a classpath from the default JRE...
IPath systemLibs = new Path(JavaRuntime.JRE_CONTAINER);
IRuntimeClasspathEntry systemLibsEntry =
JavaRuntime.newRuntimeContainerClasspathEntry(
systemLibs, IRuntimeClasspathEntry.STANDARD_CLASSES);
systemLibsEntry.setClasspathProperty(
IRuntimeClasspathEntry.BOOTSTRAP_CLASSES);
//... plus hsqldb.core plugin
IPluginRegistry registry = Platform.getPluginRegistry();
IPluginDescriptor hsqldbCore = registry.getPluginDescriptor(
"hsqldb.core");
ILibrary[] libs = hsqldbCore.getRuntimeLibraries();
String installDir = hsqldbCore.getInstallURL().toExternalForm();
URL hsqldbJar = null;
try {
hsqldbJar = Platform.asLocalURL(new URL(installDir +
libs[0].getPath()));
}
catch(Exception e) {
// ignore URL exceptions
}
IRuntimeClasspathEntry hsqldbEntry =
JavaRuntime.newArchiveRuntimeClasspathEntry(new Path(
hsqldbJar.getPath()));
hsqldbEntry.setClasspathProperty(IRuntimeClasspathEntry.USER_CLASSES);
// sets the launch configuration classpath
List classpath = new ArrayList();
classpath.add(systemLibsEntry.getMemento());
classpath.add(hsqldbEntry.getMemento());
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_CLASSPATH,
classpath);
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_DEFAULT_CLASSPATH,
false);
// current directory should be the script container
IPath dir = currentScript.getParent().getLocation();
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY,
dir.toString());
// gets the path for the selected SQL script file
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME,
"org.hsqldb.util.ScriptTool");
// builds ScriptTool command line
HsqldbParams params = getConnectionParams();
String args = "-driver org.hsqldb.jdbcDriver " +
"-url jdbc:hsqldb:hsql: " +
"-database //127.0.0.1:" + params.port + " " +
"-user " + params.user + " " +
"-script " + currentScript.getName();
if (params.passwd.length() > 0)
args += "-password " + params.passwd + " ";
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS,
args);
// saves the new config and launches it
ILaunchConfiguration config = wc.doSave();
DebugUITools.launch(config, ILaunchManager.RUN_MODE);
}
|
清單 8. 如何得到要執(zhí)行的 SQL Script,來自 hsqldb.ui.popup.actions.HsqldbRunScript
public void selectionChanged(IAction action, ISelection selection) {
currentScript = null;
if (selection != null) {
if (selection instanceof IStructuredSelection) {
IStructuredSelection ss = (IStructuredSelection)selection;
// as this action is enabled only for a single selection,
// it‘s enough to get the first element
Object obj = ss.getFirstElement();
if (obj instanceof IFile) {
currentScript = (IFile)obj;
}
}
}
}
|
HSQLDB 屬性 現(xiàn)在我們的插件的大部分功能都已經(jīng)就位,僅僅缺少配置服務(wù)器連接參數(shù)的方法:它監(jiān)聽的 TCP 端口、管理員用戶名稱以及管理員用戶密碼。TCP 端口可能必須要改變,從而避免與安裝在開發(fā)人員機(jī)器上的其他應(yīng)用程序發(fā)生沖突;用戶及密碼可以從任意客戶機(jī)上使用 SQL 語句來改變。這些參數(shù)可以被放入一個(gè)類似 C 結(jié)構(gòu)(或類似 pascal 記錄)的名為 HsqldbParams 的類中, 如清單 9 所示。它也聲明了由 Plugin Preferences Store 和 Property Page 使用的常量來取得并保存參數(shù)(引用)值。 清單 9. 用于 HSQLDB 服務(wù)器連接參數(shù)的參數(shù)選擇結(jié)構(gòu)
package hsqldb.ui;
public class HsqldbParams {
// preference names for the plugin
public static final String P_PORT = "serverPort";
public static final String P_USER = "serverUser";
public static final String P_PASSWD = "serverPasswd";
public int port = 9001;
public String user = "sa";
public String passwd = "";
}
|
不幸的是,由 PDE New Extension Wizard 生成的參數(shù)選擇頁面類起了一點(diǎn)誤導(dǎo)作用,它包括一個(gè)方法,用于設(shè)置不會(huì)被插件參數(shù)選擇存儲(chǔ)使用的默認(rèn)參數(shù)選擇值。初始化默認(rèn)值的正確位置應(yīng)該是主要的 Plugin 類本身。否則,所有使用默認(rèn)值的參數(shù)選擇將被參數(shù)選擇存儲(chǔ)返回為 0 或 null。
因此,參數(shù)選擇頁面類變得簡單多了,如清單 10 所示,并且方法 initializeDefaultPreferences 被添加到 PluginUi 類中,如清單 11 所示。注意,每個(gè)參數(shù)選擇的默認(rèn)值均由參數(shù)選擇結(jié)構(gòu)定義,而不是由插件類或參數(shù)選擇頁面類來定義。 清單 10. HSQLDB 插件的參數(shù)選擇頁面
public class HSQLDBPreferencePage
extends FieldEditorPreferencePage
implements IWorkbenchPreferencePage {
public HSQLDBPreferencePage() {
super(GRID);
setPreferenceStore(PluginUi.getDefault().getPreferenceStore());
setDescription("Connection parameters for the embedded HSQLDB server");
}
public void createFieldEditors() {
addField(new IntegerFieldEditor(HsqldbParams.P_PORT,
"&TCP Port:",
getFieldEditorParent()));
addField(new StringFieldEditor(HsqldbParams.P_USER,
"Administrator &User:",
getFieldEditorParent()));
addField(new StringFieldEditor(HsqldbParams.P_PASSWD,
"Administrator &Password:",
getFieldEditorParent()));
}
public void init(IWorkbench Workbench) {
}
}
|
清單 11. 來自生成的 Plugin 類的已更改方法
public void shutdown() throws CoreException {
// shuts down the server if running
Thread server = getHsqldbServer();
if (server != null && server.isAlive()) {
try {
HsqldbUtil.stopHsqldb();
}
catch (Exception e) {
e.printStackTrace();
}
}
super.shutdown();
}
protected void initializeDefaultPreferences(IPreferenceStore store) {
super.initializeDefaultPreferences(store);
HsqldbParams params = new HsqldbParams();
store.setDefault(HsqldbParams.P_PORT, params.port);
store.setDefault(HsqldbParams.P_USER, params.user);
store.setDefault(HsqldbParams.P_PASSWD, params.passwd);
}
|
提供反饋給用戶 由于插件組既沒有提供視圖也沒有提供編輯器給 Workbench,所以只有幾種有限的方法可以為用戶提供操作方面的反饋。我們可以使用 Workbench 窗口的狀態(tài)欄以及一個(gè) SWT MessageDialog ,這樣用戶就不會(huì)錯(cuò)過重要的事件(通常是錯(cuò)誤)。
Workbench 操作的可見性及實(shí)現(xiàn)模型主要集中在工作區(qū)資源選擇方面,但是大部分插件操作(啟動(dòng)及停止 HSQLDB 服務(wù)器,還包括啟動(dòng)Database Manager)并不依賴于資源選擇,而是依賴于服務(wù)器是否正在運(yùn)行——這個(gè)條件必須由每個(gè)操作的 run 方法進(jìn)行顯式檢查。
警告: 插件類與操作類直到絕對(duì)需要降低 Workbench 對(duì)內(nèi)存的需求時(shí)才會(huì)被初始化。清單文件內(nèi)容被用于表示菜單選擇和啟用/禁用它們, 但是由于 HSQLDB 服務(wù)器不屬于工作區(qū)資源,所以它不能啟用操作。正如清單 2 中的清單代碼所描述的那樣,您可以(請(qǐng)參見 <enablement> 元素)在激活插件的基礎(chǔ)之上啟用/禁用一項(xiàng)操作,所以在啟動(dòng)時(shí)只有“Start HSQLDB server” 操作可以被啟用,但在這之后如果服務(wù)器已經(jīng)處于運(yùn)行狀態(tài),則不存在容易的方法來禁用這項(xiàng)操作以及當(dāng)其停止后重新啟用它。
注意,用于在 Workbench 窗口上放置一個(gè)沙漏狀光標(biāo)的代碼,以及用于設(shè)置狀態(tài)欄消息的代碼不易在 PDE 文檔或 Eclipse.org 文章中找到。
最后一步 插件類重寫了 shutdown 方法,所以當(dāng) Workbench 被關(guān)閉時(shí)它能夠干凈利落地關(guān)閉服務(wù)器,這時(shí)我們就結(jié)束了整個(gè)過程。當(dāng)然,要得到一個(gè)完整的插件組還需要完成這里沒有講到的額外任務(wù),比如:
- 把兩個(gè)插件包裝成一個(gè)功能,這樣它們就可以作為一個(gè)單元進(jìn)行安裝、移除、啟用和禁用。
- 為插件本身提供幫助文檔,并使用一種適合于 Workbench 幫助管理器的格式將 HSQLDB 文檔包含在其內(nèi)。
結(jié)束語 本文介紹如何創(chuàng)建可將 HSQLDB 數(shù)據(jù)庫引入到 Eclipse Workbench 中的一組插件,以增添啟動(dòng)和停止服務(wù)器以及運(yùn)行 SQL 語句和腳本的能力。我們已經(jīng)了解到 PDE 是如何使這類插件的創(chuàng)建工作變得容易的,即對(duì)大多數(shù)任務(wù)使用向?qū)Ш途庉嬈鳎艚o開發(fā)人員的任務(wù)不過是創(chuàng)建真正實(shí)現(xiàn)所需功能的代碼。
在這個(gè)系列的下一個(gè)部分中,您將了解到如何利用 Workbench 的功能向 HSQLDB 開發(fā)添加值,而不僅僅是簡單地運(yùn)行預(yù)先存在的工具。
參考資料
|