无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层

当您自以为已经了解了所有开发工具时,肯定又会冒出一个新的工具。在本文中,developerWorks 的固定撰稿人 Rick Hightower 用一个真实世界的例子向您介绍两个最激动人心的企业新技术。Hibernate 是一个对象关系映射工具,而 Spring 是一个 AOP 框架和 IOC 容器。Rick 介绍了如何结合这两者,为企业应用程序构建一个事务持久层。
<!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --> <!--END RESERVED FOR FUTURE USE INCLUDE FILES-->

如果关心开发人员的最新热点,那么您可能听说过 IOC (控制倒置,Inversion of Control)容器和 AOP (面向方面编程)。不过,像许多开发人员一样,您可能不清楚在自己的开发工作中如何使用这些技术。在本文中,通过具体介绍使用 Hibernate 和 Spring 在企业应用程序中构建一个事务持久层,您会认识到这些技术。

Hibernate 是 Java 平台上的一种流行的、容易使用的开放源代码对象关系(OR)映射框架。Spring 是一个 AOP 框架和 IOC 容器。这两种技术一起提供了本文中介绍的开发工作的基础。将使用 Hibernate 把一些持久性对象映射到关系数据库中,用 Spring 使 Hibernate 更容易使用并提供声明性事务支持。由于为示例类编写测试代码时使用了 DbUnit,我还附带介绍了一点 TDD (测试驱动的开发)的内容。

注意,本文假定读者熟悉 Java 平台上的企业开发,包括 JDBC、OR 映射内容、J2EE 设计模式如 DAO,以及声明性事务支持,如 Enterprise JavaBean (EJB)技术所提供的事务支持。理解这里的讨论不需要成为这些技术的专家,也不需要熟悉 AOP、IOC 或者 TDD,因为在本文中对这三者都做了介绍。

我将首先介绍两种开发技术,然后分析例子。

Hibernate 简介

Hibernate 是 Java 平台上的一种全功能的、开放源代码 OR 映射框架。Hibernate 在许多方面类似于 EJB CMP CMR (容器管理的持久性/容器管理的关系)和 JDO(Java Data Objects)。与 JDO 不同,Hibernate 完全着眼于关系数据库的 OR 映射,并且包括比大多数商业产品更多的功能。大多数 EJB CMP CMR 解决方案使用代码生成实现持久性代码,而 JDO 使用字节码修饰。与之相反,Hibernate 使用反射和运行时字节码生成,使它对于最终用户几乎是透明的(以前 Hibernate 的实现只使用反射,它有助于调试,当前版本保留了这种选项)。

无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
移植基于 Hibernate 的应用程序

如果应用程序必须在多个 RDBMS 系统上运行 ,那么基于 Hibernate 的应用程序可以毫不费力地移植到 IBM DB2、MySQL、PostgreSQL、Sybase、Oracle、HypersonicSQL 和许多其他数据库。我最近甚至将一个应用程序从 MySQL 移植到 Hibernate 没有很好支持的 Firebird,而这种移植是很容易的。有关在 Postgres 和 MySQL 之间转换的案例分析,请参阅 参考资料

Hibernate 可以模拟继承(有几种方式)、关联(一对一或者一对多、containment 和 aggregation)和 composition。我将在本文中讨论每种关系类型的几个例子。

Hibernate 提供了一种称为 Hibernate Query Language (HQL) 的 查询语言,它类似于 JDO 的 JDOQL 和 EJB 的 EJB QL,尽管它更接近于前者。但是 Hibernate 没有就此止步:它还可以进行直接的 SQL 查询和/或使用 object criteria很容易地在运行时构成查询条件。在本文的例子中我将只使用 HQL。

与 EJB CMP CMR 不同,Hibernate 像 JDO 一样可以在 J2EE 容器内部或者外部工作,这可以让那些进行 TDD 和敏捷开发的人受益。


无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
回页首


Spring 简介

AOP 专家 Nicholas Lesiecki 第一次向我解释 AOP 时,他说的我一个词也没理解,我觉得就像第一次考虑使用 IOC 容器的可能性时一样。每一种技术的概念基础本身就需要很好地消化,每一种技术所使用的各种各样的缩写让事情更糟了——特别是其中许多术语与我们已经使用的 根本不一样了。

