站内搜索: 请输入搜索关键词

当前页面: 开发资料首页Eclipse 专题Eclipse 和 HSQLDB: 将关系数据库服务器嵌入到 Eclipse 中, 第 1 部分

Eclipse 和 HSQLDB: 将关系数据库服务器嵌入到 Eclipse 中, 第 1 部分

摘要: 本文介绍如何开发将 HSQLDB 纯 Java 关系数据库服务器集成到 EclipseWorkbench 中的插件。尽管不如 DB2 功能强大,也不如 MySQL 流行,但 HSQLDB(超音速 SQL 数据库)可以满足很大范围内Java 应用程序的需要,因为它具有可扩展性,而且对内存/处理器的要求不高。

超音速 SQL 数据库后来正式更名为 HSQLDB,它是一类纯 Java 撰写的嵌入式关系数据库服务器,您可以在单机模式(使用直接文件访问)或客户机/服务器模式中使用它,它支持大量的并发用户。尽管不如 DB2 功能强大,也不如 MySQL 流行,但 HSQLDB(超音速SQL数据库)可以满足很大范围内 Java 应用程序的需要,因为它具有可扩展性,而且对内存/处理器的要求不高。

HSQLDB 是一类使用方便的 Java 开发数据库,因为它支持 Structured Query Language(SQL)的丰富子集,并且 Java 程序员根本不需要在他们的开发工作站上安装严重消耗处理器、内存和磁盘空间的数据库服务器。它对于集成到 Eclipse IDE 中来说是一种很理想的工具,既能为新手也能为经验丰富的开发人员提供有用的工具。

本文及同一系列的后续文章将向您展示如何构建一组 Eclipse 插件,以将 HSQLDB 嵌入到 Eclipse Workbench 中。您将看到一个现实世界中的例子,目的是说明如何在考虑到 API 和用户接口(UI)的情况下开发这类插件,以及如何评估可供选择的方法以给用户带来所需要的功能。本文假定您使用的是 Eclipse SDK 分布,而不是 Platform Runtime-Binary 加上 JDT。但如果只是为了开发常规 Java 应用程序,则后者更加适合。

在这个系列中,我们将根据“Levels of Integration” 一文中描述的基本原理(请参阅本文后面的 参考资料 中给出的链接),使用三个步骤创建并扩展插件组:

  1. 运行 Eclipse 中现有的工具, 以便从 Workbench 菜单容易地访问所需要的预先存在的工具。
  2. 探讨如何使用 Eclipse 的其他功能来向预先存在的工具集添加值,从而提高 Java 开发人员的生产力。
  3. 使用 SWT 来重写工具,以实现与 Eclipse Workbench 的无缝集成。

了解 HSQLDB

您可以从 SourceForge( hsqldb.sourceforge.net; 请参阅 参考资料 中给出的链接)下载 HSQLDB,其中包括源代码和文档。这里注意不要与已经被冻结的原始 SourceForge 项目( hsql.sourceforge.net )相混淆。

二进制分布是一个标准的 ZIP 文件,而您要做的就是把这个 ZIP 文件解压缩到您硬盘上的某个地方。所有 HSQLDB 组件 ——数据库引擎、服务器进程、JDBC 驱动程序、文档以及一些实用工具——都放在一个单独的 JAR 包中,这个包安装在 lib/hsqldb.jar 中,大小在 260 KB 左右。运行数据库引擎只需 170 KB 的 RAM,这即使是 PDA(如 Sharp 生产的 Zaurus)也能够满足,而且包括源文件和文档在内的整个下载文件小到可以放到一张标准的 1.44 MB 软盘上。

您可以从命令行启动数据库服务器和实用工具,具体方法是调用 像 org.hsqldb.Server 和 org.hsqldb.util.DatabaseManager 这样的方便的类,这两个类均可以接受为数不多的一组命令行选项,如“-url”(用于远程连接)、“-database”(用于直接文件访问)和“-user”。还有一种“-?”选项也可以被接受,其作用是提供关于有效命令行语法的帮助。

