UML软件工程组织

Struts处方:Hibernate与Struts
作者:George Franciscus、Danilo Gurovich

在这篇从Struts处方的摘录中,作者George FranciscusDanilo Gurovich举例描述了如何在一个struts项目中使用Hibernate。他们同时也展示了如何建立一个Struts插件以改善性能。

持久层是一个应用系统最基本的部份。很显然的,如果没有持久层,所有的工作都将丢失。但是,对不同的人来说持久层意味着不同的东西。持久化时间的长短是选择持久层储存媒介的基本衡量标准之一。例如,对于生命周期为一个用户会话的数据来说,Http session是非常合适的。与之对应的,跨越多个session,或者多个用户的持久化则需要一个数据库来保持。数据的数量是另一个非常重要的衡量标准。例如,最佳实践表明大量的数据最好不要被存储在一个Http会话中。在这些情况下,你都应该考虑使用数据库。在这篇文章中,我们的目标就是数据库持久层。

你选择的数据库类型对你有架构与设计都有重要的影响。作为面向对象的开发人员,我们倾向于把数据描绘成描述手上商业问题的一组相互关联的对象这常被称为域模型。 但是,最常用的存储媒介是基于关系模型的。除非我们的对象模型映射成一个关系结构,否则内存中我们数据的表示就会与持久化它的方法不一致。这个问题也被称作不对称问题。最流行的解决这种不对称问题的是一组被称为对象关系映射工具。一个ORM工具是被用来把数据从对象视图转换为关系型、提供诸如创建、读、更新、删除(CRUD)等持久性服务的软件。有许多的关于ORM工具的论文,但是从本质上来说,他们谈论的都是对象映射模式。最流行的ORM工具是开源的Hibernate工程。

在这篇文章中,我们展示了如何在一个Struts项目中应用Hibernate。另外,我们将展示如何建立一个Struts插件来提升你系统的性能。

处方

在这个文摘中,我们使用一个例子来展示你在struts项目中使用hibernate时所需要的所有东西。我们将建立一个应用程序来获取和展示从化学元素周期表里取出的元素。这个应用程序提供给用户一个查找页来通过元素符号来查找元素。应用程序将查询数据库里匹配这个元素符号名的记录并返回查找到的元素信息来响应用户请求。

首先我们将展示如何启动Hypersonic服务器。当数据库服务器启动后,我们建立示例程序所需要的表与数据。一旦数据库准备运行了,我们将建立使用Hypersonic数据库服务器所需的Hibernate的所有东西。接下来的步骤是在action里调用Hibernate来处理数据库读取来响应查询请求。因为建立HibernateFactory对象是非常耗资源的,我们建立一个Struts plug-in来建立factory并把它保存在context里。

让我们从建立Hypersonic数据库服务器开始。你需要从http://hsqldb.Sourceforge.net/下载它。放置hsqldb.jar在你的classpath路径里,从Dos窗口中敲入以下命令来启动Hypersonic:

java org.hsqldb.Server

虽然不同版本的Hypersonic的服务器响应不同。下面的应答是典型的Hypersonic已经准备好响应数据库请求的应答:

Server 1.6 is running
Press [Ctrl]+{c} to abort

随着数据库服务器的启动,我们可以建表和填充数据,如下列表1所示:

Listing 1. 建立元素表

create table elements (id integer(3) IDENTITY,
                 name char(30),
                 number char(30),
                 mass char(30),
                 symbol char(2));

CREATE UNIQUE INDEX ui_elements_pk ON elements (symbol)

insert into elements ( name, number, mass, symbol) values ('Manganese','25','55','Mn');
insert into elements ( name, number, mass, symbol) values ('Zinc','30','65','Zn');
insert into elements ( name, number, mass, symbol) values ('Thulium','69','169','Tm');
insert into elements ( name, number, mass, symbol) values ('Californium','98','251','Cf');
insert into elements ( name, number, mass, symbol) values ('Gold','79','197','Au');
insert into elements ( name, number, mass, symbol) values ('Ytterbium','70','173','Yb');
insert into elements ( name, number, mass, symbol) values ('Molybdenum','42','96','Mo');
insert into elements ( name, number, mass, symbol) values ('Palladium','46','106','Pd');

