小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

[轉(zhuǎn)]Eclipse 和 HSQLDB: 將關(guān)系數(shù)據(jù)庫(kù)服務(wù)器嵌入到 Eclipse 中(1) - myali88的專欄

 kukoo 2005-09-03
本文介紹如何開發(fā)將 HSQLDB 純 Java 關(guān)系數(shù)據(jù)庫(kù)服務(wù)器集成到 Eclipse Workbench 中的插件。盡管不如 DB2 功能強(qiáng)大,也不如 MySQL 流行,但 HSQLDB(超音速 SQL 數(shù)據(jù)庫(kù))可以滿足很大范圍內(nèi) Java 應(yīng)用程序的需要,因?yàn)樗哂锌蓴U(kuò)展性,而且對(duì)內(nèi)存/處理器的要求不高。

超音速 SQL 數(shù)據(jù)庫(kù)后來(lái)正式更名為 HSQLDB,它是一類純 Java 撰寫的嵌入式關(guān)系數(shù)據(jù)庫(kù)服務(wù)器,您可以在單機(jī)模式(使用直接文件訪問(wèn))或客戶機(jī)/服務(wù)器模式中使用它,它支持大量的并發(fā)用戶。盡管不如 DB2 功能強(qiáng)大,也不如 MySQL 流行,但 HSQLDB(超音速SQL數(shù)據(jù)庫(kù))可以滿足很大范圍內(nèi) Java 應(yīng)用程序的需要,因?yàn)樗哂锌蓴U(kuò)展性,而且對(duì)內(nèi)存/處理器的要求不高。

HSQLDB 是一類使用方便的 Java 開發(fā)數(shù)據(jù)庫(kù),因?yàn)樗С?Structured Query Language(SQL)的豐富子集,并且 Java 程序員根本不需要在他們的開發(fā)工作站上安裝嚴(yán)重消耗處理器、內(nèi)存和磁盤空間的數(shù)據(jù)庫(kù)服務(wù)器。它對(duì)于集成到 Eclipse IDE 中來(lái)說(shuō)是一種很理想的工具,既能為新手也能為經(jīng)驗(yàn)豐富的開發(fā)人員提供有用的工具。

本文及同一系列的后續(xù)文章將向您展示如何構(gòu)建一組 Eclipse 插件,以將 HSQLDB 嵌入到 Eclipse Workbench 中。您將看到一個(gè)現(xiàn)實(shí)世界中的例子,目的是說(shuō)明如何在考慮到 API 和用戶接口(UI)的情況下開發(fā)這類插件,以及如何評(píng)估可供選擇的方法以給用戶帶來(lái)所需要的功能。本文假定您使用的是 Eclipse SDK 分布,而不是 Platform Runtime-Binary 加上 JDT。但如果只是為了開發(fā)常規(guī) Java 應(yīng)用程序,則后者更加適合。

在這個(gè)系列中,我們將根據(jù)“Levels of Integration” 一文中描述的基本原理(請(qǐng)參閱本文后面的 參考資料 中給出的鏈接),使用三個(gè)步驟創(chuàng)建并擴(kuò)展插件組:

  1. 運(yùn)行 Eclipse 中現(xiàn)有的工具, 以便從 Workbench 菜單容易地訪問(wèn)所需要的預(yù)先存在的工具。
  2. 探討如何使用 Eclipse 的其他功能來(lái)向預(yù)先存在的工具集添加值,從而提高 Java 開發(fā)人員的生產(chǎn)力。
  3. 使用 SWT 來(lái)重寫工具,以實(shí)現(xiàn)與 Eclipse Workbench 的無(wú)縫集成。

了解 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ù)庫(kù)引擎、服務(wù)器進(jìn)程、JDBC 驅(qū)動(dòng)程序、文檔以及一些實(shí)用工具——都放在一個(gè)單獨(dú)的 JAR 包中,這個(gè)包安裝在 lib/hsqldb.jar 中,大小在 260 KB 左右。運(yùn)行數(shù)據(jù)庫(kù)引擎只需 170 KB 的 RAM,這即使是 PDA (如 Sharp 生產(chǎn)的 Zaurus)也能夠滿足,而且包括源文件和文檔在內(nèi)的整個(gè)下載文件小到可以放到一張標(biāo)準(zhǔn)的 1.44 MB 軟盤上。