造成 HSQLDB 简单性的关键因素是SQL语句执行的顺序化。也就是说,尽管许多并发用户可以连接到数据库上(当数据库以服务器模式运行时),但是所有 SQL 语句都被放到一个队列中,然后一次执行一条。因此不需要实现复杂的锁定及同步算法。尽管如此,HSQLB 还是实现了 ACID(Atomicity, Consistency, Isolation, and Durability,即原子性、一致性、隔离性和持久性) 语义。换句话说,它是一个事务性的数据库,但仅仅处于 读未提交级别,还不具备事务隔离功能。HSQLDB 实际上是为嵌入式应用程序而不是为共同数据中心而创建的。

如果您想要使用触发器、聚合函数、外部联接、视图以及其他 SQL 功能,HSQLB 都可以满足您的需要(大部分轻量级关系数据库无法做到这一点)。通过把您的 Java 类添加到 HSQLB 的类路径中,您可以实现存储过程。然后您发出一条 CREATE FUNCTION 语句即可大功告成。事实上 ,像 SQRT 和 ABS 之类的许多标准 SQL 函数都被实现为到标准 Java 类(比如 java.lang.Math )的直接映射

HSQLDB 的运行模式

HSQLDB 引擎可以以多种模式运行,以适应不同的应用场合:

驻留内存模式
所有数据库表和索引都放在内存中,而且永远不会保存到磁盘上。在您发出为什么有人想要使用在应用程序终止时就会丢失的数据库这样的疑问之前,请先考虑为您可以使用标准 SQL 语句进行查询、排序、分组和更新的数据库数据拥有一块本地高速缓存。

单机模式
应用程序使用 JDBC 创建一个数据库连接,并且 HSQLDB 引擎运行在该应用程序中,这时允许直接访问数据库文件。不能存在并发用户(应用程序独占地访问数据库文件),但因此也没有额外的线程和 TCP 连接开销。单机模式是许多嵌入式应用程序的首选模式。

服务器模式
这是类似于其他关系数据库的标准客户机/服务器数据库配置,允许出现使用 TCP 套接字的并发连接。大部分开发人员喜欢这种模式,因为它允许任何 JDBC 客户机在主应用程序仍在运行的情况下连接并查询/更新表。

Web服务器模式
HSQLDB 可以用作 Web 服务器,可以通过 HTTP 接受 SQL 查询;也能作为任何标准 Web 容器中的 servlet 来运行,可以穿过防火墙或者安装在 Web 宿主服务上,而不用涉及到提供者支持小组(和昂贵的数据库宿主选项)。由于 HTTP 是无状态的,所以本模式中不存在事务。

HSQLDB 数据库文件结构

HSQLDB 将所有表和索引数据放在内存中, 将所有发出的 SQL 语句保存到一个名为 database.script 的文件中,该文件同时也充当着事务日志的角色。初始化引擎之后,该文件被读取,然后其中所有的 SQL 语句都被运行,从而完成整个数据库的重建。停机期间,HSQLDB 引擎将生成一个新的 database.script 文件,其中只包含最少的语句,目的是让数据库可以快速启动。

除了默认放在内存中的表之外,HSQLDB 还支持“缓存”表和“文本”表。所有缓存表的数据放在一个名为 database.data 的文件中,而文本表的数据则放在由 set table source 非标准 SQL 语句命名的任意分隔文本文件(像 CSV 文件)中。缓存表支持比可用 RAM 大的数据集,而文本表则可以作为一种导入导出数据的方便手段。

除了 database.script 和 database.data 文件之外,任何 HSQLDB 数据库还可能包含一个 database.properties 文件,管理员可以在该文件中设置许多影响到 ANSI SQL 兼容性的参数。所有数据库文件(文本表数据文件除外)必须放在同一个目录中。