列表1是建立表的SQL语句,在symbol列上建立唯一索引,插入上面那些化学周期元素。当然你也可以从你高校的化学书本里面找出更多的一些数据插入。

列表2是用来存储从数据库取出数据的JavaBean对象:
Listing 2. JavaBean元素

package com.strutsrecipes.hibernate.beans;

public class Element {
   private String name;
   private String symbol;
   private String number;
   private String mass;
   private int id;

   public Element() {
      super();
   }

   public Element(String name, String symbol, String number, String mass) {
      this.name = name;
      this.symbol = symbol;
      this.number = number;
      this.mass = mass;

   }

   public int getId() {
      return id;
   }

   public void setId(int id) {
      this.id = id;
   }

   public String getMass() {
      return mass;
   }

   public String getName() {
      return name;
   }

   public String getNumber() {
      return number;
   }

   public String getSymbol() {
      return symbol;
   }

   public void setMass(String mass) {
      this.mass = mass;

   }

   public void setName(String name) {
      this.name = name;
   }

   public void setNumber(String number) {
      this.number = number;
   }

   public void setSymbol(String symbol) {
      this.symbol = symbol;
   }
}

Hibernate是一个对象-关系映射工具。它的任务是映射对象到关系型表,反之亦然。所以,我们必须告诉Hibernate如何映射列到JavaBean的属性上。这个是通过Element.hbm.xml文件来完成的。这份文件里面包含的信息用来授予Hibernate从表里面拷贝数据到Elements JavaBean的权利。如果我们使用Hibernate来更新数据,Element.hbm.xml文件里的信息将被用来解析从Elements JavaBean来的数据来生成更新的SQL语句。列表3显示了Element.hbm.xml

Listing 3. Element.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
   "http://hibernate.sf.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping>
   <class name="com.strutsrecipes.hibernate.beans.Element" table="elements">
      <id name="id" column="id">
         <generator class="native"/>
      </id>
      <property name="name" column="name"/>
      <property name="number" column="number"/>
      <property name="mass" column="mass"/>
      <property name="symbol" column="symbol"/>
   </class>
</hibernate-mapping>

让我们跳到列表3

我们声明了与elements表相联系的类文件的完整的包名。然后我们声明了表的名字与这个类相关联。接下来我们声明从JavaBeanid属性到表的id列的映射。因为propertycolumn属性都有相同的值,我们本来可以忽略column属性,但是为了清晰起见,我们还是把column列出来。 <id>是个特殊的标签。它被用来声明表的主键。闭合的标签<generator>表示Hibernate以最适合数据库实现的方式生成该主键。你可以参考Hibernate文档有关标签<id>的更多信息。最后我们为其它的JavaBean属性做声明。为了清晰起见,column属性再次被声明。

一旦映射文件被详细的分析,那一切都非常的明晰了。它简单地声明了表与类的映射和JavaBean属性与表的列名的映射。接下来我将告诉你在哪里放置这个文件。

接下来,我们通过声明环境信息来配置Hibernate。在列表4我们展示Hibernate.cfg.xml文件。

Listing 4. hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
   "http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">

<hibernate-configuration>
   <session-factory>
   <property name="dialect">net.sf.hibernate.dialect.HSQLDialect</property>
   <property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
   <property name="connection.username">sa</property>
   <property name="connection.password"></property>
   <property name="connection.url">jdbc:hsqldb:hsql://127.0.0.1</property>
   <property name="show_sql"> </property>
   <property name="">true</property>

   <mapping resource="/com/strutscookbook/hibernate/beans/Element.hbm.xml"/>
</session-factory>
</hibernate-configuration>

让我们跳到列表4