您可以從命令行啟動(dòng)數(shù)據(jù)庫(kù)服務(wù)器和實(shí)用工具,具體方法是調(diào)用像 org.hsqldb.Server 和 org.hsqldb.util.DatabaseManager 這樣的方便的類,這兩個(gè)類均可以接受為數(shù)不多的一組命令行選項(xiàng),如“-url”(用于遠(yuǎn)程連接)、“-database”(用于直接文件訪問(wèn))和“-user”。還有一種“-?”選項(xiàng)也可以被接受,其作用是提供關(guān)于有效命令行語(yǔ)法的幫助。

造成 HSQLDB 簡(jiǎn)單性的關(guān)鍵因素是SQL語(yǔ)句執(zhí)行的順序化。也就是說(shuō),盡管許多并發(fā)用戶可以連接到數(shù)據(jù)庫(kù)上(當(dāng)數(shù)據(jù)庫(kù)以服務(wù)器模式運(yùn)行時(shí)),但是所有 SQL 語(yǔ)句都被放到一個(gè)隊(duì)列中,然后一次執(zhí)行一條。因此不需要實(shí)現(xiàn)復(fù)雜的鎖定及同步算法。盡管如此,HSQLB 還是實(shí)現(xiàn)了 ACID(Atomicity, Consistency, Isolation, and Durability,即原子性、一致性、隔離性和持久性) 語(yǔ)義。換句話說(shuō),它是一個(gè)事務(wù)性的數(shù)據(jù)庫(kù),但僅僅處于讀未提交級(jí)別,還不具備事務(wù)隔離功能。HSQLDB 實(shí)際上是為嵌入式應(yīng)用程序而不是為共同數(shù)據(jù)中心而創(chuàng)建的。

如果您想要使用觸發(fā)器、聚合函數(shù)、外部聯(lián)接、視圖以及其他 SQL 功能,HSQLB 都可以滿足您的需要(大部分輕量級(jí)關(guān)系數(shù)據(jù)庫(kù)無(wú)法做到這一點(diǎn))。通過(guò)把您的 Java 類添加到 HSQLB 的類路徑中,您可以實(shí)現(xiàn)存儲(chǔ)過(guò)程。然后您發(fā)出一條 CREATE FUNCTION 語(yǔ)句即可大功告成。事實(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ù)庫(kù)表和索引都放在內(nèi)存中,而且永遠(yuǎn)不會(huì)保存到磁盤上。在您發(fā)出為什么有人想要使用在應(yīng)用程序終止時(shí)就會(huì)丟失的數(shù)據(jù)庫(kù)這樣的疑問(wèn)之前,請(qǐng)先考慮為您可以使用標(biāo)準(zhǔn) SQL 語(yǔ)句進(jìn)行查詢、排序、分組和更新的數(shù)據(jù)庫(kù)數(shù)據(jù)擁有一塊本地高速緩存。

單機(jī)模式
應(yīng)用程序使用 JDBC 創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)連接,并且 HSQLDB 引擎運(yùn)行在該應(yīng)用程序中,這時(shí)允許直接訪問(wèn)數(shù)據(jù)庫(kù)文件。不能存在并發(fā)用戶(應(yīng)用程序獨(dú)占地訪問(wèn)數(shù)據(jù)庫(kù)文件),但因此也沒(méi)有額外的線程和 TCP 連接開銷。單機(jī)模式是許多嵌入式應(yīng)用程序的首選模式。

