易于数据库访问的ZK特性

org.zkoss.zk.ui.event.EventThreadCleanup 接口

就像以前强调过的,在finally子句中关闭连接是很重要的,这样每个连接都会被正确的返回连接池。

为使你的应用程序更加健壮(robust),你可以实现org.zkoss.zk.ui.event.EventThreadCleanup 接口来关闭任何处在等待之际的连接和语句(any pending connections and statements),以防你的一些应用程序代码忘记了在finally子句中关闭它们。

但是,关闭处在等待之际的连接和语句实际上依赖于你使用的服务器。你需要参考服务器的文档来得知如何编写关闭代码。

[提示]: 在许多情况下,并不需要(且并不容易)提供这样的方法,因为多数连接池的实现可以循环一个连接若调用了连接池的finalized方法(because most implementation of connection pooling be recycled a connection if its finalized method is called)。

在EL表达式中访问数据库

除了在事件监听器中访问数据库,使用EL表达式访问数据库填充一个属性也是很常见的。在下面的例子中,我们从数据库取出数据,并使用EL表达式将它们用listbox展示出来。

<zscript>
   import my.CustomerManager;
   customers = new CustomerManager().findAll(); //load from database
</zscript>
<listbox id="personList" width="800px" rows="5">
   <listhead>
      <listheader label="Name"/>
      <listheader label="Surname"/>
      <listheader label="Due Amount"/>
   </listhead>
   <listitem value="${each.id}" forEach="${customers}">
      <listcell label="${each.name}"/>
      <listcell label="${each.surname}"/>
      <listcell label="${each.due}"/>
   </listitem>
</listbox>

有几种方式来实现findAll方法。

读取所有数据并将其拷贝到链表

最简单的方式是在findAll方法内获取所有的数据,将它们拷贝到一个列表,然后关闭连接。

public class CustomerManager {
   public List findAll() throws Exception {
      DataSource ds = (DataSource)new InitialContext()
            .lookup("java:comp/env/jdbc/MyDB");

      Connection conn = null;
      Statement stmt = null;
      ResultSet rs = null;
      List results = new LinkedList();
      try {
         conn = ds.getConnection();
         stmt = conn.createStatement();
         rs = stmt.executeQuery("SELECT id, name, surname FROM customers");
         while (rs.next()) {
            long id = rs.getInt("id");
            String name = rs.getString("name");
            String surname = rs.getString("surname");
            results.add(new Customer(id, name, surname));
         }
         return results;
      } finally {
         if (rs != null) try { rs.close(); } catch (SQLException ex) [}
         if (stmt != null) try { stmt.close(); } catch (SQLException ex) [}
         if (conn != null) try { conn.close(); } catch (SQLException ex) [}
      }
   }
}

实现org.zkoss.zk.ui.util.Initiator接口

你可以使用init指令来加载数据,以代替在试图中混合使用Java代码。

<?init class="my.AllCustomerFinder" arg0="customers"?>

<listbox id="personList" width="800px" rows="5">
   <listhead>
      <listheader label="Name"/>
      <listheader label="Surname"/>
      <listheader label="Due Amount"/>
   </listhead>
   <listitem value="${each.id}" forEach="${customers}">
      <listcell label="${each.name}"/>
      <listcell label="${each.surname}"/>
      <listcell label="${each.due}"/>
   </listitem>
</listbox>

然后,使用org.zkoss.zk.ui.util.Initiator接口实现my.CustomerFindAll类。

import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.util.Initiator;

public class AllCustomerFinder implements Initiator {
   public void doInit(Page page, Object[] args) {
      try {
         page.setVariable((String)args[0], new CustomerManager().findAll());
            //Use setVariable to pass the result back to the page
      } catch (Exception ex) {
         throw UiException.Aide.wrap(ex);
      }
   }
   public void doCatch(Throwable ex) { //ignore
   }
   public void doFinally() { //ignore
   }
}

事务处理和org.zkoss.zk.util.Initiator

度与复杂的应用程序(例如分布式事务处理),你或许需要明确的控制一个事务处理的活动周期。若所有的数据库访问均是在事件监听器中被处理的,那么在ZK中为使其工作并不需要改变什么。你开始,提交或回滚(start, commit or rollback)一个事务处理,与你的J2EE/Web服务器文档建议的一样。

但是,若你想将整个ZUML页面(组件创建阶段)的赋值(evaluation)在相同的事务处理里被执行,如上一章节描述的那样,你可以实现org.zkoss.zk.util.Initiator接口控制给定页面的事务处理活动周期。

实现的框架如下所示。

import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.util.Initiator;

public class TransInitiator implements Initiator {
   private boolean _err;   public void doInit(Page page, Object[] args) {
      startTrans(); //depending the container, see below
   }
   public void doCatch(Throwable ex) {
      _err = true;
      rollbackTrans(); //depending the container, see below
   }   public void doFinally() {
      if (!_err)
         commitTrans(); //depending the container, see below
   }
}

如上所示,事务处理在doInit方法内开始,且在org.zkoss.zk.util.Initiator 接口的doFinally方法内结束。

如何开始,提交和回滚(start, commit and rollback)一个事务处理取决于你使用的容器。

J2EE事务处理及Initiator

若你使用的为一个J2EE容器,你可以找到事务管理器(transaction manager)(javax.transaction.TransactionManager),然后调用它的 begin 方法开始一个事务处理。调用rollback方法回滚。调用commit 方法提交。

Web 容器和Initiator

若你使用的为一个没有事务管理器的Web容器,可以通过构造(constructing)一个数据库连接来开始事务处理。然后,据此调用commitrollback方法。

import java.sql.*;
import javax.sql.DataSource;
import javax.naming.InitContext;
import org.zkoss.util.logging.Log;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.util.Initiator;

public class TransInitiator implements Initiator {
   private static final Log log = Log.lookup(TransInitiator.class);
   private Connection _conn;
   private boolean _err;

   public void doInit(Page page, Object[] args) {
      try {
         DataSource ds = (DataSource)new InitialContext()
            .lookup("java:comp/env/jdbc/MyDB");
         _conn = ds.getConnection();
      } catch (Throwable ex) {
         throw UiException.Aide.wrap(ex);
      }
   }
   public void doCatch(Throwable t) {
      if (_conn != null) {
         try {
            _err = true;
            _conn.rollback();
         } catch (SQLException ex) {
            log.warning("Unable to roll back", ex);
         }
      }
   }
   public void doFinally() {
      if (_conn != null) {
         try {
            if (!_err)
               _conn.commit();
         } catch (SQLException ex) {
            log.warning("Failed to commit", ex);
         } finally {
            try {
               _conn.close();
            } catch (SQLException ex) {
               log.warning("Unable to close transaction", ex);
            }
         }
      }
   }
}