在桌面应用中使用JAVA DB

 

  NetBeans 发表于: 08-03-31 10:14   [第2版 03-31 10:18↓] [ 只看该作者]
 

原文地址: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.setString(4, record.getPhone());

        stmtUpdateExistingRecord.setString(5, record.getEmail());

        stmtUpdateExistingRecord.setString(6, record.getAddress1());

        stmtUpdateExistingRecord.setString(7, record.getAddress2());

        stmtUpdateExistingRecord.setString(8, record.getCity());

        stmtUpdateExistingRecord.setString(9, record.getState());

        stmtUpdateExistingRecord.setString(10, record.getPostalCode());

        stmtUpdateExistingRecord.setString(11, record.getCountry());

        stmtUpdateExistingRecord.setInt(12, record.getId());

        stmtUpdateExistingRecord.executeUpdate();

        bEdited = true;

    } catch(SQLException sqle) {

        sqle.printStackTrace();

    }

    return bEdited;   

}

 

       保存一个新的Address数据库记录,该记录有一个新的要害值或记录标识。当我们创建PreparedStatement时,我们可以告诉数据库我们想要知道产生的值。在插入数据后,我们可以通过该值返回一个ResultSet。saveRecord方法返回新创建记录的要害值。

stmtSaveNewRecord = dbConnection.prepareStatement(

    "INSERT INTO APP.ADDRESS " +

    "   (LASTNAME, FIRSTNAME, MIDDLENAME, " +

    "    PHONE, EMAIL, ADDRESS1, ADDRESS2, " +

    "    CITY, STATE, POSTALCODE, COUNTRY) " +

    "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",

    Statement.RETURN_GENERATED_KEYS);

 

...

 

public int saveRecord(Address record) {

    int id = -1;

    try {

        stmtSaveNewRecord.clearParameters();

        stmtSaveNewRecord.setString(1, record.getLastName());

        stmtSaveNewRecord.setString(2, record.getFirstName());

        stmtSaveNewRecord.setString(3, record.getMiddleName());

        stmtSaveNewRecord.setString(4, record.getPhone());

        stmtSaveNewRecord.setString(5, record.getEmail());

        stmtSaveNewRecord.setString(6, record.getAddress1());

        stmtSaveNewRecord.setString(7, record.getAddress2());

        stmtSaveNewRecord.setString(8, record.getCity());

        stmtSaveNewRecord.setString(9, record.getState());

        stmtSaveNewRecord.setString(10, record.getPostalCode());

        stmtSaveNewRecord.setString(11, record.getCountry());

        int rowCount = stmtSaveNewRecord.executeUpdate();

        ResultSet results = stmtSaveNewRecord.getGeneratedKeys();

        if (results.next()) {

            id = results.getInt(1);

        }

    } catch(SQLException sqle) {

        sqle.printStackTrace();

    }

    return id;

}

 

发布你的应用程序

       现在你已经写好了你的应用程序,你必须发布它给用户。Java技术应用程序可以使用多种发布策略。包括Java Web Start软件,Applet和独立的JAR文件。我以独立的JAR文件的形式发布了该应用程序。

       ANT的构建文件build.XML,创建AddressBook.jar文件到dist目录。它也放置数据库JAR文件到lib子目录。该应用程序最终发布的结构如下:

AddressBook.jar

lib/derby.jar

       在很多情况下,应用程序使用例如derby.jar这样的第三方类库时,都需要一个外部的执行脚本。这个脚本通常设置第三方JAR文件的classpath和执行该应用程序的JAR文件。这个方法很麻烦。然而,由于它需要多个脚本,具有代表的一个是支持主机操作系统。例如,如何支持该程序发布到Windows,Solaris和Linux平台,我宁愿为Windows平台创建一个run.bat批处理文件,为Solaris或Linux平台创建一个run.csh脚本。换言之,我们可以避免这些笨拙的执行脚本。

       假如在AddressBook.jar的manifest文件里包含了classpath信息,你可以通过简单的命令行操作执行AddressBook.jar应用程序。在大多数平台,你可以在图形窗口界面中双击该JAR文件来运行该应用程序。在命令行下,你可以使用下面简单的执行命令:

 