服務(wù)器模式
這是類似于其他關(guān)系數(shù)據(jù)庫(kù)的標(biāo)準(zhǔn)客戶機(jī)/服務(wù)器數(shù)據(jù)庫(kù)配置,允許出現(xiàn)使用 TCP 套接字的并發(fā)連接。大部分開發(fā)人員喜歡這種模式,因?yàn)樗试S任何 JDBC 客戶機(jī)在主應(yīng)用程序仍在運(yùn)行的情況下連接并查詢/更新表。

Web服務(wù)器模式
HSQLDB 可以用作 Web 服務(wù)器,可以通過(guò) HTTP 接受 SQL 查詢;也能作為任何標(biāo)準(zhǔn) Web 容器中的 servlet 來(lái)運(yùn)行,可以穿過(guò)防火墻或者安裝在 Web 宿主服務(wù)上,而不用涉及到提供者支持小組(和昂貴的數(shù)據(jù)庫(kù)宿主選項(xiàng))。由于 HTTP 是無(wú)狀態(tài)的,所以本模式中不存在事務(wù)。

HSQLDB 數(shù)據(jù)庫(kù)文件結(jié)構(gòu)
HSQLDB 將所有表和索引數(shù)據(jù)放在內(nèi)存中, 將所有發(fā)出的 SQL 語(yǔ)句保存到一個(gè)名為 database.script 的文件中,該文件同時(shí)也充當(dāng)著事務(wù)日志的角色。初始化引擎之后,該文件被讀取,然后其中所有的 SQL 語(yǔ)句都被運(yùn)行,從而完成整個(gè)數(shù)據(jù)庫(kù)的重建。停機(jī)期間,HSQLDB 引擎將生成一個(gè)新的 database.script 文件,其中只包含最少的語(yǔ)句,目的是讓數(shù)據(jù)庫(kù)可以快速啟動(dòng)。

除了默認(rèn)放在內(nèi)存中的表之外,HSQLDB 還支持“緩存”表和“文本”表。所有緩存表的數(shù)據(jù)放在一個(gè)名為 database.data 的文件中,而文本表的數(shù)據(jù)則放在由 set table source 非標(biāo)準(zhǔn) SQL 語(yǔ)句命名的任意分隔文本文件(像 CSV 文件)中。緩存表支持比可用 RAM 大的數(shù)據(jù)集,而文本表則可以作為一種導(dǎo)入導(dǎo)出數(shù)據(jù)的方便手段。

除了 database.script 和 database.data 文件之外,任何 HSQLDB 數(shù)據(jù)庫(kù)還可能包含一個(gè) database.properties 文件,管理員可以在該文件中設(shè)置許多影響到 ANSI SQL 兼容性的參數(shù)。所有數(shù)據(jù)庫(kù)文件(文本表數(shù)據(jù)文件除外)必須放在同一個(gè)目錄中。

