用 Apache Derby 进行开发 —— 取得节节胜利

用 Apache Derby 进行 Java 数据库开发,第 6 部分

部署嵌入式应用程序

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 用 Apache Derby 进行开发 —— 取得节节胜利

敬请期待该系列的后续内容。

此内容是该系列的一部分:用 Apache Derby 进行开发 —— 取得节节胜利

敬请期待该系列的后续内容。

开发嵌入式 Apache Derby 应用程序

回想在 本系列的文章 中,您一直在使用 Apache Derby 数据库。Derby 数据库支持两种交互模式:网络 模式和嵌入 模式。较为熟悉的交互模式可能是网络模式,在这种模式下,数据库服务器运行在一台计算机上,并且数据库客户机通过网络连接连接到中央服务器上。这种设置描述了大多数商业数据库的安装设置,通常都是遵循事务处理模型。

另一个模型是嵌入模式,在这种模式下,应用程序不但包含实现必要业务逻辑的软件,而且包含数据库引擎软件。最终,应用程序可以完全自包含,有助于简化安装和设置过程。为了强调这一点,因此考虑在本系列的所有文章中,始终只以嵌入模式使用 Apache Derby 数据库。

现在就要开始开发您自己的嵌入式 Derby 数据库应用程序。开发嵌入式数据库应用程序的过程一般可以分为三个步骤:

  1. 将数据库逻辑映射到 Java 类中。
  2. 开发管理数据库专有功能的 Java 代码。
  3. 实现 Java 应用程序中的业务逻辑。

这一节的其余部分先详细讨论这三个步骤,然后再继续讨论如何部署嵌入式 Apache Derby 应用程序。

对象-关系映射

系列 中的前几篇文章已经通过使用虚构的 Bigdog 的 Surf Shop 业务介绍了数据库概念。这个简单的数据库将管理冲浪器材店的存货,主要关注包含冲浪器材店产品的数据库表。在本文中,您将开发一个显示冲浪器材店存货中具体项目详细清单的应用程序。

需要处理的第一步是实现映射到产品的 Java 类,该产品是 bigdog.products 表中的一行。在执行此操作时,您会遇到一个问题:表模式是在 SQL 中定义的,然而业务应用程序是用 Java 语言定义的。在 SQL 关系数据模型与 Java 对象模型之间映射有时会带来挑战,尤其是对于涉及通过主键-外键连接许多表的复杂数据库模式(或 Java 对象模型)。在形式上,此过程被称为对象-关系映射(Object-Relational Mapping,ORM)。

但是,对于这个演示应用程序,您将面对的惟一复杂度是如何把 bigdog.products 表的模式中使用的 SQL 数据库类型映射到适当的 Java 数据类型中。在本例中,过程相对简单,如清单 1 所示,在该清单中将定义 Product 类。

清单 1. 将产品表映射到 Java Product
public class Product {
    
    private int itemNumber = 0 ;
    private BigDecimal price = null ;
    private Date stockDate = null ;
    private String description = null ;
    
    public Product(int itemNumber, BigDecimal price, Date stockDate, String description){
        this.itemNumber = itemNumber ;
        this.price = price ;
        this.stockDate = stockDate ;
        this.description = description ;
    }

