级别: 初级 杨 凡 (yangffan@cn.ibm.com), 软件工程师, IBM 郑 霞锦 (xjzheng@cn.ibm.com), 软件工程师, IBM
2009 年 9 月 25 日 本文将介绍基于 Web2.0 的 OpenSource Framework ZK 。具体阐述 ZK 框架的搭建、在 ZK 中使用 JDBC 接口与数据库连接池操作,以及如何整合 Hibernate。
简介
ZK 是一个事件驱动 (event-driven) 的,基于组件 (component-based) 的,用以丰富网络程序中用户界面的框架。 ZK 包括一个基于 AJAX 事件驱动的引擎 (engine),一套丰富的 XUL 和 XHTML,以及一种被称为 ZUML(ZK User Interface Markup Language,ZK 用户界面标记语言 ) 的标记语言。
用户可以利用 XUL 和 XHTML 的丰富特性来呈现您的 Web 应用,操纵它们来处理因用户活动而引发的事件,就像在桌面应用程序中那样。不同于大多数其它框架,就 ZK 而言,AJAX 是一种幕后 (behind-the-scene) 技术,组件内容的同步和流水线事件 (pipelining of events) 都由 ZK 引擎自动完成。
这样大大简化了应用程序的开发周期。同时又使得前端程序美观,易用;
除了简单的模型和丰富的组件,ZK 也支持一种文本标记语言,称为 ZUML 。 ZUML,感兴趣的朋友可以深入了解这种语言,从中体会到他的强大。
ZK 的安装
作为一个基于 java 的 web framework,再使用 ZK 之前,我们需要成功安装 JDK 和 tomcat5.5 或者更高的版本。 在这之后,我们可以去 ZK 的官方网站(http://www.zkoss.org)上 download 我们开发所需要的 library 。
创建第一个工程“ Hello World ”
创建一个新的工程对于 ZK 来说非常的简单,在我们的 tomcat 的目录下($TOMCAT_HOME/webapps),建立新的目录 zkdemo 。其目录结构如下所示:
+zkdemo
+WEB-INF
web.xml
index.zul |
解压缩我们刚才 download 的 library 并将 jar 文件 copy 到我们的目录中, $TOMCAT_HOME/webapps/$PROJECT_NAME/WEB 。结果如下,
dist/lib/*.jar
dist/lib/zkforge/*.jar
dist/lib/ext/*.jar |
编辑 web.xml,将如下代码加入其中:
<!-- ZK -->
<listener>
<description>Used to clean up when a session is destroyed</description>
<display-name>ZK Session Cleaner</display-name>
<listener-class>org.zkoss.zk.ui.http.HttpSessionListener</listener-class>
</listener>
<servlet>
<description>ZK loader for ZUML pages</description>
<servlet-name>zkLoader</servlet-name>
<servlet-class>org.zkoss.zk.ui.http.DHtmlLayoutServlet</servlet-class>
<init-param>
<param-name>update-uri</param-name>
<param-value>/zkau</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>zkLoader</servlet-name>
<url-pattern>*.zul</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>zkLoader</servlet-name>
<url-pattern>*.zhtml</url-pattern>
</servlet-mapping>
<servlet>
<description>The asynchronous update engine for ZK</description>
<servlet-name>auEngine</servlet-name>
<servlet-class>org.zkoss.zk.au.http.DHtmlUpdateServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>auEngine</servlet-name>
<url-pattern>/zkau/*</url-pattern>
</servlet-mapping> |
使用 zk 标记,创建 web 页面 index.zul 。加入代码:
<window title="My First window" border="normal" width="200px">
Hello, World!
</window> |
重启 Tomcat
打开浏览器,键入 URL:http://localhost:8080/zkdemo/index.zul。此时 , 您就能看到我们的第一个 ZK 的 web 程序。
当然,我们也可以添加一些互动的元素来扩展程序的功能。
<window title="Hello" border="normal">
<button label="Say it" onClick="alert("Hello World!")"/>
</window> |
图 1: 第一个 ZK 程序
基于 ZK 的数据库应用实例
下面我们通过一个实例来开发一个基于数据库的 Web 2.0 应用。在此实例中,我们使用简单的 JDBC 方式作为数据库连接。使用 java.sql.DriverManager 。在此,我们选择的数据库是文本型的 hsqldb,数据表是一个事物记录,由 3 个字段组成(item:事件名; priority:事件优先级; Date: 事件时间)
事前的准备
与所有的 application 类似,在我们开始动手之前,需要下载我们这个应用所必须的 jar package 。幸运的是,这些 package 我们都可以在 zk5.2 的 image 中找到。并且将这些 package 拷贝到我们的 application 的目录中,${tomcat_home}/webapps/${Application_name}/WEB-INF/lib 。
DAO 对象
当然,我们还是需要把 JDBC 访问写入到我们的 DAO 对象中,与大多数 java 的 jdbc programming 类似,请参考下面的代码:
public class EventDAO
{
private String url = "jdbc:hsqldb:file:/hsqldb/event";
private String user = "sa";
private String pwd = "";
public EventDAO() {
try
{
Class.forName("org.hsqldb.jdbcDriver");
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
public List findAll()
{
Statement stmt = null;
Connection conn = null;
List allEvents = new ArrayList();
try {
conn = DriverManager.getConnection(url, user, pwd);
stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select * from event");
Event evt;
while (rs.next()) {
evt = new Event();
evt.setId(rs.getString(1));
evt.setName(rs.getString(2));
evt.setPriority(rs.getInt(3));
evt.setDate(rs.getDate(4));
allEvents.add(evt);
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
try {
stmt.close();
}
catch (SQLException e) {
e.printStackTrace();
}
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return allEvents;
}
public boolean delete(Event evt){
Connection conn = null;
Statement stmt = null;
boolean result = false;
try {
conn = DriverManager.getConnection(url, user, pwd);
stmt = conn.createStatement();
if (stmt.executeUpdate("delete from event where id = '"
+ evt.getId() + "'") > 0);
result = true;
} catch (SQLException e) {
e.printStackTrace();
}finally {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return result;
}
public boolean insert(Event evt){
Connection conn = null;
Statement stmt = null;
boolean result = false;
try {
conn = DriverManager.getConnection(url, user, pwd);
stmt = conn.createStatement();
if (stmt.executeUpdate("insert into event(id,name,priority,date) " +
"values ('" + UUID.randomUUID().toString() + "','" + evt.getName() +
"'," + evt.getPriority() + ",'" + new SimpleDateFormat("yyyy-MM-dd
HH:mm:ss").format(evt.getDate()) + "')") > 0);
result = true;
} catch (SQLException e) {
e.printStackTrace();
}finally{
try {
stmt.close();
}
catch (SQLException e) {
e.printStackTrace();
}
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return result;
}
public boolean update(Event evt){
Connection conn = null;
Statement stmt = null;
boolean result = false;
try {
conn = DriverManager.getConnection(url, user, pwd);
stmt = conn.createStatement();
if (stmt.executeUpdate("update event set name = '" + evt.getName() +
"', priority = " + evt.getPriority() + ", date = '" +
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(evt.getDate())+
"' where id = '" + evt.getId() + "'") > 0);
result = true;
} catch (SQLException e) {
e.printStackTrace();
}finally{
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return result;
}
} |
编译并将 class 文件拷贝到 ${tomcat_home}/webapps/${application_name}/WEB-INF/classes/${package_name} 。
页面 ZUL 脚本
现在我们框架已经被建立起来了,下面我们需要实现的就是前端页面的展示以及对数据库的操作。页面代码如下所示:
<window title="To do list" width="640px" border="normal">
<zscript>
import org.zkforge.todo.event.Event;
import org.zkforge.todo.event.EventDAO;
import java.util.ArrayList;
import java.text.SimpleDateFormat;
import java.util.UUID;
//fetch all events from database
EventDAO evtdao = new EventDAO();
List allEvents = evtdao.findAll();
// 添加一条记录
void add(){
//insert into database
Event newEvt = new Event(UUID.randomUUID().toString(),
name.value,priority.value.intValue(),date.value);
evtdao.insert(newEvt);
//synchronized data with database
allEvents = evtdao.findAll();
//insert a listEvent into the listbox
Listitem li = new Listitem();
li.setValue(newEvt);
li.appendChild(new Listcell(name.value));
li.appendChild(new Listcell(priority.value.toString()));
li.appendChild(new Listcell(new SimpleDateFormat("yyyy-MM-dd").format(date.value)));
box.appendChild(li);
}
// 修改一条记录
void update(){
//update database
Event editEvt = (Event)box.selectedItem.value;
editEvt.setName(name.value);
editEvt.setPriority(priority.value);
editEvt.setDate(date.value);
evtdao.update(editEvt);
//update listbox
List children = box.selectedItem.children;
((Listcell)children.get(0)).label = name.value;
((Listcell)children.get(1)).label = priority.value.toString();
((Listcell)children.get(2)).label =
new SimpleDateFormat("yyyy-MM-dd").format(date.value);
}
// 删除一条记录
void delete(){
evtdao.delete((Event)box.selectedItem.value);
box.removeItemAt(box.getSelectedIndex());
cleargb();
}
</zscript>
<listbox id="box" multiple="true" rows="4"
onSelect="move()">
<listhead>
<listheader label="Item" />
<listheader label="Priority" width="50px" />
<listheader label="Date" width="90px" />
</listhead>
<listitem forEach="${allEvents}" value="${each}">
<listcell label="${each.name}" />
<listcell label="${each.priority}" />
<listcell label="${each.date}" />
</listitem>
</listbox>
<groupbox>
<caption label="Event" />
Item: <textbox id="name" cols="25" />
Priority: <intbox id="priority" cols="1" />
Date: <datebox id="date" cols="8" />
<button label="Add" width="36px" height="24px" onClick="add()" />
<button label="Update" width="46px" height="24px" onClick="update()" />
<button label="Delete" width="46px" height="24px" onClick="delete()" />
</groupbox>
</window> |
编写完成之后将其存储为 todu.zul 拷贝到我们的 application 目录下,我的本机目录是:/${tomcat_home}/webapps/todo
打开我们的浏览器,输入 http://localhost:8080/todo/todo.zul
图 2:通过 JDBC 获取的数据
Add 用来增加一条记录,Update 用来修改,Delete 用于删除。点击任何按钮我们可以对这个数据表实现无刷新的操作。而不需要去编写复杂的 javascript 与 Ajax 特效脚本。 ZK 已经帮助我们 integrate 到 Framework 中了。
除以上办法外,所有的 J2EE 框架和 Web 服务器都支持连接池操作。这将有助于节约我们有限的 web 资源,减少系统开销。
优化:配置连接池
这个修改其实非常简单,但是对于程序的效率却有相当的提高,尤其是规模的访问来说。我们只需要在 tomcat 中去配置一下就好。
编辑 $Tomcat/conf/context.xml,加入如下的内容。
<Context>
<Resource name="hsqldb/event" auth="Container"
type="javax.sql.DataSource" username="sa" password=""
driverClassName="org.hsqldb.jdbcDriver" url="jdbc:hsqldb:file:c:/hsqldb/event"
maxActive="8" maxIdle="4" />
</Context> |
重启 TOMCAT. 验证一下我们的修改。还是打开我们的浏览器,输入 http://localhost:8080/todo/todo.zul
图 3:通过数据连接池获得的数据
自此我们这个基于 ZK 的数据库应用就已经完成了。
ZK 还有许多强大的功能有待于我们去挖掘。下面我们来看看 ZK 是如何整合 Hiberate 的。
ZK 深层次应用 : 整合 Hibernate
Hibernate
Hibernate 是一个对象关系映射 (ORM) 解决方案,专门针对 Java 语言。 Hibernate 的主要特点是简化了对于数据库的访问。
Hibernate 的安装与 ZK 的配置
在使用 Hibernate 之前,我们要下载 Hibernate(http://www.hibernate.org),将所有的 jar 文件放到 $myApp/WEB-INF/lib/ 下。
为使 ZK 和 Hibernate 顺利的工作,你需要完成下面的工作。
- 在 $myApp/WEB-INF/lib/ 下创建 zk.xml;
- 在 zk.xml 中加入如下的内容:
<!-- Hibernate SessionFactory lifecycle -->
<listener>
<description>Hibernate SessionFactory lifecycle</description>
<listener-class>org.zkoss.zkplus.hibernate.HibernateSessionFactoryListener
</listener-class>
</listener>
<!-- Hibernate OpenSessionInView Pattern -->
<listener>
<description>Hibernate Open Session In View life-cycle</description>
<listener-class>org.zkoss.zkplus.hibernate.OpenSessionInViewListener
</listener-class>
</listener>
<!-- Hibernate thread session context handler -->
<listener>
<description>Hibernate thread session context handler</description>
<listener-class>org.zkoss.zkplus.hibernate.HibernateSessionContextListener
</listener-class>
</listener> |
创建 Java 对象
package events;
import java.util.Date;
public class Event {
private Long id;
private String title;
private Date date;
public Event() {}
public Long getId() {
return id;
}
private void setId(Long id) {
this.id = id;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
} |
创建你的首个 Java 类 (Event.java), 之后,编译 Java 源文件,然后将类文件放到 Web 部署文件夹的 classes 目录下,要保证包名正确。 ( 例如,$myApp/WEB-INF/classes/event/Event.class)
映射 Java 对象
有两种方式告诉 Hibernate 如何加载和存储持久类对象,第一种方式是使用 Hibernate 映射文件,另一种方式是使用 Java 注释。
使用映射文件
简单的为持久类 Event.java 创建一个 Event.hbm.xml 文件。
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="events.Event" table="EVENTS">
<id name="id" column="EVENT_ID">
<generator class="native"/>
</id>
<property name="date" type="timestamp" column="EVENT_DATE"/>
<property name="title"/>
</class>
</hibernate-mapping> |
将这个 Event.hbm.xml 文件放到部署文件夹的目录 src 下,并且保证正确的包名。 ( 例如 ,$myApp/WEB-INF/src/event/Event.hbm.xml)
使用 Java 注释
使用 Java 注释的好处是不用创建额外的 Hibernate 配置文件。为你的 Java 类简单的添加注释来告诉如何关联 Hibernate 。
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="EVENTS")
public class Event {
private Long id;
private String title;
private Date date;
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE)
@Column(name = "EVENT_ID")
public Long getId() {
return id;
}
private void setId(Long id) {
this.id = id;
}
@Column(name = "EVENT_DATE")
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
1. @Entity 声明这个类为一个持久对象
2. @Table(name = "EVENTS") 注释表示这个实体是和数据库的 EVENTS 表映射的。
3. @Column 元素备用与映射实体属性和数据库表的字段。
4. @Id 元素定义了映射主键字段的属性。 |
创建 Hibernate 配置文件
下一步是安装 Hibernate 来使用一个数据库。 HSQL DB , 一个基于 Java 的 SQL 数据库管理系统 (DBMS),可以在 HSQL DB 网站 (http://hsqldb.org/) 下载到。
解压其到一个目录,例如 c:/hsqld 。
打开一个命令框 (command box),切换到 c:/hsqldb 目录。
在命令提示 (command prompt) 下,执行 java -cp lib/hsqldb.jar org.hsqldb.Server 。
安装完数据库之后,我们需要安装 Hibernate 配置文件。在部署文件夹的目录 src 下创建
hibernate.cfg.xml
文件 ( 例如,$myApp/WEB-INF/src/hibernate.cfg.xml) 。将下面的内容复制到你的 hibernate.cfg.xml 。这依赖于你如何映射 Java 对象。
使用映射文件
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
<property name="connection.url">jdbc:hsqldb:hsql://localhost</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">1</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
<!-- Enable Hibernate's automatic session context management -->
<property name="current_session_context_class">thread</property>
<!-- Disable the second-level cache -->
<property name=
"cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create</property>
<mapping resource="events/Event.hbm.xml"/>
</session-factory>
</hibernate-configuration> |
使用 Java 注释
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
<property name="connection.url">jdbc:hsqldb:hsql://localhost</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">1</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
<!-- Enable Hibernate's automatic session context management -->
<property name="current_session_context_class">thread</property>
<!-- Disable the second-level cache -->
<property name=
"cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create</property>
<mapping class="events.Event"/>
</session-factory>
</hibernate-configuration> |
创建 DAO 对象
创建 EventDAO.java
import java.util.Date;
import java.util.List;
import org.hibernate.Session;
import org.zkoss.zkplus.hibernate.HibernateUtil;
public class EventDAO {
Session currentSession() {
return HibernateUtil.currentSession();
}
public void saveOrUpdate(Event anEvent, String title, Date date) {
Session sess = currentSession();
anEvent.setTitle(title);
anEvent.setDate(date);
sess.saveOrUpdate(anEvent);
}
public void delete(Event anEvent) {
Session sess = currentSession();
sess.delete(anEvent);
}
public Event findById(Long id) {
Session sess = currentSession();
return (Event) sess.load(Event.class, id);
}
public List findAll() {
Session sess = currentSession();
return sess.createQuery("from Event").list();
}
} |
编译并部署该文件到 class 目录中。
ZUML 页面访问持久对象
创建 event.zul 文件,其内容如下:
package events;
<zk>
<zscript><![CDATA[
import java.util.Date;
import java.text.SimpleDateFormat;
import events.Event;
import events.EventDAO;
//fetch all allEvents from database
List allEvents = new EventDAO().findAll();
]]></zscript>
<listbox id="lbxEvents">
<listhead>
<listheader label="Title" width="200px"/>
<listheader label="Date" width="100px"/>
</listhead>
<listitem forEach="${allEvents}" value="${each}">
<listcell label="${each.title}"/>
<zscript>String datestr =
new SimpleDateFormat("yyyy/MM/dd").format(each.date);</zscript>
<listcell label="${datestr}"/>
</listitem>
</listbox>
</zk> |
这样我们就可以在浏览器中访问该 ZUL 了。
至此,我们在文章中已经看到了 ZK 的基本与高级应用。当然,ZK 还有许多强大的功能有待我们去挖掘,希望各位朋友能使用 ZK 开发出美观且功能强大的 web 应用。
参考资料
作者简介  | |  | 杨凡,IBM 软件工程师,具有丰富的 globalization 领域项目开发与管理经验,参与多个产品的全球化开发,也是开源社区的常客。 |
 | |  | 郑霞锦,IBM 软件工程师 , 五年 IT 从业经验,具有广泛的工程北京,丰富的产品自动化测试经验,对于 opensource 领域一向非常关注。 |
对本文的评价
|