不存在創(chuàng)建 HSQLDB 數(shù)據(jù)庫(kù)的顯式方法。如果您要求引擎打開一個(gè)目前不存在的數(shù)據(jù)庫(kù)文件(使用服務(wù)器模式的 -database 選項(xiàng)或單機(jī)模式的 JDBC URL),就會(huì)創(chuàng)建該文件及其所在目錄。所以,如果您肯定那個(gè)空數(shù)據(jù)庫(kù)中存在數(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。即使您以前從未接觸過(guò) 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 提供增值。

在本文中,我們將集中講述我們代碼的下列功能:

  1. 以服務(wù)器模式啟動(dòng) HSQLDB 引擎,這樣用戶應(yīng)用程序和 SQL 控制臺(tái)(像 HSQLDB 自帶的 DatabaseManager 實(shí)用工具)都可以運(yùn)行 SQL 語(yǔ)句。
  2. 完全停止 HSQLDB 服務(wù)器。
  3. 調(diào)用 DatabaseManager 實(shí)用工具,這樣開發(fā)人員可以從 Workbench 交互式地輸入 SQL 語(yǔ)句。
  4. 使用 HSQLDB ScriptTool 實(shí)用工具運(yùn)行 SQL 腳本文件。這些年來(lái),為了創(chuàng)建數(shù)據(jù)庫(kù)表和插入測(cè)試數(shù)據(jù),我已經(jīng)在我的項(xiàng)目文件夾中放入了大量 *.sql 文件,而且長(zhǎng)久以來(lái),我一直希望能夠有一種容易的方式來(lái)運(yùn)行它們。
  5. 配置 HSQLDB 連接屬性,比如 TCP 端口和管理員密碼。

如何才能使得這些函數(shù)可以為 Workbench 所用呢?完成前面三步最容易的方式是提供一個(gè) actionSet,它被添加到新的頂級(jí)菜單中并且可從它自己的工具欄訪問(wèn)。運(yùn)行 SQL 腳本文件的操作必須只被綁定到擴(kuò)展名為“*.sql” 的文件上,所以這將是一個(gè) objectContribution, 它被 Workbench 添加到顯示這些文件的任意視圖上的彈出式菜單中。最后,連接參數(shù)要能很好地符合插件的參數(shù)選擇頁(yè)面,從 Workbench Window 菜單中可以訪問(wèn)這個(gè)頁(yè)面。

圖 1 顯示了新的菜單和工具欄,而圖 2 顯示了 Navigator 視圖的彈出式菜單中的新項(xiàng),圖 3 則顯示了屬性頁(yè)面,這樣您就可以看到我們的插件組的第一個(gè)版本是什么樣子。

圖 1.HSQLDB 菜單和相關(guān)工具欄
HSQLDB 菜單和相關(guān)工具欄

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

圖 3. HSQLDB 連接屬性
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è)好處就是與許多插件共享這些常見的庫(kù)很容易。

打開您的 Eclipse SDK 安裝,并創(chuàng)建一個(gè)新的插件項(xiàng)目;將其命名為 hsqldb.core(我知道推薦使用的名稱是 org.hsqldb.core,但是我不愿意假裝使用了 HSQLDB 名稱空間?;蛟S HSQLDB 開發(fā)人員閱讀至此會(huì)贊同這個(gè)想法,并推薦它為“正式的”Eclipse 集成插件;這樣的話該名稱極有可能被改掉)。確保選中的是“Empty Plugin” 選項(xiàng), 而不是任何插件模板;否則,您將得到一個(gè)毫無(wú)用處的頂級(jí)插件類,如果您創(chuàng)建了該類之后希望刪掉它,那么它可以被安全地刪除。把 hsqldb.jar 從您的 HSQLDB 安裝拷貝到項(xiàng)目目錄中,并將其添加到項(xiàng)目的 Runtime。您可以使用 PDE Plugin Manifest Editor或者簡(jiǎn)單地拷貝清單 1 中的內(nèi)容來(lá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ò)展
接下來(lái),創(chuàng)建名為 hsqldb.ui 的第二個(gè)插件項(xiàng)目。這個(gè)項(xiàng)目將為插件組的第一修訂本包含所有的 Eclipse 擴(kuò)展:包含三個(gè)操作的一個(gè)操作集、與 *.sql 文件相關(guān)的對(duì)象作用,以及一個(gè)屬性頁(yè)面。將其主類(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 模板、彈出式菜單和屬性頁(yè)面添加兩個(gè)新的擴(kuò)展。彈出式菜單應(yīng)該與 *.sql 文件模式相關(guān)聯(lián),但是屬性頁(yè)面字段應(yīng)該通過(guò)編程進(jìn)行設(shè)置。

圖 4 顯示了 Plugin Manifest 編輯器的最終外觀,并顯示了所有的插件擴(kuò)展;圖 5 顯示了對(duì)象分配的屬性(注意針對(duì) *.sql 文件的過(guò)濾器),而圖 6 顯示了文件資源彈出式菜單中的“Run SQL Script”操作的屬性。 圖標(biāo)在本文的源代碼中給出(請(qǐng)參閱 參考資料),但是我敢肯定您認(rèn)識(shí)有比這畫得更好的圖形專家!

圖 4. 清單編輯器上的 HSQLDB 操作
清單編輯器上的 HSQLDB 操作

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

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

在能夠給我們的操作添加代碼之前,我們需要將這種 UI 插件與相應(yīng)的核心插件區(qū)別開來(lái),否則它將不能訪問(wèn) HSQLDB 類。轉(zhuǎn)到 Plugin Manifest Editor 上的“Dependencies” 頁(yè)面,并添加一個(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ò)展代碼保持簡(jiǎn)單。

圖 7. hsqldb.ui 插件依賴性
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ù)庫(kù)名稱(路徑+數(shù)據(jù)庫(kù)文件的基本名稱)、用于監(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ù)庫(kù)文件。