java -jar AddressBook.jar

 

       这样简单的发布和执行方案可以创建manifest.mf文件来实现,并成为AddressBook.jar文件的一部分。你可以写入信息到manifest文件里,并告诉Java编程语言解释器那个类包含了main方法和那些JAR文件应该被认为是classpath的一部分。下面的manifest文件做到了以上两点,当构建AddressBook.jar文件时,我们可以把它放在JAR文件里。

 

Manifest-Version: 1.0

Main-Class: com.sun.demo.addressbook.AddressFrame

Class-Path: lib/derby.jar

 

       一旦你构建程序产生先前所显示的发布结构,你可以简单的发布成为一个ZIP文件。使用者可以轻松的解压该文件到任何位置,并能运行AddressBook.jar文件。AddressBook.jar文件将包含之前提到的manifest文件和告诉运行时环境哪个JAR文件应该在classpath里。当然,因为Java DB是嵌入到这个程序里的,它应该能找到lib/derby.jar并能正确运行。

 

概要

       用Java DB工作是简单和有趣的。Java DB让你花最小的代价去创建和发布一个嵌入式数据库。仅仅需要记住几点技巧就能使你成功的使用Java DB工作了:

1.    请将derby.jar文件放在你开发环境的classpath里,以便Java技术编译器和运行时环境能找到该库并能编译和运行应用程序。

2.    设置derby.system.home系统属性去告诉Java DB在那里能找到数据库。你可以通过程序代码或命令行来设置该属性。

3.    创建一个构建程序,请确保将derby.jar文件放在你工程目录的lib子目录下。

4.    通过设置你的应用程序的JAR的manifest文件里的Class-Path属性,添加derby.jar到你的应用程序classpath里。

 

脚注

       术语”Java Virtual Machine”和”JVM”意思是Java平台的虚拟机。

 

Using Java DB in Desktop Applications

 

 

 
By John O'Conner, March 2006 

Articles Index

Sun Microsystems recently announced that it is distributing and supporting Java DB based on the 100 percent Java technology, open-source Apache Derby database. Derby was previously available under its earlier name, Cloudscape, from its former owners: Cloudscape, Informix, and IBM. IBM donated the Derby product source code to the Apache Foundation as an open-source project. Sun, IBM, other companies, and individuals have been actively involved in development of the relational database as part of the Apache Derby community. Sun distributes Java DB in many of its products, including the Sun Java Enterprise System and the Sun Java System Application Server. The NetBeans integrated development environment (IDE) 5.0 also supports Java DB.

Java DB is lightweight at 2 megabytes and embeddable within desktop Java technology applications. Desktop applications can now access powerful database storage with triggers, stored procedures, and support for SQL, Java DataBase Connectivity (JDBC) software, and Java Platform, Enterprise Edition (Java EE, formerly referred to as J2EE), all embedded within the same Java virtual machine (JVM).*

This article describes how to download, install, integrate, and deploy Java DB within desktop Java technology applications. A demo application called Address Book demonstrates how to work with Java DB as an embedded database.

Contents
 
Creating the Address Book Demo
Installing Java DB
Integrating Java DB With NetBeans IDE 5.0
Loading the Database Driver
Connecting to the Java DB Database
Creating the Database
Using the Database
Deploying Your Application
Summary
 
Creating the Address Book Demo

The Address Book demo uses Java DB to store address information. This demo stores names, phone numbers, email addresses, and postal addresses. It allows you to create new address entries and to save, edit, and delete them. The application creates its database in the user's home directory within an .addressbook subdirectory. The database is embedded with the application, so there is no need to set up or manage a separate server or system. To deploy this embedded database application, we need only the application JAR file and the database library JAR file. Figure 1 shows the demo's user interface (UI).

Figure 1: Address Book uses Java DB as an embedded database.

Address Book's main frame window is an AddressFrame class that extends a Java Foundation Classes/Swing (JFC/Swing) JFrame. The AddressFrame is a container for other graphical components and also acts as a controller by handling various events generated by the child components. The child components are JPanel subclasses, each with a different responsibility:

  • AddressPanel represents an address record. It also provides the UI for editing existing records and creating new records. It contains text fields for all the major properties of an Address object.
  • AddressActionPanel provides buttons for all the major use cases that the application supports. This panel generates events that AddressFrame must handle. For example, when the user clicks Save, this panel generates an event. AddressFrame listens to and handles all important events from this panel.
  • AddressListPanel provides a scrollable list of names on the far left of the AddressFrame. The list holds ListEntry objects. A ListEntry stores a database record's unique identifier. The record identifier (ID) allows the application to retrieve an entire record's contents into the AddressPanel.

