引言
让我们面对现实吧,如果您在企业应用程序中以手工方式编写 SQL 语句的代码,那么您将花费大量的开发时间去更新和维护持久性层。要是能够方便地将现有 Java™ 对象持久保存到关系数据库(如 IBM® DB2® Universal Database™,UDB)岂不是很好?
幸运的是存在这样的办法。对象/关系(Object/Relational,O/R)映射工具是一些成熟的工具,它们能够将对象映射到关系数据库中的行,从而不再需要复杂的持久层,并且使开发人员只需编写最少的 SQL,在多数情况下不需编写任何 SQL。
Hibernate 是按照 LGPL 许可证发布的开放式源代码应用程序,它是“用于 Java 的超高性能的对象/关系持久性和查询服务”。在本文中,我们将为您说明如何使用 Hibernate 方便地(一行 SQL 代码都不用写)将 Java 对象持久保存到 DB2 数据库中。
为了演示 Hibernate 的工作机制,我们将创建一个简单的类模型,它由两个类组成:Employee 和 Department。为了简单起见,一名员工(employee)有一个部门(department),而部门没有到员工的引用。有关类图参阅图 1。
图 1. Employee/Department 类图
我们将使用 WebSphere® Studio 5.0 的 Application Developer 配置和一个称为 Hibernator 的插件来开发应用程序,Hibernator 能简化一些配置 Hibernate 的工作。
设置 WebSphere Studio 和 Java 项目
首先,让我们花些时间准备实验所需的要素:
在 WebSphere Studio 中创建一个新的 Java 项目。
从 http://sourceforge.net/projects/hibernate/ 下载 Hibernate。在撰写本文时,Hibernate 的版本是 1.2.4。
解压缩从 SourceForge 得到的 Hibernate 压缩文档,将其中的内容解压缩到一个临时目录。
将名为 hibernate.jar 的 JAR 文件导入到项目的基本目录。
从 Hibernate 分发包中的 lib 目录导入以下 jar 文件:
commons-lang.jar
commons-collections.jar
commons-logging.jar
xml-apis.jar
xerces.jar
将 hibernate.jar 添加到您的 Java 构建路径(Java Build Path)(用鼠标右键单击 project -> Properties -> Java Build Path -> Libraries -> Add JARs...,然后指向您所导入的 hibernate.jar)。
从 SourceForge(http://sourceforge.net/projects/hibernator/)下载 Hibernate Eclipse 插件。您会看到,这个插件使得同步现有的 Java 类和定义我们的 O-R 映射规则的 Hibernate 映射文件更容易。在撰写本文时,该插件的版本是 0.9.3。
将这个插件的压缩文件解压缩到 [WSAD 5 InstallDir]\eclipse\plugins\ 目录。
要与 DB2 UDB 进行交互,我们还需要导入 DB2 JDBC 数据库驱动程序。导入缺省情况下位于 C:\program files\IBM\SQLLIB\java\ 目录的 db2java.zip 文件。确保将 db2java.zip 添加到类路径中。
我们已经在本文所附带的代码中包含了一些 JUnit 测试。如果要运行这些测试,需要导入缺省情况下位于 [WSAD5InstallDir]\eclipse\plugins\org.junit_3.7.0 目录的 junit.jar 文件。
我们必须重新启动 WebSphere Studio,以便它注册我们所添加的插件。
配置 hibernate.properties
为了促进与 DB2 UDB 的通信,我们需要让 Hibernate 知道一些我们的数据库属性。为此,我们将创建一个名为 hibernate.properties 的文件,这个文件必须出现在我们应用程序的类路径中。在我们的示例中,我们将把这个属性文件放到项目的基本目录中,这个目录包含在类路径中。您可能需要针对您自己的数据库设置更改下列属性值。
hibernate.connection.driver_class = COM.ibm.db2.jdbc.app.DB2Driver
hibernate.connection.url = jdbc:db2:empl
hibernate.connection.username = db2admin
hibernate.connection.password = db2admin
hibernate.dialect = cirrus.hibernate.sql.DB2Dialect
如果您曾经不得已编写过检索 JDBC 连接的代码,那么前四个参数对您来说应该是很熟悉的。hibernate.dialect 属性告诉 hibetnate 我们在使用 DB2“方言”(dialect)。设置这个“方言”允许 Hibernate 在缺省情况下启用一些特定于 DB2 的功能,这样您就不用手工设置它们了。
创建数据库模式
上面的属性文件引用了我们还未创建的名为 empl 的数据库。让我们继续向前,完成这项工作。让我们转到 DB2 命令行处理器:
db2=> create db empl
db2=> connect to empl user db2admin using db2admin
此外,我们需要用到一些表:
db2=> create table Employee (
EID int NOT NULL PRIMARY KEY,
FirstName varchar(30) NOT NULL,
LastName varchar(30) NOT NULL,
Email varchar(30) NOT NULL,
ManagerEID int, DepartmentID int NOT NULL)
db2=> create table Department(
DepartmentID int NOT NULL PRIMARY KEY,
Name varchar(30) NOT NULL,
City varchar(30) NOT NULL,
State varchar(30) NOT NULL)
创建要被映射的 JavaBeans
回想一下,您阅读本文目的正是要将 Java 对象映射到数据库。那我们就来定义这些对象:
创建新的类 Department:
package com.ibm.hibernate_article;
public class Department
{
private int departmentID;
private String name;
private String city;
private String state;}
创建新的类 Employee:
package com.ibm.hibernate_article;
public class Employee
{
private int employeeId;
private String firstName;
private String lastName;
private String email;
private Employee manager;
private Department department;}
对于我们新创建的两个类:
在大纲视图中,用鼠标右键单击类名。
选择 Generate Getter and Setter...。
选择 All。
单击 OK。
请记住,所有的 setter 和 getter 都必须存在,不过它们的可视性无关紧要。这样,如果您需要维护不变的对象,那么您可以在构造该对象期间设置其状态,并且将所有 setter 方法设为私有。除了所创建的任何其它构造器之外,您还必须提供一个缺省构造器;不过,缺省构造器的可视性也可以设为私有。setter 和 getter 方法以及缺省构造器之所以必须存在,是因为 Hibernate 遵循 JavaBeans 语法并且使用这些方法特征符来在 O/R 映射期间持久保持数据。
创建 XML 映射
既然 Java 类和数据库表已经准备就绪,现在我们需要定义 O/R 映射。Hibernate 通过读取包含映射定义的 XML 文件来实现这个目标。
让我们首先为 Employee 类创建映射。
在编辑器中打开 Employee.java 文件。
单击 Window -> Show View -> Other -> Hibernator -> Hibernator(请参阅图 2)。
图 2. 显示 Hibernator 视图
在 Hibernator 视图中用鼠标右键单击,然后单击 Save(图 3)。
图 3. 用 Hibernator 插件生成 O/R 映射 XML 文件
我们还必须做一些编辑工作,但该视图实际上没有提供编辑功能,它只是生成 .hbm.xml 文件。这样,我们将需要在常规的文件编辑器中打开 Employee.hbm.xml 文件。
分析映射文件
该插件生成了一个文件,内容如下:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping.dtd">
<hibernate-mapping>
<class name="com.ibm.hibernate_article.Employee" table="employee">
<many-to-one name="department"/>
<property name="email"/>
<property name="employeeId"/>
<property name="firstName"/>
<property name="lastName"/>
<many-to-one name="manager"/>
</class>
</hibernate-mapping>
<!-- parsed in 0ms -->
您会注意到,在 DOCTYPE 中定义的文档类型定义(document type definition,DTD)文件存在于指定的 URL(即 http://hibernate.sourceforge.net/hibernate-mapping.dtd)。如果该映射文件存在于类路径中,那么 Hibernate 将总是首先从那里引用它。因为该映射文件在 hibernate.jar 文件中,所以将会在您的类路径中,因此,您将不必担心需要手工将它导入。这个文件用来定义 XML 文件中允许的有效标记。
<hibernate-mapping> 标记是这个 XML 文件中的基本标记。这个标记有两个可选的属性,但我们的应用程序不需要它们。请参阅 Hibernate 文档,了解关于这些特征的更多信息。
<class> 元素代表一个持久的 Java 类。它有一个 name(名称)属性,这个属性引用我们正在映射的 Java 类的全限定(用点隔开)类名。它还有一个 table(表)属性,这个属性引用我们的类所映射到的数据库表(即员工表)。该插件没有为我们生成 table 属性,所以我们将在下一节中添加它。
<class> 元素还必须包含一个 <id> 元素,用来指定哪个字段是该数据库表的主键,并指定如何生成该主键。我们同样将在下一节中讨论这个问题。
Hibernate 文档提到:“<property> 元素声明了持久的类的 JavaBean 样式属性”。该属性主要用于基本类型或字符串类型的实例变量。在我们的示例中,员工的名字必须用一个 <property> 元素来代表。
many-to-one(多对一)元素用于“与另一个持久类的普通关联……。关系模型是一种多对一(many-to-one)关联。(它实际上就是一个对象引用。)”在我们的示例中,Employee 类与 Department 类之间存在 many-to-one 关联。
修改映射文件
我们的 employeeId 实例变量将映射到数据库中的 EID 列。因为 EID 将成为我们的主键,所以我们需要除去为 employeeId 生成的 property 元素,并且用 id 元素替代它:
<id name=" employeeId " column="EID">
<generator class="assigned"/>
</id>
<id> 元素的 name 属性引用我们的类中的 JavaBean 参数的名称。column 属性引用数据库中我们映射到的列。generator 元素的 class 属性被设置成“assigned”,意思是我们打算自己指定对象中主键的值。对于自动生成主键,还有其它一些 Hibernate 选项可供选择。您可以在 Hibernate 文档中找到关于这些选项的更多信息。
现在,我们需要进一步修改该插件所生成的代码,并着手定义每一个 <property> 和 <many-to-one> 标记将映射到哪些列:
<property name="email" column="Email"/>
<property name="firstName" column="FirstName"/>
<property name="lastName" column="LastName"/>
<many-to-one name="department" column="departmentID"/>
<many-to-one name="manager" column="managerEID"/>
这样,修改后的文档内容如下:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping.dtd">
<hibernate-mapping>
<class name="com.ibm.hibernate_article.Employee" table="employee">
<id name="employeeId" column="EID">
<generator class="assigned"/>
</id>
<property name="email" column="Email"/>
<property name="firstName" column="FirstName"/>
<property name="lastName" column="LastName"/>
<many-to-one name="department" column="departmentID"/>
<many-to-one name="manager" column="managerEID"/>
</class>
</hibernate-mapping>
我们将对 Department.hbm.xml 做本质上一样的处理。最后得到的结果如下:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping.dtd">
<hibernate-mapping>
<class name="com.ibm.hibernate_article.Department" table="department">
<id name="departmentID" column="DepartmentID">
<generator class="assigned"/>
</id>
<property name="city" column="City"/>
<property name="name" column="Name"/>
<property name="state" column="State"/>
</class>
</hibernate-mapping>
创建数据源和会话
我们需要将 XML 映射装入到某种对象表示中,这样 Hibernate 才可以使用它们。具体做法是创建 cirrus.hibernate.Datastore 类的一个实例。然后我们告诉这个 Datastore 实例为给定的类存储映射信息,办法是调用 storeClass 方法并给这个方法提供给定类的 Class 对象。storeClass 方法知道使用全限定类名在同一个包内查找相应的 .hbm.xml 映射文件。
在拥有 Datastore 对象之后,我们需要用它来构建 SessionFactory。这个 SessionFactory 将负责创建一些 Session 对象。Hibernate 文档将会话定义为“单线程的、存在时间短的对象,代表应用程序与持久存储之间的对话”。会话包装 JDBC 连接,充当 Transaction 对象的工厂,并管理应用程序中的持久对象。会话可以跨越多个事务,所以它不必像事务那样代表一个工作原子单元。
让我们创建一个静态的初始化程序,它将负责创建 SessionFactory 对象。当第一次引用该类时,这个静态的初始化程序将装入一次。在静态地装入这个初始化程序之后,我们将不再需要重新装入 Employee 和 Department 类映射。
private SessionFactory sessionFactory;
static {
try
{
Datastore ds = Hibernate.createDatastore();
ds.storeClass(Employee.class);
ds.storeClass(Department.class);
sessionFactory = ds.buildSessionFactory();
}
catch (Exception e)
{
throw new RuntimeException("couldn''t get connection");
}
}
在上述代码中,Datastore 对象通过调用 buildSessionFactory 方法获取 SessionFactory 的一个实例。如果没有给 buildSessionFactory 方法提供任何参数,它会在运行时类路径中查找缺省属性文件(即我们前面创建的 hibernate.properties 文件)查找。另一种办法是,如果在代码中需要对 properties 进行这种控制,可以将 Properties 对象传递给 buildSessionFactory 方法。
在该静态的初始化程序初始化 SessionFactory 对象之后,我们就可以调用 openSession() 方法。这个静态方法将为我们返回一个新的会话对象。如果您调用 openSession 时没有提供参数,SessionFactory 将为您自动管理 Connection。许多连接参数(如池的大小、语句高速缓存以及空闲时间)都可以通过 hibernate.properties 文件中的参数(或提供给 SessionFactory 的 properties 对象)进行配置。有关更多详细信息,请参阅 Hibernate 文档。
如果您的程序已经有一个现有的连接管理基础结构,那么您可以给 openSession(Connection con) 方法提供一个连接,Hibernate 将使用您提供的连接。
操作数据库对象
这一节描述如何写到数据库中,如何从数据库装入对象以及如何更新和查询数据库。
写到数据库
要写到数据库,我们将使用 SessionFactory 对象打开一个新的会话。然后,我们将创建想持久保存的对象并将它保存到会话中。接着,我们刷新(flush)会话,在连接上调用提交(commit),最后关闭(close)会话。
刷新会话会强制 Hibernate 把内存中的数据和数据库同步起来。Hibernate 将定期自动刷新,但不能保证在什么时候进行。于是,我们将内存中的数据显式刷新到数据库,从而确保数据立即写入数据库。
在关闭会话之前,还必须确保提交了数据库连接。
Session session = sessionFactory.openSession();
department = new Department();
department.setCity("Austin");
department.setState("TX");
department.setName("IBM Global Services");
department.setDepartmentID(211);
session.save(department);
session.flush();
session.connection().commit();
session.close();
从数据库装入对象
装入对象就是使用对象的标识将对象调回到内存中的过程。这与我们在查询数据库中讨论的查询对象不同。
为了从数据库装入对象,我们同样需要一个会话。我们还需要想装入的对象的主键。就我们前面所编写的示例来说,如果想将 Department 装回到对象中,我们可以用表示 Department 的 Class 对象来调用 session.load 方法,我们的主键是“211”。
Session session = sessionFactory.openSession();
Department dept = (Department) session.load(Department.class, new Integer(211));
session.close();
更新数据库
要更新一个对象,可以在创建该对象的那个会话中进行,也可以在一个完全不同的会话中进行。在同一个会话中更新对象很容易;只要修改对象的状态就行了。要在不同的会话中更新对象,则必须装入(或查询)该对象,然后更新它。
同一个会话
session.save(department);
session.flush();
department.setName("newName");
session.flush();
不同的会话
//first session
Department department = new Department();
department.setDepartmentId(211);
department.setName("someName");
.
. // set other stuff on department
.
session.save(department);
session.flush();
session.connection().commit();
session.close();
//later session
laterSession = sessionFactory.openSession();
Department dept = (Department) session.load(Department.class, new Integer(211));
dept.setName("aDifferentName");
laterSession.flush();
session.connection().commit();
laterSession.close();
查询数据库
查询数据库有几种方式。最简单的方式是使用 session.find 方法。您必须使用 Hibernate 简单但却功能强大的面向对象的查询语言来给 session.find 提供一个查询。下面的示例演示了一个非常简单的查询。如果要进行更复杂的查询,请参阅 Hibernate 文档获取更多详细信息。
Department department = new Department();
department.setDepartmentId(211);
department.setName("someName");
.
. // set other stuff on department
.
session.save(department);
List list = session.find
("from dept in class com.ibm.hibernate_article.Department where dept.city=''Austin'' ");
进行测试
既然我们有了会话对象并且知道了如何进行一些操作,那我们就可以在我们简单的对象模型上编写一些 CRUD 测试来看看 Hibernate 的实际运行情况。这是一种比 JUnit 测试好得多的测试方法。您可以随意看看本文所附的源代码中的 HibernateTest.java 测试用例。
结束语
在本文中,我们仅仅粗浅地讨论了如何使用 Hibernate。我们在几个 POJO(Plain Old Java Object,传统的 Java 对象)的上下文中介绍 Hibernate API。不过,请注意广泛的 Hibernate API 涵盖了更高级的主题,如 one-to-many(一对多)映射、事务以及集合。既然您已“涉足”Hibernate,那应该能更自在地探索如何在编程工作中使用开放式源代码产品了吧。
我们在一些项目中使用了 Hibernate,使用时遇到了一些障碍。我们发现,SourceForge 上的 Hibernate 论坛对于解答我们的问题是不可或缺的。在该论坛上,Hibernate 的主要开发人员非常活跃,他们几乎会解答所有贴子。
论坛的网址为:
http://sourceforge.net/forum/forum.php?forum_id=128638
Hibernate Web 页面的网址:
http://hibernate.bluemars.net/
关于作者
Javid Jamae 是一位专攻企业应用程序和软件方法学咨询的独立软件顾问。您可以通过 javidjamae@yahoo.com 与 Javid 联系。
Kulvir Singh Bhogal 的工作角色是 WebSphere 顾问,在全美实施 IBM 的电子商务战略。您可以通过 kbhogal@us.ibm.com 与 Kulvir 联系。