我們可以使用 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ǔ)句終止它的運(yùn)行。

清單 3 中的代碼顯示了 hsqldb.ui.actions.HsqldbStartAction 的 run 方法,而清單 4 顯示了 hsqldb.ui.HsqldbUtil startHsqldb 的代碼,這些代碼實(shí)際上啟動(dòng)了服務(wù)器。稍后我們將討論管理用戶反饋的技術(shù)。

上述代碼最有趣的部分是我們?nèi)绾握业揭粋€(gè)位置來(lái)放置數(shù)據(jù)庫(kù)文件。一個(gè)名為 .hsqldb 的項(xiàng)目如果不存在就會(huì)被創(chuàng)建,而在該項(xiàng)目中引擎將查找 database.script 和其他數(shù)據(jù)庫(kù)文件。

Eclipse 在一個(gè)可以保存任何配置文件的標(biāo)準(zhǔn)目錄中提供了所有插件??梢酝ㄟ^(guò)調(diào)用 plugin.getStateLocation 來(lái)得到這樣一個(gè)目錄,但是我并不覺(jué)得數(shù)據(jù)庫(kù)文件實(shí)際上是插件配置文件。它們看起來(lái)更像是用戶數(shù)據(jù)文件,而且照此說(shuō)來(lái),它們應(yīng)該位于開發(fā)人員工作區(qū)中的項(xiàng)目?jī)?nèi)。