    public void printProduct() {
        String line = "------------------------------------" ;
        
        System.out.println("\nBigdog's Surf Shop Product Information") ;
        System.out.println(line + line);
        
        System.out.printf("Item Number      : %-11s\n", this.itemNumber) ;
        System.out.printf("Item Price       : $%-8.2f\n", this.price) ;
        System.out.printf("Item StockDate   : %-10s\n", this.stockDate) ;
        System.out.printf("Item Description : %-40s\n", this.description) ;
        
        System.out.println(line + line + "\n");
    }
...

由于本系列的 第十篇文章 中从 bigdog.products 表中提取数据时介绍了关于执行查询的课程,因此您应对这个清单中提供的映射有些熟悉。该类定义了将 bigdog.products 表中的列与适当的 Java 数据类型相匹配的属性。在实践中,您可以为每个属性添加完整的 getter/setter 方法,虽然在这段演示代码中并没有这样做,但是大多数流行开发环境通常都会自动完成此操作。

Product 类中的两个主要方法是根据需要初始化对象的构造函数,以及为特定产品生成格式良好的输出的 printProduct 方法。此方法将被演示应用程序调用,但是放置在此类中以利用紧密耦合的优点 —— 毕竟,谁能比定义类更了解输出对象的方法?通常,您要为必须向业务应用程序公开的数据库中的每张表定义 Java 类。因此,虽然这样做对于这个简单的演示并不必要,但是您也可以构造一个 Java 类映射到 bigdog.vendors 表。

封装数据库逻辑

在数据库表被成功地映射到 Java 类中后,下一步是把数据库表与相应的 Java 类连接起来。一种可能的做法是将这段连接代码直接放到相应的 Java 类中 —— 例如,清单 1 中提供的 Product 类。但是,在大多数情况下,这样做不好,因为这将在数据库与数据库应用程序之间引入紧密连接。就像您在本 系列 中被反复警告的那样,通常应当尽量避免使用紧密耦合。

更好的方法是实现一个接口层,在该层中可以封装特定于数据库的 Java 代码。在这种方法中,如果数据库详细保存了更改,例如位置、版本甚至基本模式修改,则应用程序将被隔离;您需要做的惟一更改仅限于更改 DAO。此中间层中的 Java 类被称为 DAO,并且连接到数据库表的每个 Java 类通常都有一个 DAO 类。例如,在清单 2 中,ProductDAO 类提供了先前定义的 Product 类的 DAO。

清单 2. 把产品表中的行链接到 Java Product
SELECT itemNumber, price, stockDate, description " + 
        "FROM bigdog.products WHERE itemNumber = ?" ;
...
    public Product getProduct(int targetItemNumber){

        Product product = null ;

        try{
            pstmt.clearParameters() ;
            pstmt.setInt(1, targetItemNumber) ;

            ResultSet rs = pstmt.executeQuery();

            if (rs.next()) {
                int itemNumber = rs.getInt("itemNumber") ;
                BigDecimal price = rs.getBigDecimal("price") ;
                Date stockDate = rs.getDate("stockDate") ;
                String description = rs.getString("description") ;

                product = new Product(itemNumber, price, stockDate, description) ;
            }
            
            rs.close() ;
            
        } catch(SQLException se) {
            printSQLException(se) ;
        }

        return product;
    }
...

ProductDAO 类的主要用途是创建一个由 bigdog.products 表的特定行中的数据组成的新 Product 对象。该行是由传入 getProduct 方法中的 targetItemNumber 值识别的。虽然它没有显示在 清单 2 中,但是此类还将通过发出 SELECT MAX(itemNumber) FROM bigdog.products 查询并分析查询结果,确定最大项目数(演示应用程序需要)。

如果查看 清单 2,您可以轻松地看到底层数据库模式与 DAO 类之间的紧密耦合。在 getProduct 方法中,将通过执行 PreparedStatement 提取数据。通过添加附加层,最大限度地降低任何数据库更改所产生的影响 —— 对于微小更改,此演示应用程序中惟一需要更新的类是 ProductDAO

您还可以把数据库管理代码放到一个单独的类中以封装这类功能,如清单 3 所示。

清单 3. 数据库管理代码
public class DBManager {
    private static Connection con = null ;    

    private static final String driver = "org.apache.derby.jdbc.EmbeddedDriver" ;
    private static final String url = "jdbc:derby:" ;
    private static final String dbName = "SurfShop" ;
...
    private ProductDAO pdao = null ;

    public DBManager(){
        if(!dbExists()){
            try {
                Class.forName(driver) ;
                con = DriverManager.getConnection(url + dbName + ";create=true");

                processStatement(createProductsSQL) ;

                con.setAutoCommit(false) ;
                batchInsertData(insertProductsSQL) ;
                con.commit() ;

            }catch(BatchUpdateException bue) {
...
            }
        }
        pdao = new ProductDAO(con) ;
    }