The application uses a Data Access Object (DAO) to isolate the database-specific code. The DAO encapsulates database connections and statements. A DAO is a useful design pattern that allows loose coupling between an application and the underlying persistence-storage mechanism. The application's AddressDao class is an example of a DAO. When the AddressFrame edits, saves, or deletes an Address object, it always uses an instance of the AddressDao class. Although the Address Book application uses Java DB, you could change it to use an entirely different database just by modifying this one class.

Installing Java DB

The easiest way to get Java DB is to download a copy from the Sun Developer Network's Java DB site. The binary distribution provides everything you need to begin working with embedded database applications. After you download the binary distribution, you'll find a Java DB directory structure that contains the following subdirectories:

  • The demo subdirectory has two demonstration programs. One example shows how to create a trivial embedded application. The other shows how to use Java DB in a client-server environment.
  • The frameworks subdirectory contains utilities for setting up environment variables and for creating and working with databases. This directory is not useful for our demo because our application will be entirely self-contained. No outside utilities will be used for the Address Book application.
  • The javadoc subdirectory contains API documentation. This directory is particularly useful if you configure your IDE to point to it as the Java DB API Javadoc.
  • The docs subdirectory contains documentation about the Java DB product itself: setup, administrator, and reference guides.
  • Finally, the lib subdirectory contains the Java DB libraries packaged as JAR files. Read the Java DB documentation to find out about the various libraries. For an embedded database application, we will use only the derby.jar library file. 

Installing Java DB for development requires only that you make the derby.jar file part of your application classpath. It's that simple. You can set the CLASSPATH variable of your Solaris, Linux, Windows, or other host environment to include the JAR file, or you can include the file as part of your command-line options when compiling or running. The Address Book demo's ANT script shows you how to include this JAR file during project development if you use ANT. Alternatively, some IDEs, including NetBeans IDE 5.0, let you configure classpaths.

Integrating Java DB With NetBeans IDE 5.0

Most IDEs provide a way to add libraries to the development classpath. Follow these instructions to add the Java DB libraries to NetBeans IDE 5.0:

  1. From the Tools menu, select Library Manager, as shown in Figure 2.

    Figure 2: The library manager lets you add third-party libraries to your project.
  2. In the Library Manager window, create a new library named JavaDBEmbedded, as shown in Figure 3. Click OK.

    Figure 3: Name the set of libraries that your project will need.
  3. To add the derby.jar file to the JavaDBEmbedded library, click on Add JAR/Folder... in the Library Manager window. Navigate the file chooser to the derby.jar file and select it as shown in Figure 4.

    Figure 4: Add the derby.jar file to the JavaDBEmbedded library.
  4. In the same Library Manager window for the JavaDBEmbedded library, select the Javadoc tab. Add the javadoc subdirectory from your Java DB installation. Now, the Java DB API Javadoc is available when you use the JavaDBEmbedded library in your NetBeans IDE projects.

You can now add the JavaDBEmbedded library to your NetBeans IDE 5.0 project by using the project's property settings. When you compile, debug, and run the application within the IDE, the IDE will be able to find the needed derby.jar file.

To accommodate users of other IDEs, I have placed the derby.jar library within the lib subdirectory of the downloadable Address Book project. The accompanying ANT script can build and run the demo without any dependencies on the NetBeans IDE.

Loading the Database Driver

Loading the JDBC technology driver starts the database management system. Java DB's drivers come with the derby.jar file, so you don't need to download anything else. Load the JDBC driver by referencing it using the Class.forName method. The embedded driver name is org.apache.derby.jdbc.EmbeddedDriver, and you can load it as you would other JDBC drivers.

    Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
 