清單 3. 以服務(wù)器模式啟動(dòng) HSQLDB 的操作,來(lái)自 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 的代碼,來(lái)自 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 語(yǔ)句的 SHUTDOWN 命令。如清單 5 所示,來(lái)自 hsqldb.ui.HsqldbUtil 的 stopHsqldb 方法完成了這個(gè)任務(wù)。相應(yīng)操作對(duì)應(yīng)的代碼和啟動(dòng)服務(wù)器時(shí)描述的代碼幾乎完全相同,所以這里沒(méi)有列出。清單 5 中拋出了 ClassNotFoundException (來(lái)自 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 來(lái)運(yùn)行。盡管兩種模式均接受命令行參數(shù)(或 applet 參數(shù)),我們還是不希望僅僅為了得到一個(gè)用戶可以在其中輸入 SQL 語(yǔ)句的窗口,就需要額外增加啟動(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,其中包含我們打過(guò)補(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)致問(wèn)題,除非沒(méi)有 Workbench 窗口可以依靠。對(duì)于我們的 HSQLDB 插件的第一修訂本來(lái)說(shuō),這很好,但是在這個(gè)系列結(jié)束之前,我們必須將其改為一個(gè) SWT 應(yīng)用程序,就像 Eclipse 視圖那樣嵌入其中。

圖 8. HSQLDB Database Manager
HSQLDB Database Manager

清單 6. 啟動(dòng)打過(guò)補(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 語(yǔ)句。SQL 語(yǔ)句批處理由 go 命令進(jìn)行分隔, 而且腳本也可以使用 print 命令在腳本中書寫消息。

熟悉其他數(shù)據(jù)庫(kù)系統(tǒng)的開發(fā)人員創(chuàng)建的腳本可能僅僅包含由分號(hào)(;)分隔開的 SQL 語(yǔ)句,但是這使得 HSQLDB 可以在單個(gè)批處理中運(yùn)行所有腳本,而只返回最后一條語(yǔ)句的結(jié)果。您需要在 SQL 語(yǔ)句之間包含 go 命令,以接受每一條語(yǔ)句的結(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è)系列的第一篇)中最長(zhǎng)的清單,其中大部分內(nèi)容是關(guān)于建立一個(gè)包含正確的 JRE 自舉類(必須被顯式設(shè)定)和 hsqldb.core 插件中的 hsqldb.jar 的類路徑。查閱本文末尾的 參考資料 部分可以獲得更多關(guān)于運(yùn)行由 Eclipse 提供的框架以及由 JDT 提供的擴(kuò)展的信息。

對(duì)于熟悉其他 GUI 工具包的開發(fā)人員來(lái)說(shuō),如何獲取被選中 SQL 腳本文件的路徑并不是一件顯而易見的事情。IObjectActionDelegate 接口由擴(kuò)展資源彈出式菜單的對(duì)象分配實(shí)現(xiàn),調(diào)用它的 run 方法后收到的只是一個(gè)到操作本身的引用,就像一個(gè)頂級(jí)菜單操作一樣,沒(méi)有包含與被選中的資源或始發(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,來(lái)自 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 語(yǔ)句來(lái)改變。這些參數(shù)可以被放入一個(gè)類似 C 結(jié)構(gòu)(或類似 pascal 記錄)的名為 HsqldbParams 的類中, 如清單 9 所示。它也聲明了由 Plugin Preferences Store 和 Property Page 使用的常量來(lái)取得并保存參數(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ù)選擇頁(yè)面類起了一點(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ù)選擇頁(yè)面類變得簡(jiǎn)單多了,如清單 10 所示,并且方法 initializeDefaultPreferences 被添加到 PluginUi 類中,如清單 11 所示。注意,每個(gè)參數(shù)選擇的默認(rèn)值均由參數(shù)選擇結(jié)構(gòu)定義,而不是由插件類或參數(shù)選擇頁(yè)面類來(lái)定義。

清單 10. HSQLDB 插件的參數(shù)選擇頁(yè)面

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. 來(lái)自生成的 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);
    }
               

提供反饋給用戶
由于插件組既沒(méi)有提供視圖也沒(méi)有提供編輯器給 Workbench,所以只有幾種有限的方法可以為用戶提供操作方面的反饋。我們可以使用 Workbench 窗口的狀態(tài)欄以及一個(gè) SWT MessageDialog ,這樣用戶就不會(huì)錯(cuò)過(guò)重要的事件(通常是錯(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),則不存在容易的方法來(lá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è)過(guò)程。當(dāng)然,要得到一個(gè)完整的插件組還需要完成這里沒(méi)有講到的額外任務(wù),比如:

  • 把兩個(gè)插件包裝成一個(gè)功能,這樣它們就可以作為一個(gè)單元進(jìn)行安裝、移除、啟用和禁用。
  • 為插件本身提供幫助文檔,并使用一種適合于 Workbench 幫助管理器的格式將 HSQLDB 文檔包含在其內(nèi)。

結(jié)束語(yǔ)
本文介紹如何創(chuàng)建可將 HSQLDB 數(shù)據(jù)庫(kù)引入到 Eclipse Workbench 中的一組插件,以增添啟動(dòng)和停止服務(wù)器以及運(yùn)行 SQL 語(yǔ)句和腳本的能力。我們已經(jīng)了解到 PDE 是如何使這類插件的創(chuàng)建工作變得容易的,即對(duì)大多數(shù)任務(wù)使用向?qū)Ш途庉嬈?,而留給開發(fā)人員的任務(wù)不過(guò)是創(chuàng)建真正實(shí)現(xiàn)所需功能的代碼。

在這個(gè)系列的下一個(gè)部分中,您將了解到如何利用 Workbench 的功能向 HSQLDB 開發(fā)添加值,而不僅僅是簡(jiǎn)單地運(yùn)行預(yù)先存在的工具。

參考資料


    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多