像许多技术一样,理解这两种技术的实际使用比学习理论更容易。经过自己对 AOP 和 IOC 容器实现(即 XWork、PicoContainer 和 Spring)的分析,我发现这些技术可以帮助我获得功能,而不会在多框架中添加基于代码的依赖性。它们都将成为我后面开发项目的一部分。

简单地说,AOP 让开发人员可以创建非行为性的关注点,称为横切关注点,并将它们插入到应用程序代码中。使用 AOP 后,公共服务(比如日志、持久性、事务等)就可以分解成方面并应用到域对象上,同时不会增加域对象的对象模型的复杂性。

无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
关于 DbUnit

用新的框架开发而不进行单元测试,就像不带保护网走钢丝:当然可以这样做,但是很可能会受伤。我选择在有保护网的条件下开发,对我来说这个保护网就是 TDD。在有 DbUnit 之前,对依赖于数据库的代码进行测试是不太容易的。DbUnit 是 JUnit 的一个扩展,它提供了依赖于数据库的单元测试的框架。我用 DbUnit 编写本文中示例类的测试代码。虽然在本文中没有出现,不过在本文源代码中提供了 DbUnit 代码(请参阅 参考资料)。有关 DbUnit 的介绍,请参阅 Philippe Girolami 的“ Control your test-environment with DbUnit and Anthill” ( developerWorks,2004 年 4 月)。

IOC 允许创建一个可以构造对象的应用环境,然后向这些对象传递它们的协作对象。正如单词 倒置 所表明的,IOC 就像反过来的 JNDI。没有使用一堆抽象工厂、服务定位器、单元素(singleton)和直接构造(straight construction),每一个对象都是用其协作对象构造的。因此是由容器管理协作对象(collaborator)。

Spring 既是一个 AOP 框架、也是一个 IOC 容器。我记得 Grady Booch 说过,对象最好的地方是可以替换它们,而 Spring 最好的地方是它有助于您替换它们。有了 Spring,只要用 JavaBean 属性和配置文件加入依赖性(协作对象)。然后可以很容易地在需要时替换具有类似接口的协作对象。

Spring 为 IOC 容器和 AOP 提供了很好的入口(on-ramp)。因此,不需要熟悉 AOP 就可以理解本文中的例子。所需要知道的就是将要用 AOP 为示例应用程序声明式地添加事务支持,与使用 EJB 技术时的方式基本相同。要了解 IOC 容器、AOP 和 Spring 的更多内容,请参阅 参考资料


无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
回页首


具体到业务

在本文的其余部分,所有的讨论都将基于一个实际的例子。起点是一个企业应用程序,要为它实现一个事务持久层。持久层是一个对象关系数据库,它包括像 UserUser GroupRoles ContactInfo 这些熟悉的抽象。

在深入到数据库的要素——查询和事务管理——之前,需要建立它的基础:对象关系映射。我将用 Hibernate 设置它,并只使用一点 Spring。


无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
回页首


用 Hibernate 进行 OR 映射

Hibernate 使用 XML ( *.hbm.xml) 文件将 Java 类映射到表,将 JavaBean 属性映射到数据库表。幸运的是,有一组 XDoclet 标签支持 Hibernate 开发,这使得创建所需要的 *.hbm.xml 文件更容易了。清单 1 中的代码将一个 Java 类映射到数据库表。关于 XDoclet 标签的更多内容,请参阅 参考资料

清单 1. 将 Java 类映射到 DB 表
        [User.java]
/**
*
@hibernate.class table="TBL_USER"
* ..
* ..
* ...
*/
public class User {
private Long id = new Long(-1);
private String email;
private String password;

.
.
.
/**
* @return
*
@hibernate.id column="PK_USER_ID"
* unsaved-value="-1"
* generator-class="native"

*/
public Long getId() {
return id;
}
...
/**
*
@hibernate.property column="VC_EMAIL"
* type="string"
* update="false"
* insert="true"
* unique="true"
* not-null="true"
* length="82"

* @return
*/
public String getEmail() {
return email;
}
/**
*
@hibernate.property column="VC_PASSWORD"
* type="string"
* update="false"
* insert="true"
* unique="true"
* not-null="true"
* length="20"

* @return
*/
public String getPassword() {
return password;
}
...
...
...
}