不存在创建 HSQLDB 数据库的显式方法。如果您要求引擎打开一个目前不存在的数据库文件(使用服务器模式 的 -database 选项或单机模式的 JDBC URL),就会创建该文件及其所在目录。所以,如果您肯定那个空数据库中存在数据,请检查是否有录入错误。

现在让我们开始开发插件!


<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>

创建 HSQLDB Eclipse 插件组

把现有的应用程序放到 Eclipse 这样功能强大的工具中去并不是一件容易的事情。值得庆幸的是,HSQLDB 和 Eclipse 均降低了上述任务的难度,因为 HSQLDB 本身可以嵌入到其他应用程序中,而 Eclipse 提供了清晰而且易于理解的插件基础设施以及用于创建新插件的健壮的开发环境 PDE。即使您以前从未接触过 Eclipse 插件开发,PDE 也可以让您很容易上手。请参阅本文后面 参考资料 部分中讲述 Eclipse 基础知识的文章。

这些指导性内容假定您使用的是 Eclipse SDK 分布,而不是 Platform Runtime-Binary 加 JDT。但如果您只是要开发常规 Java 应用程序,则后者更加适合。您可以使用您最喜欢的操作系统,因为我们将只使用 Java 代码,而根本不会用到本机代码。

为了创建有用的插件组并尽可能地少书写代码,我们将从最不费力的工作开始。稍后,在这个系列的下一部分内容中,我们将看到如何利用 Eclipse 功能为 HSQLDB 提供增值。

在本文中,我们将集中讲述我们代码的下列功能:

  1. 以服务器模式启动 HSQLDB 引擎,这样用户应用程序和 SQL 控制台(像 HSQLDB 自带的 DatabaseManager 实用工具)都可以运行 SQL 语句。
  2. 完全停止 HSQLDB 服务器。
  3. 调用 DatabaseManager 实用工具,这样开发人员可以从 Workbench 交互式地输入 SQL 语句。
  4. 使用 HSQLDB ScriptTool 实用工具运行 SQL 脚本文件。这些年来,为了创建数据库表和插入测试数据,我已经在我的项目文件夹中放入了大量 *.sql 文件,而且长久以来,我一直希望能够有一种容易的方式来运行它们。
  5. 配置 HSQLDB 连接属性,比如 TCP 端口和管理员密码。

如何才能使得这些函数可以为 Workbench 所用呢?完成前面三步最容易的方式是提供一个 actionSet ,它被添加到新的顶级菜单中并且可从它自己的工具栏访问。运行 SQL 脚本文件的操作必须只被绑定到扩展名为“*.sql” 的文件上, 所以这将是一个 objectContribution , 它被 Workbench 添加到显示这些文件的任意视图上的弹出式菜单中。最后,连接参数要能很好地符合插件的参数选择页面,从 Workbench Window 菜单中可以访问这个页面。

图 1 显示了新的菜单和工具栏,而图 2 显示了 Navigator 视图的弹出式菜单中的新项,图 3 则显示了属性页面,这样您就可以看到我们的插件组的第一个版本是什么样子。




把 HSQLDB 变成一个 Eclipse 插件

构建我们的插件组的第一步是把 HSQLDB 本身包装成一个 Eclipse 插件。这个插件将只包含 hsqldb.jar 和 Workbench 要求的必要的plugin.xml 文件。如果您已经浏览了标准 Eclipse 插件目录,那么您可能已经发现,JUnit, Xerces, Tomcat,以及其他常见的 Java 包被隔离在它们各自的插件中,未曾改变,而且所有的 Eclipse 细节都被封装在其他插件中。这种划分方式使得这些第三方工具易于更新,而不一定要求改变 Eclipse 本身。另外一个好处就是与许多插件共享这些常见的库很容易。

