论坛首页 Java企业应用论坛

用 Apache Derby 构建脱机 Ajax

浏览 2769 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (9) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-04-10   最后修改:2010-04-10

人们非常喜爱 Ajax 应用程序,以至于他们十分乐于使用 Ajax 应用程序而不想使用等效的桌面程序。但惟一的问题是出现在网络无法访问的时候怎么办。这是必须要用脱机功能的场景。Apache Derby 是支持 Ajax 应用程序实现脱机访问的优秀选择。了解如何使用 Apache Derby 作为本地数据库,该数据库可以实现 Ajax 应用程序的脱机使用。

 

Apache Derby

Apache Derby 是任何一个 Java 应用程序都可以使用的嵌入式数据库。它是非常有用的工具,因此绑定在 Java Platform, Standard Edition (Java SE) V6 中。虽然嵌入式数据库的应用不计其数,但是许多人都不知道用 Derby 可以实现的一些客户端功能。我们将通过构建一个简单的地址本应用程序研究其中一些应用。我们将从利用 Apache Derby 的 Java Applet 开始,最终实现一个使用 Derby 作为缓存的基于 Ajax 的应用程序。

 

数据访问

对于一篇有关数据库技术的文章,应当首先从数据库代码开始讨论。首先,让我们定义用于存储联系人的简单的表模式,如下所示:

 

您可以设想更复杂的联系人模式,如添加多个电话号码、地址等。但是,对于我们的应用程序来说,使用目前的这种模式刚刚好。当然,存在一个与 Contact 表相对应的 Java 类。在本例中,我们将遵循 Active Record 模式并用能够执行所有数据库操作的类封装数据库行。其代码如下所示:

public class Contact {
    
    private Integer id;
    private String firstName;
    private String lastName;
    private String email;
    