    public void close() {
        try {
            con = DriverManager.getConnection(url + ";shutdown=true") ;
        }catch(SQLException se) {
            ; // Do Nothing. System has shut down.
        }
        con = null ;
    }

    public ProductDAO getProductDAO() {
        return pdao ;
    }

    private Boolean dbExists() {
        Boolean exists = false ;
        try{
            Class.forName(driver) ;
            con = DriverManager.getConnection(url + dbName);
            exists = true ;
        } catch(Exception e){
            ; // Do nothing, as DB does not (yet) exist
        }
        return(exists) ;
    }
...

清单 3 中提供的 DBManager 类是此应用程序中最大的类。有一部分原因是因为演示应用程序比较简单,但是它也反映了从 Java 应用程序中与数据库进行成功交互的复杂度。此类负责管理数据库连接,包括建立初始连接和创建演示应用程序要访问的 ProductDAO 实例。

由于这是嵌入式应用程序,因此这段代码还负责创建和初始化嵌入式数据库本身。在这个类中,您将通过使用 dbExists 方法完成此操作,该方法将尝试建立数据库连接。如果数据库不存在,将抛出一个异常,并且 DBManager 构造函数自己将创建数据库。然后它将创建 bigdog.products 表,并且最后通过一次批插入操作插入 10 个新行。遵循这个逻辑,如果该应用程序被执行第二次,则 dbExists 方法将成功连接(假定不知何故并未删除本地存储的数据库),并跳过数据库创建过程。为了简化各种数据库连接操作,JDBC URL 被编码为 jdbc:derby,并且根据所需的操作附加不同的后缀。例如,url + dbName + ";create=true" 用于创建数据库,而 url + dbName 用于建立数据库连接。

关于 DBManager 类的最后一个要点是提供 close 方法。此方法将通过尝试用附加在基本 JDBC URL 中的数据库属性 shutdown=true 建立数据库连接,显式关闭 Apache Derby 嵌入式数据库。通过这种方式关闭数据库,可以保证所有事务都将被正常关闭。这将使得数据库可以更加快速地重新启动,因为数据库将一直处于前后一致状态。

开发嵌入式应用程序

过程的最后一个正式步骤是开发 Java 应用程序。为了保持简单,此演示应用程序:

  • 在命令行中接受来自用户的数字输入
  • 检索拥有输入项目号的产品信息
  • 显示用户要查看的产品

给定先前定义的类,演示应用程序代码相对简短并且易于实现,如清单 4 所示。

清单 4. 嵌入式 Apache Derby 应用程序代码
import java.io.BufferedReader ;
import java.io.InputStreamReader ;

public class DemoApp {
    private DBManager dbm = null ;
    private ProductDAO pdao = null ;
    private int maxItemNumber = 0 ;

    public DemoApp() {
        this.dbm = new DBManager() ;
        this.pdao = dbm.getProductDAO() ;
        this.maxItemNumber = pdao.getMaxItemNumber() ;
    }

    public void showProduct() {

        int itemNumber = 0 ;
        Product p = null ;

        BufferedReader cin = new BufferedReader(new InputStreamReader(System.in)) ;

        while(true) {
            try {
                System.out.print("Enter object item number (0 to Exit): ") ;
                itemNumber = Integer.parseInt(cin.readLine()) ;
            }catch(Exception ex){
                ; // Do nothing. In this simple demo you ignore any input errors.
            }    
            
            if(itemNumber == 0)
                break ;
            else if ((itemNumber < 0) || (itemNumber > this.maxItemNumber))
                System.out.println ("Invalid product item number, please try again.") ;
            else{
                p = pdao.getProduct(itemNumber) ;
                p.printProduct() ;
            }
        }
        dbm.close() ;
    }

    public static void main(String[] args) {
        DemoApp da = new DemoApp() ;
        da.showProduct() ;
    }
}

清单 4 中所示的应用逻辑包含在这个类的两个方法中:构造函数和 showProduct 方法。构造函数将按照前面的描述创建一个 DBManager 对象以建立数据库连接,然后获得 ProductDAO 类的实例。此实例将在 showProduct 方法中使用,用于从数据库中检索必要的数据。构造函数还将从数据库检索最大项目数,这将简化错误管理。

showProduct 方法将从控制台的标准输入流中读取一个整数。如果该整数值是 bigdog.products 表的允许值,则将通过使用 bigdog.products 表中的相应数据创建 Product 对象,并且使用 printProduct 方法显示该值。此过程将一直持续,直到用户输入零以终止循环为止。

部署嵌入式 Apache Derby 应用程序

在成功地实现并测试嵌入式 Apache Derby 数据库应用程序之后,下一步是把所有必要的文件打包在一起,以便您可以根据需要部署应用程序。这一节的其余部分将指导您完成先前构造的演示应用程序的打包过程。第一步(如果尚未这样做)是将可从 下载 部分获得的样例代码文件解压缩,如清单 5 所示。

清单 5. 准备构建嵌入式 Apache Derby 应用程序
rb$ mkdir derbyWork
rb$ cd derbyWork/
rb$ unzip ../derby14.zip 
Archive:  ../derby14.zip
  inflating: DBManager.java          
  inflating: DemoApp.java            
  inflating: Product.java            
  inflating: ProductDAO.java         
  inflating: manifest.txt  
rb$ more manifest.txt 
Main-Class: DemoApp
Class-Path: lib/derby.jar

rb$

如您所见,第一步是创建一个新的工作目录,然后把样例代码文件解压缩到这个新目录中。压缩样例代码文件包括五个文件:演示应用程序的四个 Java 源代码文件和 manifest.txt 文件,其中的内容如 清单 5 所示,由包含名称-值对的两行组成。您将使用这个 manifest 文件为演示应用程序构建可执行的 JAR 文件。第一个名称-值对将指定主要应用程序驱动程序的类的名称,本例中为 DemoApp。第二个名称-值对将指定查找 Apache Derby 数据库 Java 类的位置,这些嵌入模式下的类全都包含在 derby.jar 文件中。

下一步是把必要的文件一起打包到 .jar 文件中,如清单 6 所示。

清单 6. 构建嵌入式 Apache Derby 应用程序
rb$ mkdir lib
rb$ cp $DERBY_INSTALL/lib/derby.jar lib/
rb$ javac *.java
rb$ jar cvmf manifest.txt demoapp.jar *.class
added manifest
adding: DBManager.class(in = 5087) (out= 2792)(deflated 45%)
adding: DemoApp.class(in = 1460) (out= 881)(deflated 39%)
adding: Product.class(in = 1684) (out= 919)(deflated 45%)
adding: ProductDAO.class(in = 2625) (out= 1369)(deflated 47%)
rb$ ls
DBManager.class         Product.class           demoapp.jar
DBManager.java          Product.java            lib
DemoApp.class           ProductDAO.class        manifest.txt
DemoApp.java            ProductDAO.java
rb$ rm -rf *.java *.class manifest.txt 
rb$ ls
demoapp.jar     lib
rb$ zip demoapp.zip demoapp.jar lib/derby.jar 
  adding: demoapp.jar (deflated 6%)
  adding: lib/derby.jar (deflated 8%)
rb$ rm -rf demoapp.jar lib 
rb$ ls
demoapp.zip

该过程的第一步是根据 manifest 文件中所示的 CLASS-PATH: lib/derby.jar 名称-值对的要求,创建 lib 目录,然后把 derby.jar 文件复制到这个目录中。如果要将该值改为其他值,请相应地更改 derby.jar 文件的位置,否则部署后的演示应用程序将失败(因为它将无法找到必需的 Apache Derby 类文件)。

接下来,您需要编译包含的演示应用程序源代码,然后创建 JAR 文件。您将利用 cvmf 标记调用 jar 可执行文件以创建 JAR 文件,cvmf 将指定创建 (create) 归档文件,详细说明 (verbose) 所采取的所有步骤,使用指定的 manifest 文件,并将得到的 JAR 文件 (file) 命名为有象征性的名称。当您以这种方式调用该可执行文件时,您必须先指定 manifest 文件,后接需要调用 JAR 文件的名称。最后,必须显式列出新 JAR 文件中应当包含的所有文件或目录。

在创建了包含应用程序代码的 JAR 文件后,您可以安全地删除那些文件(当然,应当在其他位置保存一个备份副本),然后创建包含应用程序 JAR 文件和所需支持类的新压缩文件(在本例中,就是 derby.jar 文件)。最后,为了演示新嵌入式 Apache Derby 数据库应用程序的自包含本性,您可以删除其他所有文件。关于打包嵌入式应用程序的最后一点是:在本例中不交付任何 Apache Derby 软件。因此,不需要包括任何 Apache Derby 许可信息。如果您曾经交付 Apache Derby 数据库软件 —— 例如,把本例发送给其他计算机上的其他人 —— 则需要遵循 交付 Apache Derby 软件 侧栏中说明的 Apache Derby 数据库许可要求。

现在您已经创建了一个包含整个嵌入式 Apache Derby 应用程序的新压缩文件,剩下的惟一工作就是运行演示应用程序,如清单 7 所示。

清单 7. 执行嵌入式 Apache Derby 应用程序
rb$ unzip demoapp.zip 
Archive:  demoapp.zip
  inflating: demoapp.jar             
   creating: lib/
rb$ rm demoapp.zip 
rb$ CLASSPATH=  
rb$ export CLASSPATH
rb$ echo $CLASSPATH

rb$ java -jar demoapp.jar 
Enter object item number (0 to Exit): -1
Invalid product item number, please try again.
Enter object item number (0 to Exit): 11
Invalid product item number, please try again.
Enter object item number (0 to Exit): 1

Bigdog's Surf Shop Product Information
------------------------------------------------------------------------
Item Number      : 1          
Item Price       : $19.94   
Item StockDate   : 2006-03-31
Item Description : Hooded sweatshirt                       
------------------------------------------------------------------------

Enter object item number (0 to Exit): 10

Bigdog's Surf Shop Product Information
------------------------------------------------------------------------
Item Number      : 10         
Item Price       : $34.95   
Item StockDate   : 2006-01-24
Item Description : Open-toed sandal                        
------------------------------------------------------------------------

Enter object item number (0 to Exit): 0
rb$ ls
SurfShop        demoapp.jar     derby.log       lib

要运行演示应用程序,需要展开压缩文件,然后执行 demoapp.jar 文件。作为额外的补充,此示例还将删除压缩应用程序文件并清除 shell 的 CLASSPATH 环境变量的值。因此,您知道这个演示应用程序将在未安装完整的 Apache Derby 数据库软件的计算机上运行。

运行时,这个演示应用程序将创建 Surf Shop 数据库,用 10 行填充 bigdog.products 表,然后继续检索产品信息,直到用户输入零以终止程序为止。虽然简单,但是这个演示应用程序演示了嵌入式 Apache Derby 应用程序的主要要求。您可以轻松地获得此示例,并将其扩展为允许用户更新、插入或删除 bigdog.products 表中的行。或者,您可以使用它作为您自己的嵌入式 Apache Derby 数据库应用程序的基础。

结束语

在本 系列 的最后一篇文章中,结合了前面几篇文章中提供许多课程以开发和部署一个完整的嵌入式 Apache Derby 数据库应用程序。了解了如何将 Derby 数据库表映射到 Java 类中,如何构造 DAO,以及如何成功地进行整合。您还了解了如何将 Derby 应用程序与 Apache Derby 数据库 Java 代码集成到自包含的可部署 Java 应用程序。掌握本系列文章中介绍的知识,您就能够构建您自己的嵌入式 Apache Derby 数据库应用程序 —— 那就去实践吧!


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source, Java technology
ArticleID=263722
ArticleTitle=用 Apache Derby 进行开发 —— 取得节节胜利: 用 Apache Derby 进行 Java 数据库开发,第 6 部分
publish-date=10222007