The Address Book demo reads the driver name from a configuration property file and passes the name to a loadDriver method. Additionally, as mentioned earlier, Address Book encapsulates all database functionality into a Data Access Object (DAO), a core Java EE design pattern used to access data from a variety of sources. The DAO pattern works equally well for Java SE applications like Address Book. The following code snippet shows how AddressDao reads the driver name and loads the driver:

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();
    }
}  
 
Connecting to the Java DB Database

A JDBC technology connection identifies a specific database and allows you to perform administrative tasks. Tasks include starting, stopping, copying, and even deleting a database. The driver manager provides all database connections.

Retrieve a connection from the driver manager by providing a URL string that identifies the database and a set of properties that influence the connection's interaction with the database. A very common use of properties is to associate a user name and password with a connection.

All connection URLs have the following form:

jdbc:derby:<dbName>[propertyList]
 

The dbName portion of the URL identifies a specific database. A database can be in one of many locations: in the current working directory, on the classpath, in a JAR file, in a specific Java DB database home directory, or in an absolute location on your file system. The easiest way to manage your database location in an embedded environment is to set the derby.system.home system property. This property tells Java DB the default home location of all databases. By setting this property, the Address Book demo ensures that Java DB always finds the correct application database. The application database is named DefaultAddressBook, and it will exist within the directory indicated by the derby.system.home property. The connection URL for this database would look like this:

jdbc:derby:DefaultAddressBook
 

The optional propertyList is a set of properties that you can pass to the database system. You can pass properties into the Java DB system either on the URL itself or as a separate Properties object. If properties are part of the URL, a semicolon (;) should precede each property. The most common properties are these:

  • create=true
  • databaseName=nameOfDatabase
  • user=userName
  • password=userPassword
  • shutdown=true

To connect to the DefaultAddressBook database, the demo must first set the derby.system.home system property. The demo uses the .addressbook subdirectory of the user's home directory. Use the System class to find out the user's home directory. Then use the class again to set the derby.system.home property:

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);
}
 

Once the application has specified where all databases will exist, it can retrieve a database connection. In this example, notice that I have appended connection properties to the database URL.

Connection dbConnection = null;
String strUrl = "jdbc:derby:DefaultAddressBook;user=dbuser;password=dbuserpwd";
try {
    dbConnection = DriverManager.getConnection(strUrl);
} catch (SQLException sqle) {
    sqle.printStackTrace();
}
 

Alternatively, you can put those properties into a Properties object. Pass the Properties object as an argument when retrieving a connection:

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();
}
 
Creating the Database

The Address Book demo application does not have a ready-made database. Instead, the application must create the database when it starts. One of the biggest advantages of using an embedded database is that the application -- not the user -- takes care of the details of setting up a database. The application can control where the database exists, what tables exist, and how permissions are handled.

Address Book creates a database called DefaultAddressBook in a subdirectory of the user's home directory, and it does so without asking the user for any additional information. You can create a new database in Java DB by using the create=true property when retrieving a connection to a database. Because our application will use the DefaultAddressBook database, we should first create this database. Assuming that the application has already set the derby.system.home property as discussed earlier, the application creates the database and connects to it like this:

Connection dbConnection = null;
String strUrl = "jdbc:derby:DefaultAddressBook;create=true";
try {
    dbConnection = DriverManager.getConnection(strUrl);
} catch (SQLException ex) {
    ex.printStackTrace();
}
 

Because the create=true property is included, Java DB will attempt to create the database files for the first time. Creating the database doesn't actually create any application tables. However, you should now be able to find a new subdirectory named .addressbook/DefaultAddressBook in your home directory.

After it has created the database, the application creates the tables. The demo uses only one ADDRESS table in the default application APP schema. The following SQL code creates the ADDRESS table:

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) )
 

Each record has a record identifier or ID field. Java DB generates this value for each new record that it adds to the database. The ID field is the primary key for each address record.

All remaining address record fields contain varchar elements of various lengths. For example, the LASTNAME field can contain a maximum of 30 varchar characters. The varchar type is equivalent to a UTF-16 Java char code unit.

The Java technology code that uses the above SQL statement to create the ADDRESS table looks like the following code. The dbConnection is the same as the one shown in the previous code. We simply pass it into createTables, create a new Statement, and call the execute method to run the SQL code on the newly formed database. The strCreateAddressTable instance variable holds the SQL statement text.

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;
}
 