    public static List<Contact> getContacts(String clause){
        if (clause == null)
            clause = "";
        String sql = SELECT_SQL + clause;
        Connection conn = DbManager.getConnection();
        List<Contact> contacts = new ArrayList<Contact>();
        try {
            ResultSet cursor = conn.createStatement().executeQuery(sql);
            while (cursor.next()){
                Contact c = new Contact();
                c.setId(cursor.getInt(1));
                c.setFirstName(cursor.getString(2));
                c.setLastName(cursor.getString(3));
                c.setEmail(cursor.getString(4));
                contacts.add(c);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return contacts;
    }
    
    public static List<Contact> getAllContacts(){
        return Contact.getContacts(null);
    }
    
    public static Contact getContact(String clause){
        List<Contact> results = Contact.getContacts(clause);
        if (results == null || results.size() != 1){
            return null;
        }
        else return results.get(0);    
    }
    
    public void save(){
        if (id == null)
            insert();
        else
            update();
    }
    
    public void delete(){
        Connection conn = DbManager.getConnection();
        String sql = "delete from Contact where id=?";
        try{
            PreparedStatement ps = conn.prepareStatement(sql);
            ps.setInt(1, id);
            ps.executeUpdate();
            System.out.println("Deleted contact id="+id);
        } catch (SQLException e){
            e.printStackTrace();
        }
    }
    
    private void insert() {
        Connection conn = DbManager.getConnection();
        try {
            PreparedStatement ps = conn.prepareStatement(INSERT_SQL, 
                    PreparedStatement.RETURN_GENERATED_KEYS);
            ps.setString(1, firstName);
            ps.setString(2, lastName);
            ps.setString(3, email);
            ps.executeUpdate();
            ResultSet autoRs = ps.getGeneratedKeys();
            if (autoRs.next()){
                id = autoRs.getInt(1);
            }
            System.out.println("Contact saved new id = " + id);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    private void update(){
        Connection conn = DbManager.getConnection();
        try{
            PreparedStatement ps = conn.prepareStatement(UPDATE_SQL);
            ps.setString(1, firstName);
            ps.setString(2, lastName);
            ps.setString(3, email);
            ps.setInt(4, id);
            ps.executeUpdate();
            System.out.println("Contact updated with id="+id);
        } catch (SQLException e){
            e.printStackTrace();
        }
    }

 

该类将完成很多任务,但是所有内容都非常简单。它有与数据库列对应的字段及用于每个字段的常用存取器(getter 和 setter)。它拥有执行所有创建/更新/删除(CReate Update Delete,CRUD)操作及其附带 SQL 查询的方法。例如,查询联系人的方法是静态方法,因此可以执行类似 Contact.getAllContacts() 的操作。保存和删除操作是实例方法,因此对个别联系人调用这些方法。此处没有显示查询,因为查询是标准的 SQL。该类还有常用的 getter 和 setter,但是为了简短起见并未显示。该类将用 Derby 构造基本的客户端存储。首先,我们将使用它作为 Applet UI 的一部分,但是稍后该 Applet 将把它用于 JavaScript。注意,对于每个方法,我们将调用实用程序类 DbManager 以获得连接。

 

package org.developerworks.addressbook;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DbManager {
	static {
		try {
			Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	public static Connection getConnection(){
        try {
            Connection conn = DriverManager.getConnection("jdbc:derby:contacts;create=true");
            return conn;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }
}

这是所有特定于 Derby 的代码所在的位置。实际上,并不是特别特定于 Derby。我们使用的是嵌入式数据库,但是我们处理它的方式和通过 JDBC 访问的其他数据库一样。我们将在类的静态初始化器中把 EmbeddedDriver 用于驱动程序类。另外对于 getConnection 方法,我们将添加额外的参数 create=true 来告诉 Derby 在数据库尚不存在时创建数据库。注意,不需要像使用 JDBC 那样用到用户名或密码 — 这是使用嵌入式数据库的优点之一。您看到了所有的数据访问代码。它看起来非常类似于在任何一个应用程序中都可以看到的数据库代码;而数据库刚好是嵌入式 Derby 数据库。您可以设想为应用程序创建其他模型,这样可以存储专用于应用程序的数据,但是位于客户端。让我们查看一个利用清单 2 中所示的数据访问代码的简单应用程序。

 

Applet UI

首先使用一个非常简单的 Applet,该 Applet 将使用数据访问代码。

public class AddressBookApplet extends JApplet {
    private static final long serialVersionUID = 1L;    
    private static final String[] columns = { "First Name", "Last Name", "Email", "Id"};
    
    public AddressBookApplet() {
        this.setLayout(new GridLayout(1,0));
        JPanel panel = buildUi();
        this.add(panel);
    }

    public Contact addContact(String firstName, String lastName, String email) {
        Contact c = new Contact();
        c.setFirstName(firstName);
        c.setLastName(lastName);
        c.setEmail(email);
        c.save();
        return c;
    }
    
    public void deleteContact(Integer id){
        Contact c= new Contact();
        c.setId(id);
        c.delete();
    }

    public Object[][] loadContacts() {
        List<Contact> book = Contact.getAllContacts();
        Object[][] contacts = new Object[book.size()][4];
        int cnt = 0;
        for (Contact contact : book){
            contacts[cnt++] = contact.toArray();
        }
        return contacts;
    }

    private JPanel buildUi() {
        JPanel panel = new JPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
        
        final DefaultTableModel dataModel = createDataModel();
        final JTable table = createTable(dataModel);
        
        //Lots of Swing/UI Code omitted for brevity    
      }
    
    private JTable createTable(final DefaultTableModel dataModel) {
        final JTable table = new JTable(dataModel);
        table.setPreferredScrollableViewportSize(new Dimension(500, 70));
        table.setFillsViewportHeight(true);
        return table;
    }

    private DefaultTableModel createDataModel() {
        Object[][] contacts = loadContacts();
        final DefaultTableModel dataModel = new DefaultTableModel(contacts, columns);
        return dataModel;
    }
}

 

这段代码大部分是构建 UI 的典型 Swing 代码。所有 UI 代码都是在位于类底部的私有方法中完成的。buildUi 方法处理 Swing 组件的创建,但是为了简短起见而省略了大部分内容。更有趣的是三个公共方法(除了构造函数之外):addContact、deleteContact 和 loadContacts。这三个方法实质上都是先前开发的数据访问代码的包装器。实际上,我们不需要将 Applet 用于最终应用程序的 UI,但是它提供了测试代码的简单方法。如果使用的是 Eclipse,则只需在 Applet 类上右键单击并选择 Run As > Java Applet。

 

安全性

我们将为 Applet 中使用的 JAR 设置签名。如果这里继续遵循计划,有一件事好到令人难以置信。Derby 将给我们提供嵌入到客户机中的持久数据库(所有内容都存储在客户机中)。它有几分像 HTTP Cookie,但是众所周知,那些 Cookie 在每个域中不可以超过 4 KB。客户机中的 Derby 数据库的限制是什么?答案是要么很多,要么很少。

默认情况下,Applet 无法访问本地文件系统,因此 Derby 无法在客户机中存储任何数据。那么使用 Derby 是做白日梦么?幸运的是,它不是。关键是您必须给 Applet 设置数字签名。有签名的 Applet 将获得本地文件系统的访问权,这样如果数据来自有签名的 Applet,则 Derby 可以持久存储这些数据。我们只需给 Applet 设置签名。

$ keytool -genkey -alias sigs -keystore sigstore -keypass password -storepass password
What is your first and last name?
  [Unknown]:  Michael
What is the name of your organizational unit?
  [Unknown]:  developerWorks
What is the name of your organization?
  [Unknown]:  IBM
What is the name of your City or Locality?
  [Unknown]:  San Jose
What is the name of your State or Province?
  [Unknown]:  CA
What is the two-letter country code for this unit?
  [Unknown]:  US
Is CN=Michael, OU=developerWorks, O=IBM, L=San Jose, ST=CA, C=US correct?
  [no]:  yes

$ jarsigner -keystore sigstore -storepass password -keypass password -signedjar 
addrbook.jar derby.jar sigs

Warning: The signer certificate will expire within six months.

 

正如您所见,我们使用两个 JDK 工具给 Applet(技术上是包含 Applet 的 JAR)设置签名。首先,使用 keytool 创建用于保存生成的加密密钥的密钥库。还可以使用它执行创建 SSL 证书之类的任务。在拥有密钥后,结合使用该密钥与 jarsigner 工具来给 JAR 设置签名。注意,我们包括了 Derby JAR,以及包含自定义代码的 addrbook JAR。最终获得了一个自签名 Applet 的示例。这对于开发来说没问题,但是通常不适用于任何面向用户的代码。在这种情况下,您将需要来自受信任提供商(如 VeriSign)的密钥/证书。关键原因是因为需要在客户机中存储数据,我们需要执行这些附加步骤才能符合 Java 语言的客户端安全模型。牢记这些安全事项并且拥有一颗可以正常工作的 Applet 后,我们现在已经准备好从 JavaScript 使用 Applet。


下载完整代码

   发表时间:2010-04-19  
图片怎么没了?
0 请登录后投票
   发表时间:2010-04-19  
你没听说过HTML5么
0 请登录后投票
   发表时间:2010-04-19  
就是用derby进行本地数据存储?
我觉得关键技术是,本地数据库和服务器数据的同步,这像个C/S程序。
0 请登录后投票
   发表时间:2010-09-02  
http://www.ibm.com/developerworks/cn/opensource/os-ad-offline-ajax/
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics