6.5 Spring整合Hibernate
時(shí)至今日,可能極少有J2EE應(yīng)用會(huì)直接以JDBC方式進(jìn)行持久層訪問(wèn)。畢竟,用面向?qū)ο蟮某绦蛟O(shè)計(jì)語(yǔ)言來(lái)訪問(wèn)關(guān)系型數(shù)據(jù)庫(kù),是一件讓人沮喪的事情。大部分時(shí)候,J2EE應(yīng)用都會(huì)以O(shè)RM框架來(lái)進(jìn)行持久層訪問(wèn),在所有的ORM框架中,Hibernate以其靈巧、輕便的封裝贏得了眾多開(kāi)發(fā)者的青睞。
Spring具有良好的開(kāi)放性,能與大部分ORM框架良好整合。下面將詳細(xì)介紹Spring與Hibernate的整合。
6.5.1 Spring提供的DAO支持
DAO模式是一種標(biāo)準(zhǔn)的J2EE設(shè)計(jì)模式,DAO模式的核心思想是,所有的數(shù)據(jù)庫(kù)訪 問(wèn),都通過(guò)DAO組件完成,DAO組件封裝了數(shù)據(jù)庫(kù)的增、刪、改等原子操作。而業(yè)務(wù)邏輯組件則依賴(lài)于DAO組件提供的數(shù)據(jù)庫(kù)原子操作,完成系統(tǒng)業(yè)務(wù)邏輯的實(shí)現(xiàn)。
對(duì)于J2EE應(yīng)用的架構(gòu),有非常多的選擇,但不管細(xì)節(jié)如何變換,J2EE應(yīng)用都大致可分為如下3層:
?? ● 表現(xiàn)層。
?? ● 業(yè)務(wù)邏輯層。
?? ● 數(shù)據(jù)持久層。
輕量級(jí)J2EE架構(gòu)以Spring IoC容器為核心,承上啟下。其向上管理來(lái)自表現(xiàn)層的Action,向下管理業(yè)務(wù)邏輯層組件,同時(shí)負(fù)責(zé)管理業(yè)務(wù)邏輯層所需的DAO對(duì)象。各層之間負(fù)責(zé)傳值的是值對(duì)象,也就是JavaBean實(shí)例。
圖6.5精確地描繪了輕量級(jí)J2EE架構(gòu)的大致情形。
DAO組件是整個(gè)J2EE應(yīng)用的持久層訪問(wèn)的重要組件,每個(gè)J2EE應(yīng)用的底層實(shí)現(xiàn)都難以離開(kāi)DAO組件的支持。Spring對(duì)實(shí)現(xiàn)DAO組件提供了許多工具類(lèi),系統(tǒng)的DAO組件可通過(guò)繼承這些工具類(lèi)完成,從而可以更加簡(jiǎn)便地實(shí)現(xiàn)DAO組件。
Spring的DAO支持,允許使用相同的方式、不同的數(shù)據(jù)訪問(wèn)技術(shù),如JDBC、Hibernate或JDO。Spring的DAO在不同的持久層訪問(wèn)技術(shù)上提供抽象,應(yīng)用的持久層訪問(wèn)基于Spring的DAO抽象。因此,應(yīng)用程序可以在不同的持久層技術(shù)之間切換。
Spring提供了一系列的抽象類(lèi),這些抽象將被作為應(yīng)用中DAO實(shí)現(xiàn)類(lèi)的父類(lèi)。通過(guò)繼承這些抽象類(lèi),Spring簡(jiǎn)化了DAO的開(kāi)發(fā)步驟,能以一致的方式使用數(shù)據(jù)庫(kù)訪問(wèn)技術(shù)。不管底層采用JDBC、JDO或Hibernate,應(yīng)用中都可采用一致的編程模型。
圖6.5 輕量級(jí)J2EE應(yīng)用架構(gòu)
應(yīng)用的DAO類(lèi)繼承這些抽象類(lèi),會(huì)大大簡(jiǎn)化應(yīng)用的開(kāi)發(fā)。最大的好處是,繼承這些抽象類(lèi)的DAO能以一致的方式訪問(wèn)數(shù)據(jù)庫(kù),意味著應(yīng)用程序可以在不同的持久層訪問(wèn)技術(shù)中切換。
除此之外,Spring提供了一致的異常抽象,將原有的Checked異常轉(zhuǎn)換包裝成Runtime異常,因而,編碼時(shí)無(wú)須捕獲各種技術(shù)中特定的異常。Spring DAO體系中的異常,都繼承DataAccessException,而DataAccessException異常是Runtime的,無(wú)須顯式捕捉。通過(guò)DataAccessException的子類(lèi)包裝原始異常信息,從而保證應(yīng)用程序依然可以捕捉到原始異常信息。
Spring提供了多種數(shù)據(jù)庫(kù)訪問(wèn)技術(shù)的DAO支持,包括Hibernate、JDO、TopLink、iBatis、OJB等。Spring可以使用相同的訪問(wèn)模式、不同的數(shù)據(jù)庫(kù)訪問(wèn)技術(shù)。就Hibernate的持久層訪問(wèn)技術(shù)而言,Spring提供了如下3個(gè)工具類(lèi)(或接口)來(lái)支持DAO組件的實(shí)現(xiàn):
?? ● HibernateDaoSupport。
?? ● HibernateTemplate。
?? ● HibernateCallBack。
6.5.2 管理Hibernate的SessionFactory
前面介紹Hibernate時(shí)已經(jīng)知道,在通過(guò)Hibernate進(jìn)行持久層訪問(wèn)時(shí),Hibernate的SessionFactory是一個(gè)非常重要的對(duì)象,它是單個(gè)數(shù)據(jù)庫(kù)映射關(guān)系編譯后的內(nèi)存鏡像。大部分情況下,一個(gè)J2EE應(yīng)用對(duì)應(yīng)一個(gè)數(shù)據(jù)庫(kù),也即對(duì)應(yīng)一個(gè)SessionFactory對(duì)象。
在純粹的Hibernate訪問(wèn)中,應(yīng)用程序需要手動(dòng)創(chuàng)建SessionFactory實(shí)例,可想而知,這不是一個(gè)優(yōu)秀的策略。在實(shí)際開(kāi)發(fā)中,希望以一種聲明式的方式管理SessionFactory實(shí)例,直接以配置文件來(lái)管理SessionFactory實(shí)例,在示范Struts的PlugIn擴(kuò)展點(diǎn)時(shí),大致示范了這種方式(請(qǐng)參閱2.12.1節(jié)的內(nèi)容)。
Spring的IoC容器則提供了更好的管理方式,它不僅能以聲明式的方式配置Session- Factory實(shí)例,也可充分利用IoC容器的作用,為SessionFactory注入數(shù)據(jù)源引用。
下面是Spring配置文件中配置Hibernate SessionFactory的示范代碼:
<?xml version="1.0" encoding="GBK"?>
<!-- beans是Spring配置文件的根元素,并且指定了Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
?????? xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
??? <!-- 定義數(shù)據(jù)源Bean,使用C3P0數(shù)據(jù)源實(shí)現(xiàn) -->
??? <bean id="dataSource" class="com.mchange.v2.c3p0. ComboPooledDataSource"
??? destroy-method="close">
??????? <!-- 指定連接數(shù)據(jù)庫(kù)的驅(qū)動(dòng) -->
??????? <property name="driverClass" value="com.mysql.jdbc.Driver"/>
??????? <!-- 指定連接數(shù)據(jù)庫(kù)的URL -->
??????? <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
??????? <!-- 指定連接數(shù)據(jù)庫(kù)的用戶(hù)名 -->
??????? <property name="user" value="root"/>
??????? <!-- 指定連接數(shù)據(jù)庫(kù)的密碼 -->
??????? <property name="password" value="32147"/>
??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的最大連接數(shù) -->
??????? <property name="maxPoolSize" value="40"/>
??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的最小連接數(shù) -->
??????? <property name="minPoolSize" value="1"/>
??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的初始化連接數(shù) -->
??????? <property name="initialPoolSize" value="1"/>
??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的連接最大空閑時(shí)間 -->
??????? <property name="maxIdleTime" value="20"/>
??? </bean>
??? <!-- 定義Hibernate的SessionFactory -->
??? <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
??? LocalSessionFactoryBean">
??????? <!-- 依賴(lài)注入數(shù)據(jù)源,正是上文定義的dataSource -->
??????? <property name="dataSource" ref="dataSource"/>
??????? <!-- mappingResources屬性用來(lái)列出全部映射文件 -->
??????? <property name="mappingResources">
??????????? <list>
??????????????? <!-- 以下用來(lái)列出所有的PO映射文件 -->
??????????????? <value>lee/MyTest.hbm.xml</value>
??????????? </list>
??????? </property>
????????? <!-- 定義Hibernate的SessionFactory屬性 -->
??????? <property name="hibernateProperties">
??????? ???? <props>
??????????????? <!-- 指定Hibernate的連接方言 -->
??????????? ??? <prop key="hibernate.dialect">org.hibernate.dialect.
??????????????? MySQLDialect</prop>
??????????????? <!-- 配置啟動(dòng)應(yīng)用時(shí),是否根據(jù)Hibernate映射自動(dòng)創(chuàng)建數(shù)據(jù)表 -->
??????????? ????? <prop key="hibernate.hbm2ddl.auto">update</prop>
?????????? </props>
??????? </property>
??? </bean>
</beans>
一旦在Spring的IoC容器中配置了SessionFactory Bean,它將隨應(yīng)用的啟動(dòng)而加載,并可以充分利用IoC容器的功能,將SessionFactory Bean注入任何Bean,比如DAO組件。一旦DAO組件獲得了SessionFactory Bean的引用,就可以完成實(shí)際的數(shù)據(jù)庫(kù)訪問(wèn)。
當(dāng)然,Spring也支持訪問(wèn)容器數(shù)據(jù)源。如果需要使用容器數(shù)據(jù)源,可將數(shù)據(jù)源Bean修改成如下配置:
<!-- 此處配置JNDI數(shù)據(jù)源 -->
<bean id="myDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
??? <property name="jndiName">
??????? <!-- 指定數(shù)據(jù)源的JNDI -->
??????? <value>java:comp/env/jdbc/myds</value>
??? </property>
</bean>
可見(jiàn),以聲明式的方式管理SessionFactory實(shí)例,可以讓?xiě)?yīng)用在不同數(shù)據(jù)源之間切換。如果應(yīng)用更換數(shù)據(jù)庫(kù)等持久層資源,只需對(duì)配置文件進(jìn)行簡(jiǎn)單修改即可。
提示:以聲明式的方式管理SessionFactory,非常類(lèi)似于早期將數(shù)據(jù)庫(kù)服務(wù)的相關(guān)信息放在web.xml文件中進(jìn)行配置。這種方式是為了提供更好的適應(yīng)性,當(dāng)持久層服務(wù)需要更改時(shí),應(yīng)用代碼無(wú)須任何改變。
6.5.3 使用HibernateTemplate
HibernateTemplate提供持久層訪問(wèn)模板,使用HibernateTemplate無(wú)須實(shí)現(xiàn)特定接口,它只需要提供一個(gè)SessionFactory的引用就可執(zhí)行持久化操作。SessionFactory對(duì)象既可通過(guò)構(gòu)造參數(shù)傳入,也可通過(guò)設(shè)值方式傳入。HibernateTemplate提供如下3個(gè)構(gòu)造函數(shù):
?? ● HibernateTemplate()。
?? ● HibernateTemplate(org.hibernate.SessionFactory sessionFactory)。
?? ● HibernateTemplate(org.hibernate.SessionFactory sessionFactory, boolean allowCreate)。
第一個(gè)構(gòu)造函數(shù),構(gòu)造一個(gè)默認(rèn)的HibernateTemplate實(shí)例。因此,使用Hibernate- Template實(shí)例之前,還必須使用方法setSessionFactory(SessionFactory sessionFactory)來(lái)為HibernateTemplate傳入SessionFactory的引用。
第二個(gè)構(gòu)造函數(shù),在構(gòu)造時(shí)已經(jīng)傳入SessionFactory引用。
第三個(gè)構(gòu)造函數(shù),其boolean型參數(shù)表明,如果當(dāng)前線程已經(jīng)存在一個(gè)非事務(wù)性的Session,是否直接返回此非事務(wù)性的Session。
在Web應(yīng)用中,通常啟動(dòng)時(shí)自動(dòng)加載ApplicationContext,SessionFactory和DAO對(duì)象都處在Spring上下文管理下,因此無(wú)須在代碼中顯式設(shè)置,可采用依賴(lài)注入完成Session- Factory和DAO的解耦,依賴(lài)關(guān)系通過(guò)配置文件來(lái)設(shè)置,如下所示:
<?xml version="1.0" encoding="GBK"?>
<!-- beans是Spring配置文件的根元素,并且指定了Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
?????? xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
??? <!-- 定義數(shù)據(jù)源Bean,使用C3P0數(shù)據(jù)源實(shí)現(xiàn) -->
??? <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
??? destroy-method="close">
??????? <!-- 指定連接數(shù)據(jù)庫(kù)的驅(qū)動(dòng) -->
??????? <property name="driverClass" value="com.mysql.jdbc.Driver"/>
??????? <!-- 指定連接數(shù)據(jù)庫(kù)的URL -->
??????? <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
??????? <!-- 指定連接數(shù)據(jù)庫(kù)的用戶(hù)名 -->
??????? <property name="user" value="root"/>
??????? <!-- 指定連接數(shù)據(jù)庫(kù)的密碼 -->
??????? <property name="password" value="32147"/>
??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的最大連接數(shù) -->
??????? <property name="maxPoolSize" value="40"/>
??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的最小連接數(shù) -->
??????? <property name="minPoolSize" value="1"/>
??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的初始化連接數(shù) -->
??????? <property name="initialPoolSize" value="1"/>
??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的連接最大空閑時(shí)間 -->
??????? <property name="maxIdleTime" value="20"/>
??? </bean>
??? <!-- 定義Hibernate的SessionFactory Bean -->
??? <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
??? LocalSessionFactoryBean">
??????? <!-- 依賴(lài)注入數(shù)據(jù)源,注入的正是上文中定義的dataSource -->
??????? <property name="dataSource" ref="dataSource"/>
??????? <!-- mappingResources屬性用來(lái)列出全部映射文件 -->
??????? <property name="mappingResources">
??????????? <list>
??????????????? <!-- 以下用來(lái)列出所有的PO映射文件 -->
??????????????? <value>lee/Person.hbm.xml</value>
??????????? </list>
??????? </property>
????????? <!-- 定義Hibernate的SessionFactory屬性 -->
??????? <property name="hibernateProperties">
??????? ???? <props>
??????????????? <!-- 指定Hibernate的連接方言 -->
??????????? ??? <prop key="hibernate.dialect">org.hibernate.dialect.
??????????????? MySQLDialect</prop>
??????????????? <!-- 指定啟動(dòng)應(yīng)用時(shí),是否根據(jù)Hibernate映射文件創(chuàng)建數(shù)據(jù)表 -->
??????????? ????? <prop key="hibernate.hbm2ddl.auto">update</prop>
??????? ???? </props>
??????? </property>
??? </bean>
??? <!-- 配置Person持久化類(lèi)的DAO bean -->
??? <bean id="personDao" class="lee.PersonDaoImpl">
??? ??? <!-- 采用依賴(lài)注入來(lái)傳入SessionFactory的引用 -->
??????? <property name="sessionFactory" ref="sessionFactory"/>
??? </bean>
</beans>
在PersonDao組件中,所有的持久化操作都通過(guò)HibernateTemplate實(shí)例完成,而HibernateTemplate操作數(shù)據(jù)庫(kù)非常簡(jiǎn)潔,大部分CRUD操作都可通過(guò)一行代碼解決問(wèn)題。下面介紹如何通過(guò)HibernateTemplate進(jìn)行持久層訪問(wèn)。
HibernateTemplate提供了非常多的常用方法來(lái)完成基本的操作,比如通常的增加、刪除、修改、查詢(xún)等操作,Spring 2.0更增加了對(duì)命名SQL查詢(xún)的支持,也增加了對(duì)分頁(yè)的支持。大部分情況下,使用Hibernate的常規(guī)用法,就可完成大多數(shù)DAO對(duì)象的CRUD操作。下面是HibernateTemplate的常用方法簡(jiǎn)介:
?? ● void delete(Object entity),刪除指定持久化實(shí)例。
?? ● deleteAll(Collection entities),刪除集合內(nèi)全部持久化類(lèi)實(shí)例。
?? ● find(String queryString),根據(jù)HQL查詢(xún)字符串來(lái)返回實(shí)例集合。
?? ● findByNamedQuery(String queryName),根據(jù)命名查詢(xún)返回實(shí)例集合。
?? ● get(Class entityClass, Serializable id),根據(jù)主鍵加載特定持久化類(lèi)的實(shí)例。
?? ● save(Object entity),保存新的實(shí)例。
?? ● saveOrUpdate(Object entity),根據(jù)實(shí)例狀態(tài),選擇保存或者更新。
?? ● update(Object entity),更新實(shí)例的狀態(tài),要求entity是持久狀態(tài)。
?? ● setMaxResults(int maxResults),設(shè)置分頁(yè)的大小。
下面是一個(gè)完整DAO類(lèi)的源代碼:
public class PersonDaoImpl implements PersonDao
{
??? //執(zhí)行持久化操作的HibernateTemplate實(shí)例
??? private HibernateTemplate ht = null;
??? private SessionFactory sessionFactory;
??? //該DAO組件持久化操作所需的SessionFactory對(duì)象
??? public void setSessionFactory(SessionFactory sessionFactory)
??? {
??????? this.sessionFactory = sessionFactory;
??? }
??? //用于根據(jù)SessionFactory實(shí)例返回HibernateTemplate實(shí)例的方法
??? private HibernateTemplate getHibernateTemplate()
??? {
??????? if (ht == null)
??????? {
??????????? ht = new HibernateTemplate(sessionFactory);
??????? }
??????? return ht;
??? }
??? /**
???? * 加載人實(shí)例
???? * @param id 需要加載的Person實(shí)例的主鍵值
???? * @return 返回加載的Person實(shí)例
???? */
??? public Person get(int id)
??? {
??????? return (Person)getHibernateTemplate().get(Person.class, new
??????? Integer(id));
??? }
??? /**
???? * 保存人實(shí)例
???? * @param person 需要保存的Person實(shí)例
???? */???
??? public void save(Person person)
??? {
??????? getHibernateTemplate().save(person);
??? }
??? /**
???? * 修改Person實(shí)例
???? * @param person 需要修改的Person實(shí)例
???? */
??? public void update(Person person)
??? {
??????? getHibernateTemplate().update(person);
??? }
??? /**
???? * 刪除Person實(shí)例
???? * @param id 需要?jiǎng)h除的Person的id
???? */
??? public void delete(int id)
??? {
??????? getHibernateTemplate().delete(getHibernateTemplate().get(Person.
??????? class,new Integer(id)));
??? }
??? /**
???? * 刪除Person實(shí)例
???? * @param person 需要?jiǎng)h除的Person實(shí)例
???? */
?? public void delete(Person person)
??? {
??????? getHibernateTemplate().delete(person);
??? }
??? /**
???? * 根據(jù)用戶(hù)名查找Person
???? * @param name 用戶(hù)名
???? * @return 用戶(hù)名對(duì)應(yīng)的全部用戶(hù)
???? */
??? public List findByName(String name)
??? {
??????? return getHibernateTemplate().find("from Person p where p.name
??????? like ?" , name);
??? }
??? /**
??? * 返回全部的Person實(shí)例
??? * @return 全部的Person實(shí)例
??? */
??? public List findAllPerson()
??? {
??????? return getHibernateTemplate().find("from Person ");
??? }
}
通過(guò)上面實(shí)現(xiàn)DAO組件的代碼可以看出,通過(guò)HibernateTemplate進(jìn)行持久層訪問(wèn)的代碼如此清晰,大部分CRUD操作一行代碼即可完成,完全無(wú)須Hibernate訪問(wèn)那些繁瑣的步驟。而且,一旦DAO組件獲得了SessionFactory的引用,即可很輕易地創(chuàng)建HibernateTemplate實(shí)例。
提示:HibernateTemplate是Spring眾多模板工具類(lèi)之一,Spring正是通過(guò)這種簡(jiǎn)便地封裝,完成了開(kāi)發(fā)中大量需要重復(fù)執(zhí)行的工作。