引言
让我们面对现实吧,如果您在企业应用程序中以手工方式编写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项目。从 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(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文件。
分析映射文件该插件生成了一个文件,内容如下:
/p>
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping.dtd">
您会注意到,在DOCTYPE中定义的文档类型定义(document type definition,DTD)文件存在于指定的URL(即 Hibernate将总是首先从那里引用它。因为该映射文件在hibernate.jar文件中,所以将会在您的类路径中,因此,您将不必担心需要手工将它导入。这个文件用来定义XML文件中允许的有效标记。
标记是这个XML文件中的基本标记。这个标记有两个可选的属性,但我们的应用程序不需要它们。请参阅Hibernate文档,了解关于这些特征的更多信息。
元素代表一个持久的Java类。它有一个name(名称)属性,这个属性引用我们正在映射的Java类的全限定(用点隔开)类名。它还有一个table(表)属性,这个属性引用我们的类所映射到的数据库表(即员工表)。该插件没有为我们生成table属性,所以我们将在下一节中添加它。
元素还必须包含一个元素,用来指定哪个字段是该数据库表的主键,并指定如何生成该主键。我们同样将在下一节中讨论这个问题。
Hibernate文档提到:“元素声明了持久的类的JavaBean样式属性”。该属性主要用于基本类型或字符串类型的实例变量。在我们的示例中,员工的名字必须用一个元素来代表。
many-to-one(多对一)元素用于“与另一个持久类的普通关联……。关系模型是一种多对一(many-to-one)关联。(它实际上就是一个对象引用。)”在我们的示例中,Employee类与Department类之间存在many-to-one关联。
修改映射文件我们的employeeId实例变量将映射到数据库中的EID列。因为EID将成为我们的主键,所以我们需要除去为employeeId生成的property元素,并且用id元素替代它:
元素的name属性引用我们的类中的JavaBean参数的名称。column属性引用数据库中我们映射到的列。generator元素的class属性被设置成“assigned”,意思是我们打算自己指定对象中主键的值。对于自动生成主键,还有其它一些Hibernate选项可供选择。您可以在Hibernate文档中找到关于这些选项的更多信息。
现在,我们需要进一步修改该插件所生成的代码,并着手定义每一个和标记将映射到哪些列:
这样,修改后的文档内容如下:
/p>
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping.dtd">
我们将对Department.hbm.xml做本质上一样的处理。最后得到的结果如下:
/p>
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping.dtd">
创建数据源和会话
我们需要将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的主要开发人员非常活跃,他们几乎会解答所有贴子。