原文出处http://www.ociweb.com/jnb/jnbNov2003.html
1.1 简介
现在已经有很多开源的数据访问API工具,他们可以简化数据访问的操作。在这之前,一般都是通过JDBC或者通过SQL字符串进行数据访问和查询。这些检索通常返回结果集,应用程序通过存储程序来匹配返回列的数据类型。从效率上来讲这种方案是非常脆弱的,因为它依赖于字符串和数据库中的表及列的代码匹配,改变数据表的名字会导致要查找所有实例的代码并修改他们。
一个比较好的数据存储方式是使用一种简单和直接的API来检索和更新数据,而不用手写实例的方式。最好的实现方式是不要依赖于JDBC、SQL字符串和结果集,使用一种中性的方式来解决。这中方案可以在Java对象和数据库实体之间建立一种自然映射,这仅仅需要少量的手工代码来维护映射,而不用删除JDBC所提供的数据检索和控制工具。
1.2 Hibernate
Hibernate是一个开源的对象关系映射工具,它减轻了对JDBC的直接操作。Hibernate使得数据检索和更新、事务处理、数据库连接池更加简单。Hibernate还可以匹配数据库结构来生成Java源代码。
XML文件包含了Hibernate有关所需数据库信息的配置数据。这些文件包含了数据库连接的细节、连接池细节、事务工厂设置和其它XML文件共同描述数据库中的表。这些XML文件的组合提供了强大的可配置性,它允许应用调整数据访问层的行为和性能,提高发现粒度水平。
1.3 代码构造器
Hibernate自带了一个代码构造器组件,它基于对象关系映射生成Java源文件并以XML配置文件的形式输出。这些文件将数据库表和字段列映射为Java类文件,匹配相应的数据类型,识别主键字段,确定实体关系(one-to-one、one-to-many, many-to-one等)。下面是一个Order XML配置文件举例:
这个XML文件告诉Hibernate使用一个叫做com.ociweb.Order的类,它拥有三个原始字段:long类型的字段id,boolean类型的字段paymentconfirmed,short类型的字段installments。另外还有三个字段用来表示和其它三个实体之间的关系:customer字段(涉及到customer对象,many-to-one关系)、orderitems字段(涉及到Orderitem对象,one-to-many关系)、delivery字段(配合Delibery对象,表示one-to-one关系)。Hibernate所有的工作就是利用配置文件中的细节来维护数据库中所有表的关系。上面的XML文件还表示“lazy(懒)”加载表间的关系实体,涉及到双向关联(使用inverse属性),当主表删除时子表关联删除(cascade属性)。Hibernate充分利用outer join fetching(使用many-to-one 或者one-to-one关系把多对象当做一个对象进行检索)减少和数据库的交互次数。上述的可配置性特征使得Hibernate非常强大。
Middlegen也是一个开源工具,它可以连接数据库服务器,检验数据库原数据发现表定义和关系。Middlegen自带了一个Hibernate插件,用来自动生成Hibernate配置文件。Middlegen和Hibernate代码生成程序可以使用Ant来运行。
1.4 服务层
构造一个用来分离应用和Hibernate的层面是一个非常好的办法,它用来降低耦合性。服务层负责Hibernate数据检索的接口实现和管理事务处理边界。我们可以确定接口模式来管理访问domain对象;它包括所需的CRUD(Create、Read、Update、Delete)方法,一个通过主键访问domain对象的方法和一个查询所有domain对象实例的方法。这种模式适用于大多数映射于应用数据库的domain对象。这种模式的接口定义如下:
这个接口可以用来实例化任何一个DomainOjbect(也就是接口存在于每个domain类)。通过类似的接口定义用来管理所有的domain对象。我们还需要一个Service Locator用来返回接口实现的实例。我们定义一个Service Locator接口,定义一个简单的方法用来接收返回的domain对象管理接口类实例。接口定义如下:
ServiceLocator可以通过Singleton模式进行访问,例如:
上面的代码非常简单也是直接使用服务层的例子。代码实现并不需要ServiceLocator和domain对象(上面讲到的OrderManager)如何实现。这个框架充分利用接口实现使得测试和并行开发更加容易。只要约束好所提供的接口,其它人的代码完全可以模仿接口实现。
Domain对象管理器的接口的每一个方法可以被拆分成独立的命令。因此一个AddCommand可以被定义为新增一个domain对象记录,同样可以实现UpdateCommand和DeleteCommand。Finder方法也可以进行统一定义。不管如何实现,创建分离的类用来实现每个domain管理器接口,隐藏domain管理器接口并调用合适的命令。下图描述了接口和实现类的规划:
下面的Command接口包括被调用domain对象管理器接口方法的实例,连同一起传递的调用参数,包括一个Hibernate Session对象。Hibernate Session接口提供了commands所需的便利的持久化和查询方法。
ServiceLocatorImpl负责返回一个domain管理器所给出的对象实例。所有的接口实现通过一个动态的代理委托方法调用相应的命令对象(All of the interfaces will be implemented by a dynamic proxy that will delegate method invocations to a corresponding command object.动态代理的翻译不知是否合适)。它有三个职责:提供一个访问任意命令对象的方法,确认类对象传入getManager方法,创建代理响应方法调用委托响应的命令。代码参见范例。
有效性检查方法validateIsInterface()和validateHasDomainObjectMgrAPI()只是用来检测所传入的类是否是接口。它包含了所有的domain对象管理器模式方法描述。那些方法的特征没有检查,这也是可以改进的地方。
另外一个值得注意的方面是ManageDelegate内部类的getSession方法,有一个ThreadSessionHolder调用用来得到一个Hibernate Session对象实例。通过提供的名字,ThreadSessionHolder关联一个当前的Hibernate Session executing线程。Hibernate Session用来检索和更新映射于数据库表记录对象,它可以缓存持久对象保持已经检索的对象实例。通过关联Session和当前executing线程,对象检索通过不同的daomain对象管理器将可以建立和消除他们之间的关系,而不用显示的共享同一个Session。这样设计的结果就是Session的范围需要以某种方式进行管理。在J2EE应用中,Session范围可以在请求级发生,所以每个domain对象管理器通过一个单独的共享请求调用一个普通Session,它在返回响应时关闭。对于一个非J2EE应用,也可以用近似的模型,只要有一个用于请求的组件即可。
1.5 FindWithNamedQuery
ManagerDelegate中的resolveCommand方法设法检索一个匹配调用方法命令对象。如果没有找到,它将检验调用方法的名字是否以find开头,因此返回一个没有实现的命令FindWithNamedQuery。这个命令利用了Hibernate的命名查询功能。这个命令具有任意的定位查询支持,它可以在domain对象接口中体现。这些查询唯一的要求就是它们的方法必须以find开头,返回java.util.Collection实例,而且命名必须和XML配置文件相符。比如说,一个finder方法可以加入到DomainObjectMgr接口中,可以通过名字进行实例化。
public Collection findDomainObjectByName(String name) throws LookupException;
Hibernate配置文件需要一个<query/>元素,名字和HQL查询细节:
<query name="com.ociweb.domain.DomainObjectMgr.findDomainObjectByName"><![CDATA[
from com.ociweb.bean.DomainObject as domainObject
where domainObject.name = ?]]>
</query>
映射文件中的查询定义必须以完整的路径命名,比如com.ociweb.domain.DomainObjectMgr.findDomainObjectByName。
1.6 事务处理
从上图中可以看到以TransactionalCommand为基类扩展出三个Command。这个类的目的是将Execution包含在一个事务之中。这个类调用它的execute()开始事务,调用抽象方法(command()),这个抽象方法在它的子类中实现。TransactionalCommand类如下:
AddCommand非常小也非常简单,也说明了使用Hibernate API非常简单。
1.7 灵活的查询
和简单的检索和持久数据一样,Hibernate提供了一个非常健壮的查询API,它支持查询字符串,命名查询和关联式构造查询。查询API通过两个接口暴露,Query接口和Criteria支持HQL(Hibernate Query Language)语法和关联查询。
1.7.1 Query
Hibernate Session接口使用工厂方法调用,可以通过传入HQL字符串创建createQuery()方法或者通过调用getNamedQuery()方法,传入已经在配置文件中定义的查询名称即可。Hibernate Query对象可以使用静态值(举例来说,FROM com.ociweb.Custormer as customer WHERE customer.name=’Als Petstore’)或者参数化的查询(举例来说,FROM com.ociweb.Custormer as customer WHERE customer.name=?)执行操作。实际上,参数化查询可以以两种方式定义,一是使用问号替代参数值,另外一种就是使用命名参数替代问号。例如:FROM com.ociweb.Customer as customer WHERE customer.name=:name。:name参数替换传入的关联值(Query.setString(String name, String value))。使用命名参数的优势是参数可以多次出现而值可以相同。
1.7.2 Criteria
Criteria接口和查询相似,允许构建一个子查询关联。就像Query接口一样,Hibernate Session也是通过工厂来操作Criteria。关联通过Expression类创建,它有创建不通表达式的方法(比如"greater than", "less than", "like", "between", "equals", "and", "not")。
1.8 综述
上文所描述的必须存在一个首先数据库定义,然后利用工具生成Hibernate XML配置文件,这个配置文件提供了数据库实体和Java对象之间的映射关系,还包括它们对应的Java对象类源码。此外,这个轻量级数据访问层可以提供一个简单而通用的,并且独立的Hibernate应用方法。它带给我们一个完整的可重用的持久层框架,它充分利用代码生成功能,所以它是一个具有生命力应用模式。这个框架非常通用,也易于扩充,所以Hibernate不需要地层的数据管理器。
1.9 结论
现在有很多工具和技术提供数据问题解决方案,比如数据驱动应用,也就是数据管理模型。这些工具中的两个工具,Hibernate和Middlegen联合自动生成实体模型的对象表示。
Zip file containing all source code for the sample framework (3.7M size)
Zip file containing HTML representation of the sample framework source code (80K size)