打开您的 Eclipse SDK 安装,并创建一个新的插件项目;将其命名为 hsqldb.core(我知道推荐使用的名称是 org.hsqldb.core,但是我不愿意假装使用了 HSQLDB 名称空间。或许 HSQLDB 开发人员阅读至此会赞同这个想法,并推荐它为“正式的”Eclipse 集成插件;这样的话该名称极有可能被改掉)。确保选中的是“Empty Plugin” 选项, 而不是任何插件模板;否则,您将得到一个毫无用处的顶级插件类,如果您创建了该类之后希望删掉它,那么它可以被安全地删除。把 hsqldb.jar 从您的 HSQLDB 安装拷贝到项目目录中,并将其添加到项目的 Runtime。您可以使用 PDE Plugin Manifest Editor或者简单地拷贝清单 1 中的内容来完成这项工作。


清单 1. 用于 hsqldb.core 插件的 plugin.xml 清单文件
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>

<?xml version="1.0" encoding="UTF-8"?>


   
      
         
      
   


</td></tr></table>

添加 Workbench 扩展

接下来,创建名为 hsqldb.ui 的第二个插件项目。这个项目将为插件组的第一修订本包含所有的 Eclipse 扩展:包含三个操作的一个操作集、与 *.sql 文件相关的对象作用,以及一个属性页面。将其主类( Plugin 类)命名为 PluginUi并接受包的默认名称 hsqldb.ui

使用 Plugin Manifest Editor 打开 plugin.xml 文件,并选择 Extensions 选项卡。将所分配菜单更名为 HSQLDB 并改变示范操作,使其显示标签“Runs HSQLDB Database Manager”。向带有标签“Stops HSQLDB database server” 和“Starts HSQLDB database server”的同一个 actionSet 添加另外两个操作,并为每个操作提供惟一的操作 ID 和实现类。请注意,上述操作将以与创建时相反的顺序(即与在插件清单文件中出现的顺序相反)显示在菜单和工具栏中。

单击 Add 按钮,从而使用 Extension 模板、弹出式菜单和属性页面添加两个新的扩展。弹出式菜单应该与 *.sql 文件模式相关联,但是属性页面字段应该通过编程进行设置。

图 4 显示了 Plugin Manifest 编辑器的最终外观,并显示了所有的插件扩展;图 5 显示了对象分配的属性(注意针对 *.sql 文件的过滤器),而图 6 显示了文件资源弹出式菜单中的“Run SQL Script”操作的属性。 图标在本文的源代码中给出(请参阅 参考资料),但是我敢肯定您认识有比这画得更好的图形专家!




在能够给我们的操作添加代码之前,我们需要将这种 UI 插件与相应的核心插件区别开来,否则它将不能访问 HSQLDB 类。转到 Plugin Manifest Editor 上的“Dependencies” 页面,并添加一个插件依赖性,如图 7 所示。某些依赖性,像“eclipse.ui”,由 PDE 自动进行配置,而其他依赖性,像 “org.eclipse.debug.core”,则在对操作进行编码时添加。如果您情愿直接编辑 XML 代码,清单 2 显示了 hsqldb.ui 插件的完整 plugin.xml 文件。

要完成插件的安装工作,请添加一个新类到 hsqldb.ui 包中,然后将其命名为 HsqldbUtil 。这个类将包含所有直接处理 HSQLDB 的代码,并使分配的扩展代码保持简单。



清单 2. hsqldb.ui 插件的 plugin.xml 清单文件
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>

     
<?xml version="1.0" encoding="UTF-8"?>


   
      
   
   
      
      
      
      
      
      
   

   
      
         
            
            
         
         
            
               
               
            
         
         
            
               
               
            
         
         
            
               
               
            
         
      
   
   
      
         
         
      
   
   
      
         
            
               
               
            
         
      
   
   
      
      
   

</td></tr></table>

<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>

启动 HSQLDB

