http://www.javafan.net/article/20041223180912482.html
當您自以為已經(jīng)了解了所有開發(fā)工具時,肯定又會冒出一個新的工具。在本文中,developerWorks 的固定撰稿人 Rick Hightower 用一個真實世界的例子向您介紹兩個最激動人心的企業(yè)新技術(shù)。Hibernate 是一個對象關(guān)系映射工具,而 Spring 是一個 AOP 框架和 IOC 容器。Rick 介紹了如何結(jié)合這兩者,為企業(yè)應(yīng)用程序構(gòu)建一個事務(wù)持久層。
如果關(guān)心開發(fā)人員的最新熱點,那么您可能聽說過 IOC (控制倒置,Inversion of Control)容器和 AOP (面向方面編程)。不過,像許多開發(fā)人員一樣,您可能不清楚在自己的開發(fā)工作中如何使用這些技術(shù)。在本文中,通過具體介紹使用 Hibernate 和 Spring 在企業(yè)應(yīng)用程序中構(gòu)建一個事務(wù)持久層,您會認識到這些技術(shù)。
Hibernate 是 Java 平臺上的一種流行的、容易使用的開放源代碼對象關(guān)系(OR)映射框架。Spring 是一個 AOP 框架和 IOC 容器。這兩種技術(shù)一起提供了本文中介紹的開發(fā)工作的基礎(chǔ)。將使用 Hibernate 把一些持久性對象映射到關(guān)系數(shù)據(jù)庫中,用 Spring 使 Hibernate 更容易使用并提供聲明性事務(wù)支持。由于為示例類編寫測試代碼時使用了 DbUnit,我還附帶介紹了一點 TDD (測試驅(qū)動的開發(fā))的內(nèi)容。
注意,本文假定讀者熟悉 Java 平臺上的企業(yè)開發(fā),包括 JDBC、OR 映射內(nèi)容、J2EE 設(shè)計模式如 DAO,以及聲明性事務(wù)支持,如 Enterprise JavaBean (EJB)技術(shù)所提供的事務(wù)支持。理解這里的討論不需要成為這些技術(shù)的專家,也不需要熟悉 AOP、IOC 或者 TDD,因為在本文中對這三者都做了介紹。
我將首先介紹兩種開發(fā)技術(shù),然后分析例子。
Hibernate 簡介
Hibernate 是 Java 平臺上的一種全功能的、開放源代碼 OR 映射框架。Hibernate 在許多方面類似于 EJB CMP CMR (容器管理的持久性/容器管理的關(guān)系)和 JDO(Java Data Objects)。與 JDO 不同,Hibernate 完全著眼于關(guān)系數(shù)據(jù)庫的 OR 映射,并且包括比大多數(shù)商業(yè)產(chǎn)品更多的功能。大多數(shù) EJB CMP CMR 解決方案使用代碼生成實現(xiàn)持久性代碼,而 JDO 使用字節(jié)碼修飾。與之相反,Hibernate 使用反射和運行時字節(jié)碼生成,使它對于最終用戶幾乎是透明的(以前 Hibernate 的實現(xiàn)只使用反射,它有助于調(diào)試,當前版本保留了這種選項)。
移植基于 Hibernate 的應(yīng)用程序
如果應(yīng)用程序必須在多個 RDBMS 系統(tǒng)上運行 ,那么基于 Hibernate 的應(yīng)用程序可以毫不費力地移植到 IBM DB2、MySQL、PostgreSQL、Sybase、Oracle、HypersonicSQL 和許多其他數(shù)據(jù)庫。我最近甚至將一個應(yīng)用程序從 MySQL 移植到 Hibernate 沒有很好支持的 Firebird,而這種移植是很容易的。 Hibernate 可以模擬繼承(有幾種方式)、關(guān)聯(lián)(一對一或者一對多、containment 和 aggregation)和 composition。我將在本文中討論每種關(guān)系類型的幾個例子。
Hibernate 提供了一種稱為 Hibernate Query Language (HQL) 的 查詢語言,它類似于 JDO 的 JDOQL 和 EJB 的 EJB QL,盡管它更接近于前者。但是 Hibernate 沒有就此止步:它還可以進行直接的 SQL 查詢和/或使用 object criteria 很容易地在運行時構(gòu)成查詢條件。在本文的例子中我將只使用 HQL。
與 EJB CMP CMR 不同,Hibernate 像 JDO 一樣可以在 J2EE 容器內(nèi)部或者外部工作,這可以讓那些進行 TDD 和敏捷開發(fā)的人受益。
Spring 簡介
AOP 專家 Nicholas Lesiecki 第一次向我解釋 AOP 時,他說的我一個詞也沒理解,我覺得就像第一次考慮使用 IOC 容器的可能性時一樣。每一種技術(shù)的概念基礎(chǔ)本身就需要很好地消化,每一種技術(shù)所使用的各種各樣的縮寫讓事情更糟了——特別是其中許多術(shù)語與我們已經(jīng)使用的根本不一樣了。
像許多技術(shù)一樣,理解這兩種技術(shù)的實際使用比學(xué)習(xí)理論更容易。經(jīng)過自己對 AOP 和 IOC 容器實現(xiàn)(即 XWork、PicoContainer 和 Spring)的分析,我發(fā)現(xiàn)這些技術(shù)可以幫助我獲得功能,而不會在多框架中添加基于代碼的依賴性。它們都將成為我后面開發(fā)項目的一部分。
簡單地說,AOP 讓開發(fā)人員可以創(chuàng)建非行為性的關(guān)注點,稱為橫切關(guān)注點,并將它們插入到應(yīng)用程序代碼中。使用 AOP 后,公共服務(wù)(比如日志、持久性、事務(wù)等)就可以分解成方面并應(yīng)用到域?qū)ο笊希瑫r不會增加域?qū)ο蟮膶ο竽P偷膹?fù)雜性。
IOC 允許創(chuàng)建一個可以構(gòu)造對象的應(yīng)用環(huán)境,然后向這些對象傳遞它們的協(xié)作對象。正如單詞 倒置 所表明的,IOC 就像反過來的 JNDI。沒有使用一堆抽象工廠、服務(wù)定位器、單元素(singleton)和直接構(gòu)造(straight construction),每一個對象都是用其協(xié)作對象構(gòu)造的。因此是由容器管理協(xié)作對象(collaborator)。
Spring 既是一個 AOP 框架、也是一個 IOC 容器。我記得 Grady Booch 說過,對象最好的地方是可以替換它們,而 Spring 最好的地方是它有助于您替換它們。有了 Spring,只要用 JavaBean 屬性和配置文件加入依賴性(協(xié)作對象)。然后可以很容易地在需要時替換具有類似接口的協(xié)作對象。
Spring 為 IOC 容器和 AOP 提供了很好的入口(on-ramp)。因此,不需要熟悉 AOP 就可以理解本文中的例子。所需要知道的就是將要用 AOP 為示例應(yīng)用程序聲明式地添加事務(wù)支持,與使用 EJB 技術(shù)時的方式基本相同。
具體到業(yè)務(wù)
在本文的其余部分,所有的討論都將基于一個實際的例子。起點是一個企業(yè)應(yīng)用程序,要為它實現(xiàn)一個事務(wù)持久層。持久層是一個對象關(guān)系數(shù)據(jù)庫,它包括像 User、User Group、Roles 和 ContactInfo 這些熟悉的抽象。
在深入到數(shù)據(jù)庫的要素——查詢和事務(wù)管理——之前,需要建立它的基礎(chǔ):對象關(guān)系映射。我將用 Hibernate 設(shè)置它,并只使用一點 Spring。
用 Hibernate 進行 OR 映射
Hibernate 使用 XML (*.hbm.xml) 文件將 Java 類映射到表,將 JavaBean 屬性映射到數(shù)據(jù)庫表。幸運的是,有一組 XDoclet 標簽支持 Hibernate 開發(fā),這使得創(chuàng)建所需要的 *.hbm.xml 文件更容易了。清單 1 中的代碼將一個 Java 類映射到數(shù)據(jù)庫表。
清單 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")數(shù)據(jù)庫機制生成鍵(例如,Oracle sequences 和 SQL Server Identity 鍵)。Hibernate 可以指定 generator-class="native" 以外的、其他可以想象的得到主鍵獲得策略,不過我更愿意使用 native。type 和 length 屬性用于從 Hibernate *.hbm.xml OR 映射文件生成表。這些 final 屬性是可選的,因為使用的可能不是 green-field 數(shù)據(jù)庫。在這個例子中,已經(jīng)有數(shù)據(jù)庫了,所以不需要額外的屬性。(green-field 應(yīng)用程序 是一個新的應(yīng)用程序, green-field 數(shù)據(jù) 是新應(yīng)用程序的一個新數(shù)據(jù)庫。不會經(jīng)常開發(fā)一個全新的應(yīng)用程序,不過偶爾有一兩次也不錯)。
看過了表如何映射到類以及列如何映射到 JavaBean 屬性,該使用 Hibernate 在 OR 數(shù)據(jù)庫中設(shè)置一些關(guān)系了。
設(shè)置對象關(guān)系
在本節(jié)中,我將只觸及 Hibernate 提供的設(shè)置對象間關(guān)系的選項的一小部分。首先設(shè)置像 User、User Group、Roles 和 ContactInfo 這些類之間的關(guān)系。其中一些關(guān)系如圖 1 所示,這是數(shù)據(jù)庫的驗證對象模型。
 圖 1. 關(guān)系的圖示