At this point, the database and the ADDRESS table exist in a subdirectory named .addressbook/DefaultAddressBook in your home directory. Although you can browse this subdirectory, avoid modifying any files. If you edit or delete any of these database files directly, you can destroy the integrity of your database.

Using the Database

Once the database and its tables have been created, your application can create new connections and statements to add, edit, delete, or retrieve records. In Address Book, these actions are controlled by buttons within the AddressActionPanel. Figure 5 shows the available options:

  • New. Create a new address record.
  • Delete. Delete the displayed address record.
  • Edit. Edit the displayed address record.
  • Save. Save the new or edited address record that is displayed.
  • Cancel. Cancel any edits or any attempt to create a new record.
Figure 5: Address Book has several options for interacting with records.

The main window of the application is AddressFrame, which acts as a controller and as a view at the same time. It registers itself with the AddressActionPanel to receive notification when a user clicks anywhere on the action bar.

The New command clears the address entry panel and enables the user to edit all fields. No SQL commands are issued at this point, but the UI should allow you to enter a new address.

The Delete command attempts to delete the currently selected address record. AddressFrame retrieves the currently selected Address identifier from the AddressPanel and uses AddressDao to delete the record. The panel calls its own deleteAddress method, which calls the DAO's deleteRecord method with the correct ID. After deleting the record from the database, the application must delete the ListEntry from the AddressListPanel too.

private void deleteAddress() {
    int id = addressPanel.getId();
    if (id != -1) {
        db.deleteRecord(id);
        int selectedIndex = addressListPanel.deleteSelectedEntry();
        ...
    }
    ...
}
 

In the AddressDao, the deleteRecord method handles the actual deletion of the record from the database. The AddressDao creates a PreparedStatement when it first connects to the database.

stmtDeleteAddress = dbConnection.prepareStatement(
        "DELETE FROM APP.ADDRESS " +
        "WHERE ID = ?");
 

The PreparedStatement can be used multiple times, and this one uses a parameter to determine which record to delete. The deleteRecord method executes the update after setting the ID parameter:

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;
}    
 

The Edit command allows the user to edit the currently selected Address record in the AddressPanel. For example, you can change the name, city, or phone number of a saved record.

The Save command retrieves either the newly created or edited Address from the AddressPanel and attempts to either update the existing record or create a new record. If the user has been editing a record, Save will update that record with the new information. If the user has created a new record, Save will insert a new record in the database. New records have not yet been saved. At this point, their ID field is still set to the default -1 value. This value changes to an autogenerated, unique record identifier once you save the record.

The following code in AddressFrame will save edited and newly created address records by calling the DAO's editRecord or saveRecord method, respectively. Of course, when you create a new record, the application must also update the 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);
    }
}   
 

The DAO's editRecord method must update the fields that change in the Address record. Because the demo application doesn't distinguish between edited and unedited fields, it simply updates all fields in the record. Following are the PreparedStatement object and the editRecord method:

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.setString(4, record.getPhone());
        stmtUpdateExistingRecord.setString(5, record.getEmail());
        stmtUpdateExistingRecord.setString(6, record.getAddress1());
        stmtUpdateExistingRecord.setString(7, record.getAddress2());
        stmtUpdateExistingRecord.setString(8, record.getCity());
        stmtUpdateExistingRecord.setString(9, record.getState());
        stmtUpdateExistingRecord.setString(10, record.getPostalCode());
        stmtUpdateExistingRecord.setString(11, record.getCountry());
        stmtUpdateExistingRecord.setInt(12, record.getId());
        stmtUpdateExistingRecord.executeUpdate();
        bEdited = true;
    } catch(SQLException sqle) {
        sqle.printStackTrace();
    }
    return bEdited;   
}  
 

Saving a new Address creates a new database record, and that new record has a new primary key or record identifier. When we create the PreparedStatement, we can tell the database that we want to know the generated keys. Notice how we can retrieve a ResultSet of generated keys after inserting the record. The saveRecord method returns the primary key of the newly created record.