可以看到, @hibernate.class table="TBL_USER" 标签将 User 映射到 TBL_USER 表。 @hibernate.property column="VC_PASSWORD" 将 JavaBean 属性 password 映射到 VC_PASSWORD 列。 @hibernate.id column="PK_USER_ID" 标签声明 id 属性是主键,它将使用本机( generator-class="native" )数据库机制生成键(例如,Oracle sequences 和 SQL Server Identity 键)。Hibernate 可以指定 generator-class="native" 以外的、其他可以想象的得到主键获得策略,不过我更愿意使用 native。 type length属性用于从 Hibernate *.hbm.xml OR 映射文件生成表。这些 final 属性是可选的,因为使用的可能不是 green-field 数据库。在这个例子中,已经有数据库了,所以不需要额外的属性。( green-field 应用程序是一个新的应用程序, green-field 数据是新应用程序的一个新数据库。不会经常开发一个全新的应用程序,不过偶尔有一两次也不错)。

看过了表如何映射到类以及列如何映射到 JavaBean 属性,该使用 Hibernate 在 OR 数据库中设置一些关系了。

设置对象关系

在本节中,我将只触及 Hibernate 提供的设置对象间关系的选项的一小部分。首先设置像 UserUser GroupRolesContactInfo 这些类之间的关系。其中一些关系如图 1 所示,这是数据库的验证对象模型。



图 1. 关系的图示
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层

如您所见,在上述抽象中存在各种各样的关系。 UserContactInfo 有一对一关系。 ContactInfo 的生命周期与 User 相同(用数据库的术语,UML 中的组成 aka 级联删除)。如果删除 User ,则相应的 ContactInfo 也会删除。在 User s 与 Role s 之间存在多对多关系(即与独立生命周期相关联)。在 Group s 与 User s 之间存在一对多关系,因为组有许多用户。用户可以存在于组外,即是 aggregation 而不是 composition (用数据库的说法,在 Group s 和 Users 之间没有级联删除关系)。此外, UserEmployee 有子类关系,就是说, Employee 的类型为 User 。表 1 显示了如何用 XDoclet 标签创建一些不同类型的对象关系。

表 1. 用 XDoclet 创建对象关系
关系 Java/XDoclet SQL DDL(由 Hibernate Schema Export 生成的 MySQL)
组包含用户

一对多

Aggregation

双向
(Group<-->Users)

[Group.java]
/**
*
* @return
*
* @hibernate.bag name="users"
* cascade="save-update"
* lazy="true"
* inverse="true"
*
* @hibernate.collection-key
* column="FK_GROUP_ID"
*
* @hibernate.collection-one-to-many
* class="net.sf.hibernateExamples.User"
*/
public List getUsers() {
return users;
}

[User.java]
/**
* @hibernate.many-to-one
* column="FK_GROUP_ID"
* class="net.sf.hibernateExamples.Group"
*/
public Group getGroup() {
return group;
}


create table TBL_USER (
PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,
USER_TYPE VARCHAR(255) not null,
FK_GROUP_ID BIGINT,
VC_EMAIL VARCHAR(82) not null unique,
primary key (PK_USER_ID)
)


create table TBL_GROUP (
PK_GROUP_ID BIGINT NOT NULL AUTO_INCREMENT,
VC_DESCRIPTION VARCHAR(255),
VC_NAME VARCHAR(40) unique,
primary key (PK_GROUP_ID)
)

alter table TBL_USER add index (FK_GROUP_ID),
add constraint FK_111 foreign key (FK_GROUP_ID)
references TBL_GROUP (PK_GROUP_ID)

用户有联系信息

一对一

Composition
单向
(User-->ContactInfo)

[User.java]
/**
* @return
*
* @hibernate.one-to-one cascade="all"
*
*/
public ContactInfo getContactInfo() {
return contactInfo;
}

[ContactInfo.java]
(Nothing to see here. Unidirectional!)
create table TBL_USER (
PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,
USER_TYPE VARCHAR(255) not null,
FK_GROUP_ID BIGINT,
VC_EMAIL VARCHAR(82) not null unique,
primary key (PK_USER_ID)
)