我们以指定数据库实现方言开始,允许Hibernate充分利用实现特殊化的属性。我们声明Hypersonic方言。我们可以参考Hibernate文档以选择数据库相应的方言。然后我们声明数据库驱动。必须保证这个驱动在应用程序的classpath上。然后我们声明数据库的用户名,数据库密码,连接数据库的URL。接下来我们通知Hibernate在日志里显示运行时生成的SQL语句。

Hibernate.cfg.xml文件必须被放在你的classpath里。
在你的程序里使用hibernate必须有下面几个步骤:
1        建立一个Hibernate configuration对象
2        使用Hibernate configuration对象来建立一个Hibernate factory对象。
3        使用Hibernate factory对象来建立一个Hibernate session对象。
4        使用Hibernate session对象来开始一个事务(可选)
5
        使用Hibernate session对象来建立、读取、更新、删除数据库里的数据
6        提交事务(可选)
7        关闭session

Hibernate最佳实践是建立和缓存Hibernate factory来提高性能。所以我们最好在第一步和第二步建立一个Struts plug-in 来在servlet context中缓存Hibernate factory。如List5所示:
Listing 5. HibernatePlugin.java

package com.strutsrecipes.hibernate.plugin;

import java.net.URL;
import javax.servlet.ServletException;

import net.sf.hibernate.HibernateException;
import net.sf.hibernate.MappingException;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.cfg.Configuration;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.action.PlugIn;
import org.apache.struts.config.ModuleConfig;

public class HibernatePlugin implements PlugIn {
   private Configuration config;
   private SessionFactory factory;
   private String path = "/hibernate.cfg.xml";
   private static Class clazz = HibernatePlugin.class;

      public static final String KEY_NAME = clazz.getName();

      private static Log log = LogFactory.getLog(clazz);

   public void setPath(String path) {
      this.path = path;
   }

   public void init(ActionServlet servlet, ModuleConfig modConfig)
      throws ServletException {

      try {
         URL url = HibernatePlugin.class.getResource(path);
         config = new Configuration().configure(url);
         factory = config.buildSessionFactory();
         servlet.getServletContext().setAttribute(KEY_NAME, factory);

      } catch (MappingException e) {
         log.error("mapping error", e);
         throw new ServletException();

      } catch (HibernateException e) {
         log.error("hibernate error", e);
         throw new ServletException();
      }

   }

   public void destroy() {
      try {
         factory.close();
      } catch (HibernateException e) {
         log.error("unable to close factory", e);
      }
   }
}

建立一个Struts plug-in只需要两个步骤。第一,建立一个类的实现 org.apache.struts.actionPlugIn(列表5)。第二,在struts-config.xml文件中定义<plug-in>标签(列表6)

让我们跳到第5步:

我们创建一个常量来储存servlet上下文属性键值的名字。这里我们选择HibernatePlugin这个类名。注意到这个常量必须是static public final的。我们使用HibernatePlugin类来取用Action中这个关键字的值(列表7)。我们定义了path属性。默认的,Hibernate-Plugin会在/hibernate.cfg.ml文件里找hibernate的配置。我们可以使用这个属性来从classpath里的其它任意路径或文件里加载Hibernate的配置。接下来,我们使用classloader来查找Hibernate配置文件,然后建立Hibernate configuration对象。我们使用Hibernate configuration对象来建立一个Hibernate factory对象,然后我们把这个Hibernate factory对象存在servlet context中。这个factory现在便可以在servlet的任意实例中使用了。

作为一个好的实践,应该在destroy方法里面关闭factory

列表6展示了struts-config应用程序。这里唯一与平常的不一样的地方是<plug-in>标签。我们在这里声明Hibernate plug-in,它将建立并且缓存Hibernate factory对象。

Listing 6. struts-config.xml

<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE struts-config PUBLIC
   "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
   "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">