stmtSaveNewRecord = dbConnection.prepareStatement(
    "INSERT INTO APP.ADDRESS " +
    "   (LASTNAME, FIRSTNAME, MIDDLENAME, " +
    "    PHONE, EMAIL, ADDRESS1, ADDRESS2, " +
    "    CITY, STATE, POSTALCODE, COUNTRY) " +
    "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
    Statement.RETURN_GENERATED_KEYS);
    
...
public int saveRecord(Address record) {
    int id = -1;
    try {
        stmtSaveNewRecord.clearParameters();
        stmtSaveNewRecord.setString(1, record.getLastName());
        stmtSaveNewRecord.setString(2, record.getFirstName());
        stmtSaveNewRecord.setString(3, record.getMiddleName());
        stmtSaveNewRecord.setString(4, record.getPhone());
        stmtSaveNewRecord.setString(5, record.getEmail());
        stmtSaveNewRecord.setString(6, record.getAddress1());
        stmtSaveNewRecord.setString(7, record.getAddress2());
        stmtSaveNewRecord.setString(8, record.getCity());
        stmtSaveNewRecord.setString(9, record.getState());
        stmtSaveNewRecord.setString(10, record.getPostalCode());
        stmtSaveNewRecord.setString(11, record.getCountry());
        int rowCount = stmtSaveNewRecord.executeUpdate();
        ResultSet results = stmtSaveNewRecord.getGeneratedKeys();
        if (results.next()) {
            id = results.getInt(1);
        }
    } catch(SQLException sqle) {
        sqle.printStackTrace();
    }
    return id;
} 
 
Deploying Your Application

Now that you have written the application, you must deploy it to users. Java technology applications can use a variety of deployment strategies, including Java Web Start software, applets, and stand-alone JAR files. I distribute the Address Book application as a stand-alone application with JAR files.

The ANT build file, build.xml, uses a dist target to create AddressBook.jar. It also places the database JAR file in the lib subdirectory directly under the AddressBook.jar location. The final distribution structure for the application looks like this:

AddressBook.jar
lib/derby.jar 
 

In many cases, applications that use third-party libraries such as derby.jar require an external script to run. The script usually places the third-party JAR files on the classpath and then executes the application from its JAR. This method is troublesome, however, because it requires multiple scripts, typically one for each supported host operating system. For example, to support distribution on Windows, Solaris, and Linux platforms, I would create a run.bat batch file for the Windows platform and a run.csh script for a Solaris or Linux platform. Instead, we can avoid these clumsy execution scripts.

If our build process includes classpath information in the AddressBook.jar manifest file, you can run the application by simply passing the AddressBook.jar file on the execution command line. On most platforms, you can also just double-click on the JAR file name in a graphical window. On a command line, you can use this simple execution command:

java -jar AddressBook.jar
 

This simple deployment and execution scenario can be accomplished by creating a manifest.mf file that becomes part of the AddressBook.jar file. You can include information in the manifest that tells the Java programming language interpreter which class contains the main method and what other JAR files should become part of the classpath. The following manifest does both, and we can include it when building AddressBook.jar.

Manifest-Version: 1.0 
Main-Class: com.sun.demo.addressbook.AddressFrame 
Class-Path: lib/derby.jar 
 

Once your build process generates the application distribution structure shown previously, you can simply distribute this structure as a ZIP file. Users can simply unzip the file into any location and run the AddressBook.jar file. The AddressBook.jar file will contain the manifest file mentioned earlier and will tell your runtime environment what JAR files should also be on the classpath. Of course, because Java DB is embedded into this application, it must be able to find lib/derby.jar in order to run correctly.

Summary

Working with Java DB is easy and fun. Java DB makes it possible to create and distribute an embedded database with minimal effort. Just remember a few tips to make your work with Java DB successful:

  1. Place the derby.jar file in your development environment's classpath so that your Java technology compiler and runtime environment can find the libraries to compile and run the application.
  2. Set the derby.system.home system property to tell Java DB where to find your database. You can set this property programmatically or on the command line.
  3. Create a build process that places the derby.jar file in a lib subdirectory immediately below your application's own directory.
  4. Add derby.jar to the application classpath by including a Class-Path property in your application JAR's manifest file.
Footnote

The terms "Java Virtual Machine" and "JVM" mean a Virtual Machine for the Java platform."

For More Information

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值