?
spring + hibernate 數據話持久層 [轉]
spring?+?hibernate?數據話持久層(轉)? ? 張利海?發表 ??? 對于J2EE?應用程序而言,事務的處理一般有兩種模式: 1.?依賴特定事務資源的事務處理 這是應用開發中最常見的模式,即通過特定資源提供的事務機制進行事務管理。 ????????如通過JDBC、JTA?的rollback、commit方法;Hibernate?Transaction?的rollback、commit方法等。這種方法大家已經相當熟悉。 2.?依賴容器的參數化事務管理 通過容器提供的集約式參數化事務機制,實現事務的外部管理,如EJB?中的事務管理模式。 ????????如,下面的EJB事務定義中,將SessionBean?MySession的doService方 法定義為Required。也就是說,當MySession.doServer?方法被某個線程調用時,容器將此線程納入事務管理容器,方法調用過程中如果發生異常,當前事務將被容器自動回滾,如果方法正常結束,則容器將自動提交當前事務。 <container-transaction?> <method?> <ejb-name>MySession</ejb-name> <method-intf>Remote</method-intf> <method-name>doService</method-name> <method-params> <method-param>java.lang.String</method-param> </method-params> </method> <trans-attribute>Required</trans-attribute> </container-transaction> 容器管理的參數化事務為程序開發提供了相當的靈活性,同時因為將事務委托給容器進行管理,應用邏輯中無需再編寫事務代碼,大大節省了代碼量(特別是針對需要同時操作多個事務資源的應用),從而提高了生產率。然而,使用EJB?事務管理的代價相當高昂,撇開EJB?容器不菲的價格,EJB的學習成本,部署、遷移、維護難度,以及容器本身帶來的性能開銷(這往往意味著需要更高的硬件配置)都給我們帶來了相當的困惑。此時事務管理所帶來的優勢往往還不能抵消上面這些負面影響。 Spring事務管理能給我們帶來什么? 下面這段xml配置片斷展示了Spring中的事務設定方式: <beans> <bean?id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property?name="driverClassName"> <value>org.gjt.mm.mysql.Driver</value> </property> <property?name="url"> <value>jdbc:mysql://localhost/sample</value> </property> <property?name="username"> <value>user</value> </property> <property?name="password"> <value>mypass</value> </property> </bean> <bean?id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTr ansactionManager"> <property?name="dataSource"> <ref?local="dataSource"?/> </property> </bean> <bean?id="userDAO"?class="net.xiaxin.dao.UserDAO"> <property?name="dataSource"> <ref?local="dataSource"?/> </property> </bean> SpringFrameWork?Developer’s?Guide?Version?0.6 October?8,?2004?So?many?open?source?projects.?Why?not?Open?your?Documents? <bean?id="userDAOProxy" class="org.springframework.transaction.interceptor.Tran sactionProxyFactoryBean"> <property?name="transactionManager"> <ref?bean="transactionManager"?/> </property> <property?name="target"> <ref?local="userDAO"?/> </property> <property?name="transactionAttributes"> <props> <prop?key="insert*">PROPAGATION_REQUIRED</prop> <prop?key="get*"> PROPAGATION_REQUIRED,readOnly </prop> </props> </property> </bean> </beans> 配置中包含了dataSource,transactionManager?等資源定義。這些資源都為一個名為userDAOProxy?的TransactionProxyFactoryBean?服務,?而userDAOProxy?則對包含實際數據邏輯的userDAO進行了事務性封裝。 可以看到,在userDAOProxy?的"transactionAttributes"屬性中,我們定義了針對userDAO?的事務策略,即將所有名稱以insert?開始的方法(如UserDAO.insertUser方法)納入事務管理范圍。如果此方法中拋出異常,則Spring 將當前事務回滾,如果方法正常結束,則提交事務。而對所有名稱以get?開始的方法(如UserDAO.getUser?方法)則以只讀的事務處理機制進行處理。(設為只讀型事務,可以使持久層嘗試對數據操作進行優化,如對 于只讀事務Hibernate將不執行flush操作,而某些數據庫連接池和JDBC?驅動也對只讀型操作進行了特別化。) ????????結合上面這段申明帶來的感性認知,看看Spring?的事務管理機制與EJB?中事務管理有何不同,或者有何優勢。這里自然有許多方面可以比較,不過,筆者認為其中最為關鍵的兩點是: 1.?Spring可以將任意Java?Class?納入事務管理 這里的UserDAO只是我們編寫的一個普通Java?Class,其中包含了一些基本的數據應用邏輯。通過Spring,我們即可簡單的實現事務的可配置化。也就是說,我們可以隨意為某個類的某個方法指定事務管理機制。與之對比,如果使用EJB容器提供的事務管理功能,我們不得不按照EJB規范編將UserDAO?進行改造,將其轉換為一個標準的EJB。 2.?Spring事務管理并不依賴特定的事務資源。 EJB?容器必須依賴于JTA?提供事務支持。而Spring?的事務管理則支持JDBC、JTA?等多種事務資源。這為我們提供了更多的選擇,從而也使得我們的系統部署更加靈活。 ????????對Spring事務管理機制進行簡單分析之后,我們將結合持久層封裝的具體事務應用機制,對Spring中的事務管理進行更具實效的探討。 ??????????????????????????????????????????????????????????????????????持久層封裝 ???????????????????????????????????????????????????????????????????????????JDBC Spring對JDBC進行了良好的封裝,通過提供相應的模板和輔助類,在相當程度上降低了JDBC操作的復雜性。并且得益于Spring良好的隔離設計,JDBC封裝類庫可以脫離Spring?Context獨立使用,也就是說,即使系統并沒有采用Spring作為結構性框架,我們也可以單獨使用Spring的JDBC部分(spring-dao.jar)來改善我們的代碼。作為對比,首先讓我們來看一段傳統的JDBC代碼: Connection?conn?=null; Statement?stmt?=?null; try?{ conn?=?dataSource.getConnection(); stmt?=?con.createStatement(); stmt.executeUpdate("UPDATE?user?SET?age?=?18?WHERE?id?=?'erica'" ![]() }?finally?{ if?(stmt?!=?null)?{ try?{ stmt.close(); }?catch?(SQLException?ex)?{ logger.warn("Exception?in?closing?JDBC?Statement",?ex); } } if?(conn?!=?null)?{ try?{ conn.close(); }?catch?(SQLException?ex)?{ logger.warn("Exception?in?closing?JDBC?Connection",?ex); } } } 類似上面的代碼非常常見。為了執行一個SQL語句,我們必須編寫22行代碼,而其中21行與應用邏輯并無關聯,并且,這樣的代碼還會在系統其他地方(也許是每個需要數據庫訪問的地方)重復出現。 于是,大家開始尋找一些設計模式以改進如此的設計,Template模式的應用是其中一種典型的改進方案。Spring的JDBC封裝,很大一部分就是借助Template模式實現,它提供了一個優秀的JDBC模板庫,借助這個工具,我們可以簡單有效的對傳統的JDBC編碼方式加以改進。下面是借助Spring?JDBC?Template修改過的代碼,這段代碼完成了與上面代碼相同的功能。 JdbcTemplate?jdbcTemplate?=?new?JdbcTemplate(dataSource); jdbcTemplate.update("UPDATE?user?SET?age?=?10?WHERE?id?=?'erica'" ![]() 可以看到,兩行代碼完成了上面需要19行代碼實現的功能。所有冗余的代碼都通過合理的抽象匯集到了JdbcTemplate中。無需感嘆,借助Template模式,我們大致也能實現這樣一個模板,不過,Spring的設計 者已經提前完成了這一步驟。org.springframework.jdbc.core.JdbcTemplate中包含了這個模板實現的代碼,經過Spring設計小組精心設計,這個實現可以算的上是模板應用的典范。特別是回調(CallBack)的使用,使得整個模板結構清晰高效。值得一讀。 Tips:實際開發中,可以將代碼中硬編碼的SQL語句作為Bean的一個String類型屬性,借助DI機制在配置文件中定義,從而實現SQL的參數化配置。 再對上面的例子進行一些改進,通過PrepareStatement執行update操作以避免SQL Injection?漏洞?9: JdbcTemplate?jdbcTemplate?=?new?JdbcTemplate(dataSource); jdbcTemplate .update( "UPDATE?user?SET?age?=???WHERE?id?=??", new?PreparedStatementSetter()?{ public?void?setValues(PreparedStatementSetter?ps) throws?SQLException?{ ps.setInt(1,?18); ps.setString(2,?"erica" ![]() } } ); 可以看到,上面引用了update方法的另一個版本,傳入的參數有兩個,第一個用于創建 PreparedStatement的SQL。第二個參數是為PreparedStatement設定參數的PreparedStatementSetter。 第二個參數的使用方法比較獨到,我們動態新建了一個PreparedStatementSetter類,并實現了這個抽象類的setValues方法。之后將這個類的引用作為參數傳遞給update。update接受參數之后,即可調用第二個參數提供的方法完成PreparedStatement的初始化。 Spring?JDBC?Template中大量使用了這樣的Callback機制,這帶來了極強的靈活性和擴展性。 上面演示了update方法的使用(同樣的操作適用于update、insert、delete)。下面是一個查詢的示例。 final?List?userList?=?new?ArrayList(); JdbcTemplate?jdbcTemplate?=?new?JdbcTemplate(dataSource); jdbcTemplate .query( "SELECT?name,?sex,?address?FROM?user?WHERE?age?>?18", 9?SQL?Injection:?SQL語句中直接引入參數值而導致的系統漏洞,具體請參見以下論文: ![]() SpringFrameWork?Developer’s?Guide?Version?0.6 October?8,?2004?So?many?open?source?projects.?Why?not?Open?your?Documents? new?RowCallbackHandler()?{ public?void?processRow(ResultSet?rs)?throws?SQLException?{ User?user?=?new?User(); user.setId(rs.getString("name" ![]() user.setSex(rs.getString("sex" ![]() user.setAddress(rs.getString("address" ![]() userList.add(product); } } ); 這里傳入query方法的有兩個參數,第一個是Select查詢語句,第二個是一個RowCallbackHandler實例,我們通過RowCallbackHandler對Select語句得到的每行記錄進行解析,并為其創建一個User數據對象。實現了手動的OR映射。此外,我們還可以通過JdbcTemplate.call方法調用存儲過程。query、update方法還有其他很多不同參數版本的實現,具體調用方法請參見SpringJavaDoc。 ????????????????????????????????????????????????????????????????? ??????????????????????????????????????????????????????????????JdbcTemplate與事務 上例中的JdbcTemplate操作采用的是JDBC默認的AutoCommit模式,也就是說我們還無法保證數據操作的原子性(要么全部生效,要么全部無效),如: JdbcTemplate?jdbcTemplate?=?new?JdbcTemplate(dataSource); jdbcTemplate.update("UPDATE?user?SET?age?=?10?WHERE?id?=?'erica'" ![]() jdbcTemplate.update("UPDATE?user?SET?age?=?age+1?WHERE?id?=?'erica'" ![]() 由于采用了AutoCommit模式,第一個update操作完成之后被自動提交,數據庫中”erica”對應的記錄已經被更新,如果第二個操作失敗,我們無法使得整個事務回滾到最初狀態。對于這個例子也許無關緊要,但是對于一個金融帳務系統而言,這樣的問題將導致致命錯誤。 為了實現數據操作的原子性,我們需要在程序中引入事務邏輯,在JdbcTemplate中引入事務機制,在Spring中有兩種方式: 1.?代碼控制的事務管理 2.?參數化配置的事務管理 下面就這兩種方式進行介紹。 u?代碼控制的事務管理 首先,進行以下配置,假設配置文件為(Application-Context.xml): <beans> <bean?id="dataSource" SpringFrameWork?Developer’s?Guide?Version?0.6 October?8,?2004?So?many?open?source?projects.?Why?not?Open?your?Documents? class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property?name="driverClassName"> <value>net.sourceforge.jtds.jdbc.Driver</value> </property> <property?name="url"> <value>jdbc:jtds:sqlserver://127.0.0.1:1433/Sample</value> </property> <property?name="username"> <value>test</value> </property> <property?name="password"> <value>changeit</value> </property> </bean> <bean?id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransac tionManager"> <property?name="dataSource"> <ref?local="dataSource"?/> </property> </bean> <bean?id="userDAO"?class="net.xiaxin.dao.UserDAO"> <property?name="dataSource"> <ref?local="dataSource"?/> </property> <property?name="transactionManager"> <ref?local="transactionManager"?/> </property> </bean> </beans> 配置中包含了三個節點: Ø?dataSource 這里我們采用了apache?dhcp組件提供的DataSource實現,并為其配置了JDBC驅動、數據庫URL、用戶名和密碼等參數。 Ø?transactionManager 針對JDBC?DataSource類型的數據源,我們選用了DataSourceTransactionManager作為事務管理組件。 如果需要使用基于容器的數據源(JNDI),我們可以采用如下配置: SpringFrameWork?Developer’s?Guide?Version?0.6 October?8,?2004?So?many?open?source?projects.?Why?not?Open?your?Documents? <bean?id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property?name="jndiName"> <value>jdbc/sample</value> </property> </bean> <bean?id="transactionManager" class="org.springframework.transaction.jta.JtaTrans actionManager" /> Ø?userDAO 申明了一個UserDAO?Bean,并為其指定了dataSource和transactionManger資源。 UserDAO對應的代碼如下: public?class?UserDAO?{ private?DataSource?dataSource; SpringFrameWork?Developer’s?Guide?Version?0.6 October?8,?2004?So?many?open?source?projects.?Why?not?Open?your?Documents? private?PlatformTransactionManager?transactionManager; public?PlatformTransactionManager?getTransactionManager()?{ return?transactionManager; } public?void?setTransactionManager(PlatformTransactionManager transactionManager)?{ this.transactionManager?=?transactionManager; } public?DataSource?executeTestSource()?{ return?dataSource; } public?void?setDataSource(DataSource?dataSource)?{ this.dataSource?=?dataSource; } public?void?insertUser()?{ TransactionTemplate?tt?= new?TransactionTemplate(getTransactionManager()); tt.execute(new?TransactionCallback()?{ public?Object?doInTransaction(TransactionStatus?status)?{ JdbcTemplate?jt?=?new?JdbcTemplate(executeTestSource()); jt.update( "insert?into?users?(username)?values?('xiaxin');" ![]() jt.update( "insert?into?users?(id,username)?values(2, 'erica');" ![]() return?null; } }); } } 可以看到,在insertUser方法中,我們引入了一個新的模板類: org.springframework.transaction.support.TransactionTemplate。 TransactionTemplate封裝了事務管理的功能,包括異常時的事務回滾,以及操作成功后的事務提交。和JdbcTemplate一樣,它使得我們無需在瑣碎的try/catch/finally代碼中徘徊。 在doInTransaction中進行的操作,如果拋出未捕獲異常將被自動回滾,如果成功執行,?則將被自動提交。 這里我們故意制造了一些異常來觀察數據庫操作是否回滾(通過在第二條語句中更新自增ID字段故意觸發一個異常): 編寫一個簡單的TestCase來觀察實際效果: InputStream?is?=?new?FileInputStream("Application-Context.xml" ![]() XmlBeanFactory?factory?=?new?XmlBeanFactory(is); UserDAO?userDAO?=?(UserDAO)?factory.getBean("userDAO" ![]() userDAO.insertUser(); 相信大家多少覺得上面的代碼有點凌亂,Callback類的編寫似乎也有悖于日常的編程習慣(雖然筆者覺得這一方法比較有趣,因為它巧妙的解決了筆者在早期自行開發數據訪問模板中曾經遇到的問題)。如何進一步避免上面這些問題?Spring?的容器事務管理機制在這里即體現出其強大的能量。u?參數化配置的事務管理在上面的Application-Context.xml增加一個事務代理(UserDAOProxy)配置,同時,由于事務由容器管理,UserDAO不再需要TransactionManager設定,將其移除: <bean?id="UserDAOProxy" class="org.springframework.transaction.interceptor.Transac tionProxyFactoryBean"> <property?name="transactionManager"> <ref?bean="transactionManager"?/> </property> <property?name="target"> <ref?local="userDAO"?/> </property> <property?name="transactionAttributes"> <props> <prop?key="insert*">PROPAGATION_REQUIRED</prop> <prop?key="*">PROPAGATION_REQUIRED,readOnly</prop> </props> </property> </bean> <bean?id="userDAO"?class="net.xiaxin.dao.UserDAO"> <property?name="dataSource"> <ref?local="dataSource"?/> </property> </bean> SpringFrameWork?Developer’s?Guide?Version?0.6 October?8,?2004?So?many?open?source?projects.?Why?not?Open?your?Documents? 上面的配置中,UserDAOProxy節點配置了一個針對userDAO?bean的事務代理(由target屬性指定)。通過transactionAttributes屬性,我們指定了事務的管理策略,即對所有以insert開頭的方法進行事務管理,如果被管理方法拋出異常,則自動回滾方法中的事務,如果成功執行,則在方法完成之后進行事務提交。另一方面對于其他方法(通過通配符*表示),則進行只讀事務管理,以獲得更好的性能。與之對應UserDAO.insertUser的代碼修改如下: public?void?insertUser(RegisterInfo?regInfo)?{ JdbcTemplate?jt?=?new?JdbcTemplate(executeTestSource()); jt.update("insert?into?users?(username)?values?('xiaxin');" ![]() jt.update("insert?into?users?(id,username)?values?(2,'erica');" ![]() } 測試代碼修改如下: InputStream?is?=?new?FileInputStream("Application-Context.xml" ![]() XmlBeanFactory?factory?=?new?XmlBeanFactory(is); //注意這里須通過代理Bean"userDAOProxy"獲得引用,而不是直接getBean(“userDAO”) //此外這里還存在一個有關強制轉型的潛在問題,請參見Hibernate?in?Spring一節后 //關于強制轉型的補充描述。 UserDAO?userDAO?=?(UserDAO)?factory.getBean("userDAOProxy" ![]() userDAO.insertUser(); 可以看到,insertUser變得非常簡潔。數據邏輯清晰可見,對比前面代碼控制的事務管理,以及傳統的JDBC操作,相信大家會有一些霍然開朗的感覺。細心的讀者會說,這只不過將代碼轉移到了配置文件,并沒有減少太多的工作量。這點區別也許并不重要,從應用維護的角度而言,配置化的事務管理顯然更具優勢。何況,實際開發中,如果前期設計細致,方法的事務特性確定之后一般不會發生大的變動,之后頻繁的維護過程中,我們只需面對代碼中的數據邏輯即可。上面我們結合JdbcTemplate介紹了Spring中的模板操作以及事務理機制。Spring作為一個開放式的應用開發平臺。同時也針對其他組件提供了良好的支持。在持久層,Spring提供面向了Hibernate、ibatis和JDO的模板實現,同樣,這些實現也為我們的開發提供了強有力的支持。 下面我們就hibernate、ibatis這兩種主流持久層框架在Spring中的使用進行介紹。至于JDO,由于實際開發中使用并不廣泛(實際上筆者覺得JDO前景堪憂),這里也就不重點介紹,有興趣的讀者可參見Spring-Reference中的相關章節。 SpringFrameWork?Developer’s?Guide?Version?0.6 October?8,?2004?So?many?open?source?projects.?Why?not?Open?your?Documents? Hibernate?in?Spring Hibernate在開源的持久層框架中無疑是近期最為鮮亮的角色,其作者甚至被邀請加入 新版EJB設計工作之中,足見Hibernate設計的精彩貼切。關于Hibernate的使用,在筆者 的另外一篇文檔中進行了探討: 《Hibernate開發指南》? ![]() 下面主要就Hibernate在Spring中的應用加以介紹,關于Hibernate本身就不多加描 述。 另外考慮到Spring對容器事務的良好支持,筆者建議在基于Spring?Framework的應 用開發中,盡量使用容器管理事務,以獲得數據邏輯代碼的最佳可讀性。下面的介紹中,將 略過代碼控制的事務管理部分,而將重點放在參數化的容器事務管理應用。代碼級事務管理 實現原理與上面JdbcTemplate中基本一致,感興趣的讀者可以參見Spring-Reference中 的相關內容。 出于簡潔,我們還是沿用上面的示例。首先,針對Hibernate,我們需要進行如下配置: Hibernate-Context.xml: <beans> <bean?id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property?name="driverClassName"> <value>net.sourceforge.jtds.jdbc.Driver</value> </property> <property?name="url"> <value>jdbc:jtds:sqlserver://127.0.0.1:1433/Sample</value> </property> <property?name="username"> <value>test</value> </property> <property?name="password"> <value>changeit</value> </property> </bean> <bean?id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean" > <property?name="dataSource"> <ref?local="dataSource"?/> </property> <property?name="mappingResources"> <list> <value>net/xiaxin/dao/entity/User.hbm.xml</value> </list> SpringFrameWork?Developer’s?Guide?Version?0.6 October?8,?2004?So?many?open?source?projects.?Why?not?Open?your?Documents? </property> <property?name="hibernateProperties"> <props> <prop?key="hibernate.dialect"> net.sf.hibernate.dialect.SQLServerDialect </prop> <prop?key="hibernate.show_sql"> true </prop> </props> </property> </bean> <bean?id="transactionManager" class="org.springframework.orm.hibernate.HibernateTransactionMana ger"> <property?name="sessionFactory"> <ref?local="sessionFactory"?/> </property> </bean> <bean?id="userDAO"?class="net.xiaxin.dao.UserDAO"> <property?name="sessionFactory"> <ref?local="sessionFactory"?/> </property> </bean> <bean?id="userDAOProxy" class="org.springframework.transaction.interceptor.TransactionPro xyFactoryBean"> <property?name="transactionManager"> <ref?bean="transactionManager"?/> </property> <property?name="target"> <ref?local="userDAO"?/> </property> <property?name="transactionAttributes"> <props> <prop?key="insert*">PROPAGATION_REQUIRED</prop> <prop?key="get*">PROPAGATION_REQUIRED,readOnly</prop> </props> SpringFrameWork?Developer’s?Guide?Version?0.6 October?8,?2004?So?many?open?source?projects.?Why?not?Open?your?Documents? </property> </bean> </beans> 與上面JDBC中的配置相對比,區別主要在于: 1.?SessionFactory的引入 Hibernate中通過SessionFactory創建和維護Session。Spring對SessionFactory的配置也進行了整合,無需再通過Hibernate.cfg.xml對SessionFactory進行設定。 SessionFactory節點的mappingResources屬性包含了映射文件的路徑,list節點下可配置多個映射文件。 hibernateProperties節點則容納了所有的屬性配置。 可以對應傳統的Hibernate.cfg.xml文件結構對這里的SessionFactory配置進行解讀。 2.?采用面向Hibernate的TransactionManager實現: org.springframework.orm.hibernate.HibernateTransactionManag er 可以看到,對于事務管理配置,基本與上一章節中相同。對應剛才的Users表,建立如下映射類: User.java: /** *?@hibernate.class?table="users" */ public?class?User?{ public?Integer?id; public?String?username; public?String?password; /** *?@hibernate.id *?column="id" *?type="java.lang.Integer" *?generator-class="native" */ public?Integer?getId()?{ return?id; SpringFrameWork?Developer’s?Guide?Version?0.6 October?8,?2004?So?many?open?source?projects.?Why?not?Open?your?Documents? } public?void?setId(Integer?id)?{ this.id?=?id; } /** *?@hibernate.property?column="password"?length="50" */ public?String?getPassword()?{ return?password; } public?void?setPassword(String?password)?{ this.password?=?password; } /** *?@hibernate.property?column="username"?length="50" */ public?String?getUsername()?{ return?username; } public?void?setUsername(String?username)?{ this.username?=?username; } } 上面的代碼中,通過xdoclet指定了類/表;屬性/字段的映射關系,通過xdoclet?anttask?我們可以根據代碼生成對應的user.hbm.xml文件。具體細節請參見《hibernate開發指南》一文。 下面是生成的user.hbm.xml: <hibernate-mapping> <class name="net.xiaxin.dao.entity.User" table="users" dynamic-update="false" dynamic-insert="false" > <id name="id" column="id" SpringFrameWork?Developer’s?Guide?Version?0.6 October?8,?2004?So?many?open?source?projects.?Why?not?Open?your?Documents? type="java.lang.Integer" > <generator?class="native"> </generator> </id> <property name="password" type="java.lang.String" update="true" insert="true" access="property" column="password" length="50" /> <property name="username" type="java.lang.String" update="true" insert="true" access="property" column="username" length="50" /> </class> </hibernate-mapping> UserDAO.java: public?class?UserDAO?extends?HibernateDaoSupport?implements?IUserDAO { public?void?insertUser(User?user)?{ getHibernateTemplate().saveOrUpdate(user); } } 看到這段代碼想必會有點詫異,似乎太簡單了一點……,不過這已經足夠。短短一行代碼我們已經實現了與上一章中示例相同的功能,這也正體現了Spring+Hibernate的威力所在。 上面的UserDAO實現了自定義的IUserDAO接口(這里的IUserDAO接口僅包含insertUser方法的定義,不過除此之外,它還有另一層含義,見下面的代碼測試部分), 并擴展了抽象類: HibernateDaoSupport HibernateSupport實現了HibernateTemplate和SessionFactory實例的關聯。與JdbcTemplate類似,HibernateTemplate對Hibernate?Session操作進行了封裝,而HibernateTemplate.execute方法則是一封裝機制的核心,感興趣的讀者可以 研究一下其實現機制。借助HibernateTemplate我們可以脫離每次數據操作必須首先獲得Session實例、啟動事務、提交/回滾事務以及煩雜的try/catch/finally的繁瑣操作。從而獲得以上代碼中精干集中的邏輯呈現效果。 對比下面這段實現了同樣功能的Hibernate原生代碼,想必更有體會: Session?session try?{ Configuration?config?=?new?Configuration().configure(); SessionFactory?sessionFactory?= config.buildSessionFactory(); session?=?sessionFactory.openSession(); Transaction?tx?=?session.beginTransaction(); User?user?=?new?User(); user.setName("erica" ![]() user.setPassword("mypass" ![]() session.save(user); tx.commit(); }?catch?(HibernateException?e)?{ e.printStackTrace(); tx.rollback(); }finally{ session.close(); } 測試代碼: InputStream?is?=?new?FileInputStream("Hibernate-Context.xml" ![]() XmlBeanFactory?factory?=?new?XmlBeanFactory(is); IUserDAO?userDAO?=?(IUserDAO)factory.getBean("userDAOProxy" ![]() SpringFrameWork?Developer’s?Guide?Version?0.6 October?8,?2004?So?many?open?source?projects.?Why?not?Open?your?Documents? User?user?=?new?User(); user.setUsername("erica" ![]() user.setPassword("mypass" ![]() userDAO.insertUser(user); 這段代碼似乎并沒有什么特殊,但有一個細微之處: IUserDAO?userDAO?=?(IUserDAO)factory.getBean("userDAOProxy" ![]() 這里并沒有直接用UserDAO對獲得的Bean實例進行強制轉型。這與上面JdbcTemplate的測試代碼不同。并非完全出自設計上的考慮,這里情況有些特殊,我們可以嘗試一下用UserDAO類對bean實例進行強制轉型,不過將得到一個ClassCastException,程序異常中止。 為什么會出現這樣的問題?是不是只有在使用Hibernate才會出現這樣的問題?事實并非如此,如果對上面基于JdbcTempate的UserDAO進行改造,使之實現IUserDAO接口,同樣的問題也將會出現。IUserDAO接口本身非常簡單(僅包含一個insertUser方法的定義),顯然也不是導致異常的原因所在。原因在于Spring的AOP實現機制,前面曾經提及,Spring中的事務管理實際上是基于動態AOP機制實現,為了實現動態AOP,Spring在默認情況下會使用Java?DynamicProxy,但是,Dynamic?Proxy要求其代理的對象必須實現一個接口,該接口定義了準備進行代理的方法。而對于沒有實現任何接口的Java?Class,需要采用其他方式,Spring通過CGLib10實現這一功能。 當UserDAO沒有實現任何接口時(如JdbcTemplate示例中)。Spring通過CGLib對UserDAO進行代理,此時getBean返回的是一個繼承自UserDAO類的子類實例,可以通過UserDAO對其強制轉型。而當UserDAO實現了IUserDAO接口之后,Spring將通過JavaDynamic?Proxy機制實現代理功能,此時返回的Bean,是通過java.lang.reflect.Proxy.newProxyInstance方法創建的IUserDAO接口的一個代理實現,這個實例實現了IUserDAO接口,但與UserDAO類已經沒有繼承關系,因此無法通過UserDAO強制轉型。由于此問題牽涉到較為底層的代理機制實現原理,下面的AOP章節中我們再進行詳細探討。 實際開發中,應該面向接口編程,通過接口來調用Bean提供的服務。 10?CGLib可以在運行期對Class行為進行修改。由于其功能強大,性能出眾,常常被作為Java?Dynamic?Proxy 之外的動態Proxy模式的實現基礎。在Spring、Hibernate中都用到了CGLib類庫。 |