create table TBL_CONTACT_INFO (
PK_CONTACT_INFO_ID BIGINT not null,
...
...
...
primary key (PK_CONTACT_INFO_ID)
)

用户与角色关联

多对多

Association

单向
(Users-->Roles)

[User.java]
/**
* @return
* @hibernate.bag
* table="TBL_JOIN_USER_ROLE"
* cascade="all"
* inverse="true"
*
* @hibernate.collection-key
* column="FK_USER_ID"
*
* @hibernate.collection-many-to-many
* class="net.sf.hibernateExamples.Role"
* column="FK_ROLE_ID"
*
*/
public List getRoles() {
return roles;
}

[Role.java]
Nothing to see here. Unidirectional!
create table TBL_ROLE (
PK_ROLE_ID BIGINT NOT NULL AUTO_INCREMENT,
VC_DESCRIPTION VARCHAR(200),
VC_NAME VARCHAR(20),
primary key (PK_ROLE_ID)
)

create table TBL_USER (
PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,
USER_TYPE VARCHAR(255) not null,
FK_GROUP_ID BIGINT,
VC_EMAIL VARCHAR(82) not null unique,
primary key (PK_USER_ID)
)

create table TBL_JOIN_USER_ROLE (
FK_USER_ID BIGINT not null,
FK_ROLE_ID BIGINT not null
)

雇员是用户

Inheritance

用户
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
雇员

[User.java]
/**
* @hibernate.class table="TBL_USER"
* discriminator-value="2"
* @hibernate.discriminator column="USER_TYPE"
*
...
...
...
*/
public class User {

[Employee.java]
/**
* @hibernate.subclass discriminator-value = "1"
*/
public class Employee extends User{

create table TBL_USER (
PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,
USER_TYPE VARCHAR(255) not null,
FK_GROUP_ID BIGINT,
VC_EMAIL VARCHAR(82) not null unique,
primary key (PK_USER_ID)
)

要了解在 Hibernate 中设置对象关系的更多内容,请参阅 参考资料


无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
回页首


Hibernate 中的查询

Hibernate 有三种类型的查询:

  • Criteria, object composition
  • SQL
  • HQL

在下面的例子中将只使用 HQL。本节还要使用 Spring,用它的 AOP-driven HibernateTemplate 简化 Hibernate 会话的处理。在本节将开发一个 DAO(Data Access Object)。要了解更多关于 DAO 的内容,请参阅 参考资料

清单 2 展示了两个方法:一个使用 HQL 查询的组查询,另一个是后面接一个操作的组查询。注意在第二个方法中,Spring HibernateTemplate 是如何简化会话管理的。

清单 2. 使用查询
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Session;
import net.sf.hibernate.Query;
import org.springframework.orm.hibernate.HibernateCallback;
import org.springframework.orm.hibernate.support.HibernateDaoSupport;
/**
* @author Richard Hightower
* ArcMind Inc. http://www.arc-mind.com
*/
public class UserDAO extends HibernateDaoSupport{
.
.
.
/**
* Demonstrates looking up a group with a HQL query
* @param email
* @return
*/
public Group findGroupByName(String name) {
return (Group) getHibernateTemplate().find("from Group g where g.name=?",name).get(0);
}

/**
* Demonstrates looking up a group and forcing it to populate users (relationship was lazy)
* @param email
* @return
*/
public Group findPopulatedGroupByName(final String name) {
HibernateCallback callback = new HibernateCallback(){
public Object doInHibernate(Session session) throws HibernateException, SQLException {
Group group =null;
String query = "from Group g where g.name=?";
Query queryObject = getHibernateTemplate().createQuery(session, query);
queryObject.setParameter(0, name);
group = (Group) queryObject.list().get(0);
group.getUsers().size();//force load
return group;
}

};

return (Group) getHibernateTemplate().execute(callback);
}
.
.
.
}

您可能会注意到第二个方法比第一个方法复杂得多,因为它强迫加载 users 集合。因为 Group->Users 之间的关系设置为 lazy initialize(即表 2 中 lazy="true" ),组对象需要一个活跃的会话以查询用户。在定义 GroupUser s 之间关系时设置这个属性为 lazy="false" ,则不需要第二个方法。在这种情况下,可能使用第一种方法 ( findGroupByName ) 列出组,用第二种方法( findPopulatedGroupByName )查看组细节。


无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
回页首


Spring IOC 和 Hibernate

使用 Spring 时,在 J2EE 容器内和容器外工作一样容易。比如在最近的项目中,我在 Eclipse 中,使用 HSQL 和本地数据库对使用 Hibernate 事务管理器的 Hypersonic SQL 数据库进行持久性单元测试。然后,在部署到 J2EE 服务器时,将持久层转换为使用 J2EE 数据源(通过 JNDI)、JTA 事务和使用 FireBird (一个开放源代码版本的 Interbase)。这是用 Spring 作为 IOC 容器完成的。

从清单 3 中可以看出,Spring 允许加入依赖性。注意清单中应用程序上下文文件是如何配置 dataSource 的。 dataSource 传递给 sessionFactorysessionFactory 传递给 UserDAO

清单 3. Spring IOC 和 Hibernate
<beans>
<!-- Datasource that works in any application server
You could easily use J2EE data source instead if this were
running inside of a J2EE container.
-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
<property name="url"><value>jdbc:mysql://localhost:3306/mysql</value></property>
<property name="username"><value>root</value></property>
<property name="password"><value></value></property>
</bean>
<!-- Hibernate SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
<property name="dataSource"><ref local="dataSource"/></property>

<!-- Must references all OR mapping files. -->
<property name="mappingResources">
<list>
<value>net/sf/hibernateExamples/User.hbm.xml</value>
<value>net/sf/hibernateExamples/Group.hbm.xml</value>
<value>net/sf/hibernateExamples/Role.hbm.xml</value>
<value>net/sf/hibernateExamples/ContactInfo.hbm.xml</value>
</list>
</property>

<!-- Set the type of database; changing this one property will port this to Oracle,
MS SQL etc. -->
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</prop>
</props>
</property>
</bean>

<!-- Pass the session factory to our UserDAO -->
<bean id="userDAO" class="net.sf.hibernateExamples.UserDAO">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>

</beans>

设置了 UserDAO 后,下一步就是定义并使用更多的查询以展示可以完成的操作。Hibernate 可以用预定义查询将查询存储到源代码之外,如清单 4 所示。

清单 4. 预定义查询
        [User.java]
/**
* @author Richard Hightower
* ArcMind Inc. http://www.arc-mind.com
* @hibernate.class table="TBL_USER" discriminator-value="2"
* @hibernate.discriminator column="USER_TYPE"
*
* @hibernate.query name="AllUsers" query="from User user order by user.email asc"
*
* @hibernate.query name="OverheadStaff"
* query="from Employee employee join employee.group g where g.name not in ('ENGINEERING','IT')"
*
* @hibernate.query name="CriticalStaff"
* query="from Employee employee join employee.group g where g.name in ('ENGINEERING','IT')"
*
* @hibernate.query name="GetUsersInAGroup"
* query="select user from Group g join g.users user"
*
* @hibernate.query name="GetUsersNotInAGroup"
* query="select user from User user where user.group is null"
*
* @hibernate.query name="UsersBySalaryGreaterThan"
* query="from User user inner join user.contactInfo info where info.salary > ?1"
*
* @hibernate.query name="UsersBySalaryBetween"
* query="from User user join user.contactInfo info where info.salary between ?1 AND ?2"
*
* @hibernate.query name="UsersByLastNameLike"
* query="from User user join user.contactInfo info where info.lastName like ?1"
*
* @hibernate.query name="GetEmailsOfUsers"
* query="select user.email from Group g join g.users as user where g.name = ?1"
*
*/
public class User {
.
.
.

上述代码定义了几个预定义查询。 预定义查询 是存储在 *.hbm.xml文件中的查询。在清单 5 中,可以看到如何执行预定义查询。

清单 5. 使用预定义查询
        [UserDAO.java]
/**
* Demonstrates a query that returns a String.
*/
public String[] getUserEmailsInGroup(String groupName){
List emailList =
getHibernateTemplate().findByNamedQuery("GetEmailsOfUsers");
return (String [])
emailList.toArray(new String[emailList.size()]);
}
/**
* Demonstrates a query that returns a list of Users
*
* @return A list of emails of all of the users in the authentication system.
*
*/
public List getUsers(){
return getHibernateTemplate().findByNamedQuery("AllUsers");
}
/**
* Demonstrates passing a single argument to a query.
*
* @return A list of UserValue objects.
*
*/
public List getUsersBySalary(float salary){
return getHibernateTemplate()
.findByNamedQuery("UsersBySalaryGreaterThan",
new Float(salary));
}
/**
* Demonstrates passing multiple arguments to a query
*
* @return A list of UserValue objects.
*
*/
public List getUsersBySalaryRange(float start, float stop){
return getHibernateTemplate()
.findByNamedQuery("UsersBySalaryBetween",
new Object[] {new Float(start), new Float(stop)});
}


查询进行时,可以在持久层中加上最后一层:使用 Spring 的事务管理。


无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
回页首


用 Spring 管理事务

Spring 可以声明式地管理事务。例如, UserDAO.addUser 方法当前不是在单个事务中执行的。因此,组中的每一个用户都插入到自己的事务中,如清单 6 所示。

清单 6. 添加一组用户
        [UserDAO.java]
/**
* @param group
*/
public void addGroup(Group group) {
getHibernateTemplate().save(group);

}
[UserDAOTest.java]
public void testAddGroupOfUsers(){
Group group = new Group();

for (int index=0; index < 10; index++){
User user = new User();
user.setEmail("rick"+index+"@foobar.com" );
user.setPassword("foobar");
group.addUser(user);
}

group.setName("testGroup");

userDAO.addGroup(group);
assertNotNull(group.getId());

Group group2 = userDAO.findPopulatedGroupByName("testGroup");

assertEquals("testGroup",group2.getName());
assertEquals(10, group2.getUsers().size());
String email = ((User)group2.getUsers().get(0)).getEmail();
assertEquals("[email protected]", email);

}

不建议使用上述解决方案,因为每一个 User 都要在自己的事务中插入到数据库中。如果出现问题,那么只能添加部分用户。如果希望保留 ACID 属性(即保证所有都发生或者所有都不发生),可以通过程序进行事务管理,但是它很快就会变得一团糟了。相反,应使用 Spring 的 AOP 来支持声明式的事务,如清单 7 所示。

清单 7. 声明式管理事务
        [applicationContext.xml]
<!-- Pass the session factory to our UserDAO -->
<bean id="userDAOTarget" class="net.sf.hibernateExamples.UserDAOImpl">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>

<bean id="transactionManager"
class="org.springframework.orm.hibernate.HibernateTransactionManager">
<property name="sessionFactory"><ref bean="sessionFactory"/></property>
</bean>
<bean id="userDAO"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager"><ref local="transactionManager"/></property>
<property name="target"><ref local="userDAOTarget"/></property>
<property name="transactionAttributes">
<props>
<prop key="addGroup">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>

注意在准备清单 7 的代码时,我重新改写了 UserDAO 并提取了其接口。这个接口现在是 UserDAO ,它的实现类是 UserDAOImpl 。这样清单 7 中的事务代码就使用了带有事务属性 (PROPAGATION_REQUIRED) UserDAO.addGroup() 方法。现在只要底层数据库支持,就可以在一个事务中添加所有用户。


无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
无需容器的对象关系映射 ------->用 Hibernate 和 Spring 开发事务持久层
回页首


结束语

在本文中,介绍了如何使用 Hibernate 和 Spring 实现一个事务持久层。Hibernate 是一种先进的 OR 映射工具,而 Spring 是一个 AOP 框架和 IOC 容器。这两种技术的综合使用,使得开发人员可以编写媲美数据库厂商的代码,它可以在 J2EE 容器中运行,也可以单独运行。使用了 DbUnit (JUnit 的扩展)构建和测试本文中例子的所有代码,虽然这不是讨论的重点。

要了解有关 AOP、IOC 容器和测试驱动开发的更多内容,请参阅 参考资料