如您所見,在上述抽象中存在各種各樣的關(guān)系。User 與 ContactInfo 有一對一關(guān)系。ContactInfo 的生命周期與 User 相同(用數(shù)據(jù)庫的術(shù)語,UML 中的組成 aka 級聯(lián)刪除)。如果刪除 User,則相應(yīng)的 ContactInfo 也會刪除。在 Users 與 Roles 之間存在多對多關(guān)系(即與獨立生命周期相關(guān)聯(lián))。在 Groups 與 Users 之間存在一對多關(guān)系,因為組有許多用戶。用戶可以存在于組外,即是 aggregation 而不是 composition (用數(shù)據(jù)庫的說法,在 Groups 和 Users 之間沒有級聯(lián)刪除關(guān)系)。此外,User 和 Employee 有子類關(guān)系,就是說,Employee 的類型為 User。表 1 顯示了如何用 XDoclet 標簽創(chuàng)建一些不同類型的對象關(guān)系。
表 1. 用 XDoclet 創(chuàng)建對象關(guān)系
關(guān)系 |
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) |
用戶有聯(lián)系信息 一對一 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) ) |
用戶與角色關(guān)聯(lián) 多對多 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 用戶 雇員 |
[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 有三種類型的查詢:
☆ Criteria, object composition ☆ SQL ☆ HQL
在下面的例子中將只使用 HQL。本節(jié)還要使用 Spring,用它的 AOP-driven HibernateTemplate 簡化 Hibernate 會話的處理。在本節(jié)將開發(fā)一個 DAO(Data Access Object)。要了解更多關(guān)于 DAO 的內(nèi)容,請參閱 參考資料。
清單 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); } . . . }
您可能會注意到第二個方法比第一個方法復(fù)雜得多,因為它強迫加載 users 集合。因為 Group->Users 之間的關(guān)系設(shè)置為 lazy initialize(即表 2 中 lazy="true"),組對象需要一個活躍的會話以查詢用戶。在定義 Group 和 Users 之間關(guān)系時設(shè)置這個屬性為 lazy="false",則不需要第二個方法。在這種情況下,可能使用第一種方法 (findGroupByName) 列出組,用第二種方法(findPopulatedGroupByName)查看組細節(jié)。
Spring IOC 和 Hibernate
使用 Spring 時,在 J2EE 容器內(nèi)和容器外工作一樣容易。比如在最近的項目中,我在 Eclipse 中,使用 HSQL 和本地數(shù)據(jù)庫對使用 Hibernate 事務(wù)管理器的 Hypersonic SQL 數(shù)據(jù)庫進行持久性單元測試。然后,在部署到 J2EE 服務(wù)器時,將持久層轉(zhuǎn)換為使用 J2EE 數(shù)據(jù)源(通過 JNDI)、JTA 事務(wù)和使用 FireBird (一個開放源代碼版本的 Interbase)。這是用 Spring 作為 IOC 容器完成的。
從清單 3 中可以看出,Spring 允許加入依賴性。注意清單中應(yīng)用程序上下文文件是如何配置 dataSource 的。dataSource 傳遞給 sessionFactory,sessionFactory 傳遞給 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>
設(shè)置了 UserDAO 后,下一步就是定義并使用更多的查詢以展示可以完成的操作。Hibernate 可以用預(yù)定義查詢將查詢存儲到源代碼之外,如清單 4 所示。
清單 4. 預(yù)定義查詢 [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 { . . .
上述代碼定義了幾個預(yù)定義查詢。預(yù)定義查詢 是存儲在 *.hbm.xml 文件中的查詢。在清單 5 中,可以看到如何執(zhí)行預(yù)定義查詢。
清單 5. 使用預(yù)定義查詢 [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 的事務(wù)管理。
用 Spring 管理事務(wù)
Spring 可以聲明式地管理事務(wù)。例如,UserDAO.addUser 方法當前不是在單個事務(wù)中執(zhí)行的。因此,組中的每一個用戶都插入到自己的事務(wù)中,如清單 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("rick0@foobar.com", email);
}
不建議使用上述解決方案,因為每一個 User 都要在自己的事務(wù)中插入到數(shù)據(jù)庫中。如果出現(xiàn)問題,那么只能添加部分用戶。如果希望保留 ACID 屬性(即保證所有都發(fā)生或者所有都不發(fā)生),可以通過程序進行事務(wù)管理,但是它很快就會變得一團糟了。相反,應(yīng)使用 Spring 的 AOP 來支持聲明式的事務(wù),如清單 7 所示。
清單 7. 聲明式管理事務(wù) [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 并提取了其接口。這個接口現(xiàn)在是 UserDAO,它的實現(xiàn)類是 UserDAOImpl。這樣清單 7 中的事務(wù)代碼就使用了帶有事務(wù)屬性 (PROPAGATION_REQUIRED) 的 UserDAO.addGroup() 方法。現(xiàn)在只要底層數(shù)據(jù)庫支持,就可以在一個事務(wù)中添加所有用戶。
結(jié)束語
在本文中,介紹了如何使用 Hibernate 和 Spring 實現(xiàn)一個事務(wù)持久層。Hibernate 是一種先進的 OR 映射工具,而 Spring 是一個 AOP 框架和 IOC 容器。這兩種技術(shù)的綜合使用,使得開發(fā)人員可以編寫媲美數(shù)據(jù)庫廠商的代碼,它可以在 J2EE 容器中運行,也可以單獨運行。使用了 DbUnit (JUnit 的擴展)構(gòu)建和測試本文中例子的所有代碼,雖然這不是討論的重點。 |