级别: 中级
Sunil Patil (sunilpatil@in.ibm.com)
软件工程师, IBM
2004 年 11 月
本文一步一步地指导我们在创建 Hibernate 应用程序的时候,使用 Websphere® Application Server 的连接以及事务管理。
获取本文中所使用的产品以及工具
如果您是 developerWorks 的订户,就会拥有单用户的使用许可,以使用 WebSphere Application Server 和其他 DB2®, Lotus®, Rational®, Tivoli® 产品,包括基于 Eclipse 的 WebSphere Studio IDE -- 来开发、测试、评估以及演示您的应用程序。如果您不是 developerWorks 的订户,现在可以预定。
前言
Hibernate 是一个流行的开源对象关系映射工具,运行在 Java 环境中。用基于 SQL 的模式可以把数据呈现从对象模型映射到关系数据模型,对象关系映射引用了这项技术。这也就意味着在与数据库交互时,Hibernate 提供了多级抽象方法。
Hibernate 非常灵活,并且支持多种使用方法。在一种情况下,使用 Hibernate API 的最小子集,它仅仅可以用来同数据库交互,在这种情况下,应用程序必须为自己提供连接并且管理自己的事务;例如 WebSphere Application Server 这样的中间件可以实现这些功能。在另一种情况下,即使您没有运行中间件,您也可以使用完整版本的 Hibernate,在这种情况下,您向 Hibernate 提供数据库配置信息,这时它不仅仅为您创建和管理连接,而且还可以通过把事务委派到底层数据库来管理它们。
使用 Hibernate,您需要创建 Java 类来描述数据库中的表,并且将类中的实例变量映射到数据库中的列。这时,正如您所想的那样,您可以调用 Hibernate 的方法来选择、插入、更新和删除底层表中的记录,而不是自己创建和执行查询。
Hibernate 架构有三个主要的组件:
连接管理
因为在于数据库交互时,打开以及关闭数据库的开销是非常昂贵的,您应该将您的数据库连接放在池中并且能重新使用。
事务管理
在一个批处理中执行多个查询时,会用到事务;结果可能是所有的查询都成功,或者是所有的都失败。
对象关系映射
在这部分中,特定的 Java 对象 Hibernate 来插入或者更新数据;例如,当您传递一个对象的实例到 Session.save() 方法时,Hibernate 将读取这个对象实例变量的状态,并且创建和执行必须的查询。在选择查询的情况下,将返回描述结果集的对象。
Hibernate 非常的灵活,并且提供多种途径来使用这些组件:
“Lite”架构
当您仅仅想要使用 Hibernate 对象关系映射组件时使用。在这种情况下,您需要自己实现连接以及事务管理,例如,用 WebSphere Application Server。
“Full cream”架构
当您想要应用所有三个 Hibernate 组件时使用。Hibernate 可以为您管理连接,但是您必须通过 XML 配置提供如驱动程序类名、用户名、密码以及其他一些连接信息。当管理事务的时候,您可以在 Hibernate 对象上调用开始、提交以及回滚方法。
当涉及到对象关系映射时,Hibernate 是非常好的工具,但是在连接管理以及事务管理方面,它却缺乏必要的性能和能力。幸运的是,我们可以将 Hibernate 的对象关系映射与 WebSphere Application Server 的连接和事务管理结合起来,创建强大的应用程序。
本文假设您已经具备了 Hibernate、Enterprise JavaBean(EJB)组件以及 Struts 的基础知识。如果您刚刚接触 Hibernate,请参考没有对象的对象关系映射,这篇文章讨论了怎么用 Hibernate 来实现简单的插入、更新以及删除操作。
关于示例应用程序
为了实现本文的目的,我们将创建一个简单的 Struts Web 应用程序,它允许用户来向 Contact 表中插入新的记录。我们的示例应用程序将使用 AddContact.jsp 来接收用户的输入。AddContactAction.java 是活动类,它将用 Hibernate 来插入一个新的联系人到数据库中去。如果联系人成功插入,这时用户被重定向到 success.jsp,这个页面将显示最新插入联系人的详细信息。如果遇到了问题,将显示 failure.jsp 页面。
图 1. 实例应用程序插入页面
使用 Hibernate
开始时,我们将创建实例应用程序,这个程序使用 Hibernate 自己的连接池以及事务管理(同样请参考 "full cream" 架构):
创建一个 Struts 应用程序,既可以使用 WebSphere Studio Application Developer(以后称作 Application Developer),也可以通过导入包含在 Struts 发布里面的 struts-blank.war 来实现。为示例应用程序创建必须的 JSP 页面和活动类,您也可以从下载文件 sample1.zip中导入。
从 Hibernate 网站上下载 Hibernate 二进制版本:复制 hibernate2.jar 以及 JAR 库文件(dom4j.jar,xmlapi.jar及其他),到 WEB-INF/lib 目录中。
创建 Contact.java 类,并且为 Contact 中的每一列创建一个成员变量。每个字段都应该创建 Getter 和 Setter 方法。同样需要创建 contact.hbm.xml 文件,这个文件将把 Contact.java 的实例变量映射到 Contact 表的列上。
在您的源代码根目录中创建 hibernate.cfg.xml。这个文件为 Hibernate 保存应用程序级别的配置信息,正如清单 1 所示的那样。
清单 1
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">
COM.ibm.db2.jdbc.app.DB2Driver</property>
<property name="hibernate.connection.url">jdbc:db2:SAMPLE</property>
<property name="hibernate.connection.username">db2admin</property>
<property name="hibernate.connection.password">db2admin</property>
<property name="hibernate.connection.pool_size">10</property>
<property name="show_sql">true</property>
<property name="dialect">net.sf.hibernate.dialect.DB2Dialect</property>
<property name="hibernate.hbm2ddl.auto">create-drop</property>
<!-- Mapping files -->
<mapping resource="contact.hbm.xml"/>
</session-factory>
</hibernate-configuration>
在这个文件中:
您将注意到如果需要用纯 JDBC 打开连接,我们必须在这里指定同样的信息:
hibernate.connection.driver_class 是我们决定要使用的 JDBC 驱动器类。
hibernate.connection.password 和 hibernate.connection.username 是数据库的信任状。
hibernate.connection.url 是用来连接数据库的 JDBC URL。
dialect 属性决定了在获取查询的时候用到的专用语。Hibernate 支持所有流行的关系数据库管理系统(RDBMS)的专用语,例如 DB2 或者 Oracle™。因此,例如,如果您在开发过程中使用 Oracle,并且在生产的时候想要转移到 DB2 上,仅仅需要改变 hibernate.cfg.xml 文件。
hibernate.hbm2ddl.auto 可以用来指示 Hibernate,当我们初始化它时,从映射文件为我们的应用程序创建 DDL,并且在数据库中创建这些表和序列。create-drop 的值意味着当 Hibernate 关闭时将删除由 Hibernate 创建的表。
contact.hbm.xml 是 Contact 标的映射文件。如果您有多个表,您即可以创建多个映射文件(每个表一个),以及添加多个 <mapping> 元素,也可以在一个文件中创建多个映射,并且添加一个 <mapping> 元素。在我们的应用程序接下来的部分,我们将读取由用户输入的值,并且向 Contact 表中插入一条新的记录。AddContactAction.java 类执行这个方法:
清单 2
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
//Create new instance of Contact and set values in it by reading them from form object
Contact contact = new Contact();
contact.setFirstName((String)contactForm.get("firstName"));
session.save(contact);
// Actual contact insertion will happen at this step
session.flush();
session.close();
通过指定 new Configuration().configure().buildSessionFactory() 来初始化 Hibernate。Hibernate 会设法在根目录中定位 /hibernate.cfg.xml,如果发现的话就会加载它,并且提供给我们一个到 SessionFactory 的引用,我们可以用它来同 Hibernate 交互。会话(session)指示了一个逻辑上的交互;在一个会话中您可以激活多个查询。
为了插入联系人,创建一个 Contact 类的新实例。通过调用 setter 方法来用输入的联系人信息设定各个字段的值,这时把这个联系人对象传递给 Hibernate,调用 session.save() 来保存。Hibernate 将负责生成和执行插入查询。当联系人被插入时,Contact 类中 ID 字段的值将和 Contact 表中最新插入的记录主键值相同。如果出现任何问题,Hibernate 将会抛出异常。
请您确认复制 JDBC 驱动器 JAR 文件到 WEB-INF/lib 目录中,否则将出现异常。
为了尝试这个示例,请下载 sample1.zip 文件。
通过 WebSphere Application Server 连接池来使用 Hibernate
基于上面我们已经完成的内容,我们可以改进示例联系人管理应用程序,它包含 WebSphere Application Server 一些关键特性的使用。
首先,在开发上面的应用程序时,我们用了 Hibernate 中的连接池。这个连接池算法有点不成熟,并且并不能用在生产环境里面,您应该考虑使用第三方的连接池比如 Apache's DBCP。我们将更改我们的应用程序,来使用 WebSphere Application Server 的连接池:
为了使用 WebSphere Application Server 连接池,需要创建一个 JDBC 数据源并设定 JNDI 名,比如 jdbc/ds1。(参考 WebSphere Application Servers Information Center 来查看配置数据源的细节。)在 hibernate.cfg.xml 文件中,移除 hibernate.connection.driver_class、URL 以及其他属性,并且添加这一行:
<property name="hibernate.connection.datasource">jdbc/ds1</property>
在 AddContactAction.java 执行方法中,我们每次都创建一个 SessionFactory 对象,但这并不是一个好主意,因为使用 SessionFactory 创建由所有应用程序线程共享的线程安全对象是非常昂贵的。我们应该仅仅在应用程序启动的时候创建 SessionFactory 对象。我们可以创建一个 SessionContextListener 类并且通过配置 SessionFactory 来实现它,也可以通过更好的办法,创建一个配置 SessionFactory 的 Struts 插件来实现,如清单 3 所示。
清单 3
public class HibernateStrutsPlugIn implements PlugIn{
public void init(ActionServlet arg0, ModuleConfig arg1)
throws ServletException {
try {
SessionFactory sessionFactory = new
Configuration().configure().buildSessionFactory();
} catch (HibernateException e) {
System.out.println("Error in
configuring SessionFactory");
e.printStackTrace();
}
}
// Other methods
}
为了创建 Structs 插件:
创建实现 PlugIn 接口的 HibernateStrutsPlugin 类。在这个类的 init() 方法中,调用 Configuration().configure().buildSessionFactory() 方法。
在 struts-config.xml 中为新的插件类添加条目,既可以通过使用 Application Developer(图 2)来实现,也可以通过手动编辑 struts-config.xml 并且在末端添加清单 4 中的那一行。
图 2. Struts Configuration File Editor
清单 4
<struts-config>
<plug-in className="com.sample.util.HibernateStrutsPlugIn">
</plug-in>
</struts-config>
我们将对 hibernate.cfg.xml 文件做一些修改,使重新使用 SessionFactory 更加得容易。通过添加下面的条目,我们告诉 Hibernate 当我们调用 Configuration().configure() 时,它应该创建 SessionFactory 的一个实例,并且将它同 HibernateSessionFactory 绑定在一起。当把 SessionFactory 绑定到 JNDI 时,Hibernate 将在初始化 InitialContext 时使用 hibernate.jndi.url 以及 hibernate.jndi.class 的值。如果这些值没有指定,将使用默认的 InitialContext 值。
<property name="hibernate.session_factory_name"> HibernateSessionFactory</property>
我们将更新我们的应用程序并且添加两个新表:Phone 和 Address。同样我们必须更改 AddContact.jsp 来添加这两个新的输入。我们想要应用程序完成的功能是,如果用户指定了电话号码或者是地址,一条新的记录将添加到合适的表中。另外,所有这些都应该是一个事务的一部分,也就是说,在添加联系人和电话号码以后,在添加地址是突然出现了错误,这时所有与该事务关联活动都应该回滚,并且将向用户显示出错页面。
创建 Address.java 和 Phone.java 来描述 Address 以及 Phone 表,并且通过 address.hbm.xml 和 contact.hbm.xml 来映射。在 hibernate.cfg.xml 中为这两个文件添加 <mapping> 元素。
清单 5
try {
Context ctx = new InitialContext();
Object obj = ctx.lookup("HibernateSessionFactory");
sessionFactory = (SessionFactory) obj;
} catch (NamingException e) {
e.printStackTrace();
}
session = sessionFactory.openSession();
Transaction contactInsertTransaction =
try{
session.beginTransaction();
//Create new instance of Contact and set values in it by reading them from form object
Contact contact = new Contact();
// Prepare contact object
session.save(contact);
//Check if value for phone is not null if yes then insert it in db
String phoneStr = (String) contactForm.get("phone");
if (phoneStr.length() != 0) {
//Code for inserting new phone here
}
//If you uncomment this line you will find out contact gets inserted
//even if there is some problem afterwards/*if(true)
throw new RuntimeException("Test transaction exception"); */
// Code for inserting new Address
contactInsertTransaction.commit();
} catch (HibernateException e) {
contactInsertTransaction.rollback();
return mapping.findForward("failure");
}finally{
session.flush();
session.close();
}
由于这些改变,我们已经:
改变了执行方法,从 JNDI 中获得 SessionFactory 引用。
改变了执行方法,使联系人、电话和地址的插入操作运行在 Hibernate 自己管理的事务中。
如果所有的东西都运行正常,我们将在关闭会话以前提交事务。如果出现了任何问题,我们将调用事务对象中的 rollback() 方法。
利用 WebSphere Application Server 事务管理来使用 Hibernate
第二,现在我们想要更改我们的应用程序,使非 Web 客户端也可以访问。为了实现这个目标,我们将创建一个无状态会话 Bean,并且对外公开一个 addContact() 方法,并且,把所有的业务逻辑从 AddContactAction 类中移动到 ContactBean.java 中。
因为移动到无状态会话 Bean 的缘故,我们必须初始化 Hibernate 的方法。对于绑定对象来说,JNDI 一旦可用,就应该初始化 Hibernate,并且应该把 SessionFactory 对象绑定到 JNDI。当应用程序服务器已经完成了启动以后,WebSphere Application Server 就会允许我们注册回滚通知。
在 Application Developer 中创建一个名为 sampleListener 的 Java 项目,并且在这个项目中,创建 HibernateJNDIListener.java,如清单 6 所示。
清单 6
public class HibernateJNDIListener implements CustomService,
NotificationListener {
public void initialize(Properties arg0) throws
Exception {
NotificationFilterSupport filter = new
NotificationFilterSupport();
filter.enableType(NotificationConstants.TYPE_J2EE
_STATE_RUNNING);
ObjectName target = new
ObjectName("WebSphere:*,type=Server");
AdminServiceFactory.getAdminService().addNotificationListenerExtended(target,this,filter,null);
}
public void handleNotification(Notification arg0, Object arg1) {
try {
TestConfiguration.init();
System.out.println("Hibernate configured sucessfully");
} catch (HibernateException e) {
e.printStackTrace();
}
}
}
使用 WebSphere Application Server 中的管理控制台,创建并且注册名为(HibernateJNDIListener)的类,这个类实现了 CustomService 接口。
图 3. WebSphere 管理控制台
当应用程序服务器或者节点启动时,这个类也将启动,并且 initialize() 方法中获取控制权,该方法将注册一个 HibernateJNDIListner 实例用来接收 TYPE_J2EE_STATE_RUNNING 通知。HibernateJNDIListner 类实现了 NotificationListener 来指明它现在已经可以接收通知。当绑定对象可以使用 WebSphere Application Server 中的 JNDI 时,它将调用 HibernateJNDIListener 中的 Notification() 处理方法。在这个方法中,我们将初始化 SessionFactory 并且将其在 JNDI 中绑定。
如果要使其工作正常,所有的 Hibernate 映射类和 .hbm.xml 文件需要与 sampleListener.jar 在同一个级别上。创建一个新的 Java 项目,sampleDB,并且将 com.sample.db Java 文件移动到这个项目中,.hbm.xml 和 hibernate.cfg.xml 文件也是一样处理。
将 sampleDB.jar 和 sampleListener.jar 复制到 appserver\lib 中,以下文件也一样处理:
hibernate2.jar
do dom4j.jar
cgilib-full-2.0.1.jar
xml-apis.jar
commons-collection-2.1.jar
ecache-0.7.jar.
启动 WebSphere Application Server,并且检查 systemout.log,找到表明 Hibernate 配置成功的消息。
现在,我们将使用 WebSphere Application Server 事务管理来更改我们的应用程序,这样我们添加的联系人信息将变为现有事务的一部分。例如,在插入新的联系人信息以后,我们可能想要发送一个确认信给用户。如果我们已经有了一个消息驱动 Bean 来发送 e-mail,这个附加的信息应该是一个事务中的一部分。我们可以通过在 hibernate.cfg.xml 中添加这个条目,来与 WebSphere Application Server 事务管理结合在一起。
<property name="net.sf.hibernate.transaction.JTATransactionFactory"> net.sf.hibernate.transaction.WebSphereTransactionManagerLookup</property>
在这个例子中,我们不会手动指定事务的边界。WebSphere Application Server 在它调用 addContact() 方法时将开始一个新的事务。如果该方法正常完成了执行操作,没有抛出任何错误,这时事务将被提交,否则将回滚。
结束语
当使用 Hibernate 的时候,如果您没有应用服务器,就应该使用完整的架构;例如,您有一个要与数据库交互的 SWING 应用程序。在这种情况下 Hibernate 可以节省您很多低级别的消耗。然而,如果您运行了中间件,您可以使用 WebSphere Application Server 中配置的数据源,因为它为性能做了优化,并且您可以在部属的多个应用程序中共享同样的连接池。
对于企业应用程序,您可能有数据库、消息系统以及各种 EIS 系统,并且您想所有需要的系统都可以运行在一个事务中。在这种情况下,您应该在 Hibernate 中使用 WebSphere Application Server 提供的 Java Transaction API(JTA);当 Hibernate 管理事务时,它通过把调用委托给底层数据库来实现这一点,但是当它是跨系统而不是数据库时,将会出现问题。
参考资料
您可以参阅本文在 developerWorks 全球站点上的 英文原文。
用 WebSphere Studio V5 来编写简单的 struts 应用程序
Modifying an Application to use DynaActionForm
使用 Hibernate 将您的 Java 对象持久化到 IBM DB2 Universal Database 中
下载
Name
Size
Download method
sample1.zip
5.0 MB
|HTTPsample2.zip
5.0 MB
|HTTPsample3.zip
5.6 MB
|HTTP关于作者
Sunil Patil 是一个在 IBM 印度公司 Lotus 部门的一位软件工程师。他具有四年的 J2EE 经验。