<struts-config>
   <form-beans>
      <form-bean name="searchForm"type="com.strutsrecipes.hibernate.forms.SearchForm"/>
   </form-beans>

   <global-forwards>
      <forward name="search" path="/search.jsp"/>
      <forward name="searchsubmit" path="/searchsubmit.do"/>
   </global-forwards>

   <action-mappings>
      <action path="/searchsubmit"
            type="com.strutsrecipes.hibernate.actions.SearchAction"
            name="searchForm"
            scope="request"
            input="/search.jsp">
            <forward name="success" path="/element.jsp"/>
      </action>
   </action-mappings>

   <plug-in className="com.strutsrecipes.hibernate.plugin.HibernatePlugin">
      <set-property property="path" value="/hibernate.cfg.xml"/>
   </plug-in>
</struts-config>

列表7展示了用来查询一个元素的SearchForm。它非常的简单因为用户只能通过元素符号来查询。
Listing 7. SearchForm.java

package com.strutsrecipes.hibernate.forms;

import org.apache.struts.action.ActionForm;
public class SearchForm extends ActionForm {
   String symbol;

   public String getSymbol() {
      return symbol;
   }

   public void setSymbol(String symbol) {
      this.symbol = symbol;
   }
}

让我们来看一个列表8SearchAction。虽然我们也许会决定在系统架构的其它部份上使用Hibernate,在这里我们选择在Action里面使用它。我们将推迟对其它选择的讨论到我们的讨论部份。

Listing 8. SearchAction.java

package com.strutsrecipes.hibernate.actions;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import net.sf.hibernate.Hibernate;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

import com.strutsrecipes.hibernate.beans.Element;
import com.strutsrecipes.hibernate.forms.SearchForm;
import com.strutsrecipes.hibernate.plugin.HibernatePlugin;

public class SearchAction extends Action {
   private static Log log = LogFactory.getLog(SearchAction.class);

   final public static String HQL_FIND_ELEMENT =
      "from com.strutsrecipes.hibernate.beans.Element as e where e.symbol = ?";

   public ActionForward execute(
      ActionMapping mapping,
      ActionForm form,
      HttpServletRequest request,
      HttpServletResponse response)
      throws Exception {

      SearchForm searchForm = (SearchForm) form;
      Element element = null;
      List elements = null;
      SessionFactory factory = null;
      Session session = null;

      try {
         factory =
                (SessionFactory) servlet.getServletContext()
               .getAttribute(HibernatePlugin.KEY_NAME);

         session = factory.openSession();

         elements = session.find(HQL_FIND_ELEMENT,searchForm.getSymbol(),
            Hibernate.STRING);

         if (!elements.isEmpty()) {
            element = (Element) elements.get(0);
         }

      } catch (HibernateException e) {
         log.error("Hibernate error", e);
         } finally {
            log.error("Hibernate exception encountered");
         session.close();
      }

      if (element != null) {
            request.setAttribute("element", element);
            return mapping.findForward("success");
      }

      ActionErrors errors = new ActionErrors();

      errors.add(ActionErrors.GLOBAL_ERROR,
         new ActionError("error.notfound"));
      saveErrors(request, errors);
      return mapping.getInputForward();
   }
}

让我们迅速的浏览一下在SearchAction里面发生了什么。SearchAction使用SearchForm.getSymbol()方法来获取用户在查询页面输入的元素符号。Hibernate被用来查找数据库并且转换存在数据库里的数据到一个Element对象。这个Element对象将被置于请求的下下文中以够JSP页面使用。让我们跳到列表8来一行一行的详细看到底发生了什么。

首先,我们声明一个常量来搜索数据库。接着我们将form转换为SearchForm,然后我们获得Hibernate factory。重新调用已经被创建而且缓存在servlet上下文的factory。接下来,我们获得一个session。这个session包含了一个到数据库的连接。Hibernate使用在列表4的配置信息来连接到数据库。然后我们搜寻数据库。

有其它的方法来使用Hibernate搜寻数据库,但find方法无论一个查询是否有使用主键都非常的适合。注意,我们已经声明了HQL_FIND_ELEMENT常量。在这个常量里定义的SQL看起来非常的像标准SQL,但不完全。Hibernate使用的SQLHibernate私有的,OO版本的SQL语句。

