原文地址: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
|
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
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:
- 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.
|
|
- 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.
|
|
- 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.
|
|
- 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:
- 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.
- 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.
- Create a build process that places the
derby.jar file in a
lib subdirectory immediately below your application's own directory.
- 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
|