使用类 org.hsqldb.Server 以服务器模式启动 HSQLDB 引擎相当容易。以HSQLDB 数据库名称(路径+数据库文件的基本名称)、用于监听连接请求的TCP 端口以及一个用于判别停机时它是否应该调用 System.exit() 的标志 作为命令行参数。下面的命令行是运行 HSQLDB 时的典型情况

java -cp /opt/hsqldb/hsqldb.jar org.hsqldb.Server -database /tmp/bd -port 9001 -system_exit=true

上面这一行命令创建了 /tmp/db.script、 /tmp/db.properties 和 /tmp/db.data 三个数据库文件。

我们可以使用 Server 类主方法并传递一个字符串数组作为其参数。但是我们必须在一个新线程中做这项工作;否则,我们将锁定整个 Workbench。惟一的插件类维持一个到这个线程的引用,这样它就能够在连接到某台服务器之前检查该服务器是否已经启动,并且还可以检查它是否正在运行,因为任何客户机均可连接到这台服务器上,然后提交 SHUTDOWN 语句终止它的运行。

清单 3 中的代码显示了hsqldb.ui.actions.HsqldbStartAction 的 run 方法,而清单 4 显示了 hsqldb.ui.HsqldbUtil startHsqldb 的代码,这些代码实际上启动了服务器。稍后我们将讨论管理用户反馈的技术。

上述代码最有趣的部分是我们如何找到一个位置来放置数据库文件。一个名为 .hsqldb 的项目如果不存在就会被创建,而在该项目中引擎将查找 database.script 和其他数据库文件。

Eclipse 在一个可以保存任何配置文件的标准目录中提供了所有插件。可以通过调用 plugin.getStateLocation 来得到这样一个目录,但是我并不觉得数据库文件实际上是插件配置文件。它们看起来更像是用户数据文件,而且照此说来,它们应该位于开发人员工作区中的项目内。


清单 3. 以服务器模式启动 HSQLDB 的操作,来自 hsqldb.ui.actions.HsqldbStartAction
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>

    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();
            }
        }
    }
            
</td></tr></table>



清单 4. 真正以服务器模式启动 HSQLDB 的代码,来自 hsqldb.ui.HsqldbUtil
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>

    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();
    }
            
</td></tr></table>

<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>

停止 HSQLDB

要停止 HSQLDB 服务器,所需要的只是一个作为 SQL 语句的 SHUTDOWN 命令。如清单 5 所示, 来自 hsqldb.ui.HsqldbUtil 的 stopHsqldb 方法 完成了这个任务。相应操作对应的代码和启动服务器时描述的代码几乎完全相同,所以这里没有列出。清单 5 中抛出了 ClassNotFoundException ( 来自 Class.forName ) 和 SQLException 异常,所以操作代码能够提供足够的反馈给用户。


清单 5. 用于停止 HSQLDB 服务器的代码
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>

    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);
    }
            
</td></tr></table>

<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>

运行 HSQL Database Manager

运行 HSQLDB 中的 Database Manager 实用工具比运行服务器本身还要棘手一点。因为服务器被创建为可以嵌入到其他应用程序中,而上述实用工具则计划作为单机应用程序或 Java applet 来运行。尽管两种模式均接受命令行参数(或 applet 参数),我们还是不希望仅仅为了得到一个用户可以在其中输入 SQL 语句的窗口,就需要额外增加启动一个新的 Java VM 的开销。直接调用类 static void main(String[] args) 不会如预期一样工作,因为它将调用 System.exit() 终止 Workbench。

浏览 org.hsqldb.util.DatabaseManager 源代码之后,我们发现实例化和初始化一个 JDBC 连接很容易,但是所必需的方法不是公共的。所以我们可以在 HSQLDB 源树中创建一个名为 org.hsqldb.util.PatchedDatabaseManager 的类,然后使用所提供的 Ant 构建脚本生成一个新的 hsqldb.jar,其中包含我们打过补丁的 Database Manager。只有两个方法的可见性需要改为 public: void main() 和 void connect(Connection con) 。清单 6 显示了插件如何使用这两个方法运行补丁类。