深入研究Hibernate SQL(HQL)
from com.strutsrecipes.hibernate.beans.Element as e where e.symbol = ?

这个声明告诉Hibernate选择置于com.strutsrecipes.hibernate.beans包下的所有Element对象。where子句来用过滤出元素符号匹配运行时参数的所有Element对象。as e指明了e将被在HQL的任何位置作为一个别名,正如我们已经在where子句里面做的那样。我们可以看到我们从数据库里选择的是对象而不是行。Hibernate使用列表4里的信息来映射类到其对应的表。在这个例子里,表与对象之间的关系是非常接近的,但这并不是必须的。

find方法的第二个与第三个参数分别是HQL替代参数的值与类型。Hibernate参考资料里描述了替换运行时参数的其它方法。

find方法总是返回一个List对象。在这个例子中,我们获得了包含Element对象的一个List。这里我们可以确定最多一个实例被返回因为elements表在symbol列上有唯一性的索引约束。(参列表1

返回到列表8,我们拷贝List中的第一个元素的引用给变量element。为了处理任何Hibernate异常,我们记录异常并给用户显示一个找不到的消息。但是也许你想显示不同的提示信息或使用不同的异常处理。接着,我们关闭session。在finally区段关闭session保证即使在异常抛出的时候也也要关闭它。我们把Element对象保存在request上下文中并且在最后找不到符号的时候建立了一个ActionError。为了完整起见,我们展示searchjspelementjsp(列表10)。

Listing 9. Search.jsp

<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<html:html>
<body>
   <h1>Search for an Element</h1>
   <html:form action="/searchsubmit.do">
      symbol <form:text property="symbol"/>
   <html:submit value="Search"/>
   </html:form>
   <html:errors/>
</body>
</html:html>
Listing 10. Element.jsp
<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

<html:html>
   <h1>Periodic Element</h1>
   Name: <bean:write name="element" property="name"/><br>
   Symbol: <bean:write name="element" property="symbol"/><br>
   Number: <bean:write name="element" property="number"/><br>
   Mass: <bean:write name="element" property="mass"/><br>
   <html:link forward="search">Search</html:link><p>
</html:html>

在把Hibernate投入使用前,参考Hibernate文档,确保所有Hibernate所需的jar文件都在classpath中。

讨论

数据持久化是一项乏味而且费力的工作。让事情更糟糕的是,将基于对象的数据表现形式转化为关系型的或相反都必须付出相当大的努力。幸运的是,有许多优秀的对象-关系映射工具存在来减轻这种负担。在这个摘录中,我们探究了Hibernate – 方便Java程序员使用的,非常流行的开源ORM工具之一。

Hibernate是一个功能非常强大的产品,还有一些未知的功能留给你们去发现。我们简单的例子只是关于读这个行为,但是CRUD里的其它功能也是一样的简单。功能性的更新和读取指定对象一样简单,调用JavaBean setter,调用sessioncommit方法。Hibernate负责帮你生成SQL语句并且更新数据库。一个删除也是非常的简单—session.delete(element)便是所有要做的!最后建立只是需要初始化对象,调用setters方法,然后调用session.save(element)

Hibernate最佳实践推荐缓存Hibernate factory对象。我们选择通过Struts plug-in来建立并且缓存factory。你也可以选择使用其它方法在你的类里缓存它。

虽然这个摘录能很好的满足你的需要,它还有其它的一些缺点。第一,我们在Struts Action里使用了Hibernate。迁移到其它的持久层框架上便将需要我们改变每个使用HibernateAction。第二,我们的持久层紧密的与表示层连接。这种关联使我们在其它表示层机制中没有重新使用持久层逻辑的机会,例如批处理程序。

虽然有许多改进的空间,当你不需要重用表现层的时候,这个摘录还是很适合的。


版权所有:UML软件工程组织