Database Manager 是一个 AWT 应用程序 (参见图 8), 它将创建自己的事件线程(独立于 Eclipse SWT 线程),并且在调用 System.exit() 关闭 Workbench 之后就会终止。可以运行该实用程序的多个实例而不会导致问题,除非没有 Workbench 窗口可以依靠。对于我们的 HSQLDB 插件的第一修订本来说,这很好,但是在这个系列结束之前,我们必须将其改为一个 SWT 应用程序,就像 Eclipse 视图那样嵌入其中。



清单 6. 启动打过补丁的 Database Manager 实用程序
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>

    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);
        }       
    }
            
</td></tr></table>

<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>

运行 SQL 脚本

HSQLDB Script Tool 读取纯文本文件,并依靠一个给定的 JDBC URL 执行文件中包含的 SQL 语句。SQL 语句批处理由 go 命令进行分隔, 而且脚本也可以使用 print 命令在脚本中书写消息。

熟悉其他数据库系统的开发人员创建的脚本可能仅仅包含由分号(;)分隔开的 SQL 语句,但是这使得 HSQLDB 可以在单个批处理中运行所有脚本,而只返回最后一条语句的结果。您需要在 SQL 语句之间包含 go 命令,以接受每一条语句的结果。

让用户浏览脚本结果最容易的方法是把它们放入一个控制台视图中,就像从 Workbench 调用的 Java 应用程序和 Ant 构建脚本一样。这要求创建一个 Java Launch 配置,而该配置又会创建另一个 Java VM。因为 SQL 脚本存在时间较短,所以我们可以认为其开销是可以接受的,而且如果您认为 Database Manager 也应该在它自己的 VM 中被调用,您可以使用清单 7中给出的 runScriptTool 方法作为一个例子。

清单 7 是本文(这个系列的第一篇)中最长的清单,其中大部分内容是关于建立一个包含正确的 JRE 自举类(必须被显式设定)和 hsqldb.core 插件中的 hsqldb.jar 的类路径。查阅本文末尾的 参考资料 部分可以获得更多关于运行由 Eclipse 提供的框架以及由 JDT 提供的扩展的信息。

对于熟悉其他 GUI 工具包的开发人员来说,如何获取被选中 SQL 脚本文件的路径并不是一件显而易见的事情。 IObjectActionDelegate 接口由扩展资源弹出式菜单的对象分配实现,调用 它的 run 方法后收到的只是一个到操作本身的引用,就像一个顶级菜单操作一样,没有包含与被选中的资源或始发控件有关的信息。资源引用实际上被提供给 selectionChanged 方法,而且必须被保存为一个实例变量,以备稍后使用——请参见清单 8。


清单 7. 运行 HSQLDB Script Tool
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>

    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);
    }
            
</td></tr></table>

清单 8. 如何得到要执行的 SQL Script,来自 hsqldb.ui.popup.actions.HsqldbRunScript
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>

    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;
                }
            }
        }
    }
            
</td></tr></table>

<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>

HSQLDB 属性

现在我们的插件的大部分功能都已经就位,仅仅缺少配置服务器连接参数的方法:它监听的 TCP 端口、管理员用户名称以及管理员用户密码。TCP 端口可能必须要改变,从而避免与安装在开发人员机器上的其他应用程序发生冲突;用户及密码可以从任意客户机上使用 SQL 语句来改变。这些参数可以被放入一个类似 C 结构(或类似 pascal 记录)的名为 HsqldbParams 的类中, 如清单 9 所示。它也声明了由 Plugin Preferences Store 和 Property Page 使用的常量来取得并保存参数(引用)值。


清单 9. 用于 HSQLDB 服务器连接参数的参数选择结构
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>

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 = "";
}
            
</td></tr></table>

不幸的是,由 PDE New Extension Wizard 生成的参数选择页面类起了一点误导作用,它包括一个方法,用于设置 不会被插件参数选择存储使用的默认参数选择值。初始化默认值的正确位置应该是 主要的 Plugin 类本身。否则,所有使用默认值的参数选择将被参数选择存储返回为 0 或 null。

因此,参数选择页面类变得简单多了,如清单 10 所示, 并且方法 initializeDefaultPreferences 被添加到 PluginUi 类中,如清单 11 所示。注意,每个参数选择的默认值均由参数选择结构定义,而不是由插件类或参数选择页面类来定义。


清单 10. HSQLDB 插件的参数选择页面
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>

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) {
    }
}            
</td></tr></table>

清单 11. 来自生成的 Plugin 类的已更改方法
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>

    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);
    }
               
</td></tr></table>

<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>

提供反馈给用户

由于插件组既没有提供视图也没有提供编辑器给 Workbench,所以只有几种有限的方法可以为用户提供操作方面的反馈。我们可以使用 Workbench 窗口的状态栏以及一个 SWT MessageDialog ,这样用户就不会错过重要的事件(通常是错误)。

Workbench 操作的可见性及实现模型主要集中在工作区资源选择方面,但是大部分插件操作(启动及停止 HSQLDB 服务器,还包括启动Database Manager)并不依赖于资源选择,而是依赖于服务器是否正在运行——这个条件必须由每个操作的 run 方法进行显式检查。

警告: 插件类与操作类直到绝对需要降低 Workbench 对内存的需求时才会被初始化。清单文件内容被用于表示菜单选择和启用/禁用它们, 但是由于 HSQLDB 服务器不属于工作区资源,所以它不能启用操作。正如清单 2 中的清单代码所描述的那样,您可以(请参 元素)在激活插件的基础之上启用/禁用一项操作,所以在启动时只有“Start HSQLDB server” 操作可以被启用,但在这之后如果服务器已经处于运行状态,则不存在容易的方法来禁用这项操作以及当其停止后重新启用它。

注意,用于在 Workbench 窗口上放置一个沙漏状光标的代码,以及用于设置状态栏消息的代码不易在 PDE 文档或 Eclipse.org 文章中找到。


<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>

最后一步

插件类重写了 shutdown 方法,所以当 Workbench 被关闭时它能够干净利落地关闭服务器,这时我们就结束了整个过程。当然,要得到一个完整的插件组还需要完成这里没有讲到的额外任务,比如:


<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>

结束语

本文介绍如何创建可将 HSQLDB 数据库引入到 Eclipse Workbench 中的一组插件,以增添启动和停止服务器以及运行 SQL 语句和脚本的能力。我们已经了解到 PDE 是如何使这类插件的创建工作变得容易的,即对大多数任务使用向导和编辑器,而留给开发人员的任务不过是创建真正实现所需功能的代码。

在这个系列的下一个部分中,您将了解到如何利用 Workbench 的功能向 HSQLDB 开发添加值,而不仅仅是简单地运行预先存在的工具。


<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>


<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>

关于作者<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td colspan="3"></td></tr><tr align="left" valign="top"><td>

</td><td></td><td width="100%">

Fernando Lozano 是一位长期从事开源工作的狂热者和 Java 开发人员,还是 Java and GNU/Linux 一书(仅在葡萄牙发行销售)的作者。他另外从事的工作还包括:向企业出售使用 Eclipse 和其他开源工具开发 J2EE 应用程序的服务,协助维护 GNU Project Web 站点上葡萄牙语的翻译工作,而且还是 Linux Professional Institute Brazil 委员会的一员。可以通过 fernando at lozano.eti.br与Fernando 取得联系。

</td></tr></table>

<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>


↑返回目录
前一篇: 基于 Eclipse 的 Apache Derby 工具
后一篇: Eclipse 和 HSQLDB: 将关系数据库服务器嵌入到 Eclipse 中,第 2 部分