Spring抽象的DAO體系兼容多種數據訪問技術,它們各有特點,各有千秋。像Hibernate是很是優秀的ORM實現方案,但對底層SQL的控制不 太方便;而iBatis則經過模板化技術讓你方便地控制SQL,但沒有Hibernate那樣高的開發效率;自由度最高的固然是直接使用Spring JDBC了,但它也是底層的,靈活的代價是代碼的繁複。很難說哪一種數據訪問技術是最優秀的,只有在某種特定的場景下才能給出答案。因此在一個應用中,每每 採用多個數據訪問技術:通常是兩種,一種採用ORM技術框架,而另外一種採用偏JDBC的底層技術,二者珠聯璧合,造成聯合軍種,共同禦敵。
可是,這種聯合軍種如何應對事務管理的問題呢?咱們知道Spring爲每種數據訪問技術提供了相應的事務管理器,難道須要分別爲它們配置對應的事務管理器嗎?它們究竟是如何協做和工做的呢?這些層出不窮的問題每每壓制了開發人員使用聯合軍種的想法。
其實,在這個問題上,咱們低估了Spring事務管理的能力。若是你採用了一個高端ORM技術(Hibernate、JPA、JDO),同時採用一個 JDBC技術(Spring JDBC、iBatis),因爲前者的會話(Session)是對後者鏈接(Connection)的封裝,Spring會「足夠智能地」在同一個事務線 程讓前者的會話封裝後者的鏈接。因此,咱們只要直接採用前者的事務管理器就能夠了。表10-1給出了混合數據訪問技術框架所對應的事務管理器。
|
序 號 |
混合數據訪問技術框架 |
事務管理器 |
|
1 |
Hibernate+ Spring JDBC或iBatis |
org.springframework.orm.hibernate3.HibernateTransactionManager |
|
2 |
JPA+Spring JDBC或iBatis |
org.springframework.orm.jpa.JpaTransactionManager |
|
3 |
JDO+Spring JDBC或iBatis |
org.springframework.orm.jdo.JdoTransactionManager |
Hibernate+Spring JDBC混合框架的事務管理
因爲通常不會出現同時使用多個ORM框架的狀況(如Hibernate+JPA),咱們不擬對此命題展開論述,只重點研究ORM框架+JDBC框架的情 況。Hibernate+Spring JDBC多是被使用得最多的組合,本節咱們經過實例觀察事務管理的運做狀況。 java
package com.baobaotao.mixdao; … @Service("userService") public class UserService extends BaseService { @Autowired private HibernateTemplate hibernateTemplate; @Autowired private ScoreService scoreService; public void logon(String userName) { //①經過Hibernate技術訪問數據 System.out.println("before updateLastLogonTime().."); updateLastLogonTime(userName); System.out.println("end updateLastLogonTime().."); //②經過JDBC技術訪問數據 System.out.println("before scoreService.addScore().."); scoreService.addScore(userName, 20); System.out.println("end scoreService.addScore().."); } public void updateLastLogonTime(String userName) { User user = hibernateTemplate.get(User.class,userName); user.setLastLogonTime(System.currentTimeMillis()); hibernateTemplate.update(user); //③這句很重要,請看下文的分析 hibernateTemplate.flush(); } }
在①處,使用Hibernate操做數據,而在②處調用ScoreService#addScore(),該方法內部使用Spring JDBC操做數據。
在③處,咱們顯式調用了flush()方法,將Session中的緩存同步到數據庫中(即立刻向數據庫發送一條更新記錄的SQL語句)。之因此要顯式執行 flush()方法,緣由是在默認狀況下,Hibernate對數據的更改只是記錄在一級緩存中,要等到事務提交或顯式調用flush()方法時纔會將一 級緩存中的數據同步到數據庫中,而提交事務的操做發生在 logon()方法返回前。若是全部針對數據庫的更改操做都使用Hibernate,這種數據同步的延遲機制並不會產生任何問題。可是,咱們在 logon()方法中同時採用了Hibernate和Spring JDBC混合數據訪問技術,Spring JDBC沒法自動感知Hibernate一級緩存,因此若是不及時調用flush()方法將記錄數據更改的一級緩存同步到數據庫中,則②處經過 Spring JDBC進行數據更改的結果將被Hibernate一級緩存中的更改覆蓋掉,由於Hibernate一級緩存要等到logon()方法返回前才同步到數據 庫!
ScoreService使用Spring JDBC數據訪問技術,其代碼以下所示: mysql
package com.baobaotao.mixdao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.apache.commons.dbcp.BasicDataSource; @Service("scoreService") public class ScoreService extends BaseService{ @Autowired private JdbcTemplate jdbcTemplate; public void addScore(String userName, int toAdd) { String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?"; jdbcTemplate.update(sql, toAdd, userName); BasicDataSource basicDataSource = (BasicDataSource) jdbcTemplate.getDataSource(); //①查看此處數據庫激活的鏈接數量 System.out.println("[scoreUserService.addScore]激活鏈接數量:" +basicDataSource.getNumActive()); } }
Spring關鍵的配置文件代碼以下所示: spring
… <!--①使用Hibernate事務管理器 --> <bean id="hiberManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" p:sessionFactory-ref="sessionFactory"/> <!--②使UserService及ScoreService的公用方法都擁有事務 --> <aop:config proxy-target-class="true"> <aop:pointcut id="serviceJdbcMethod" expression="within(com.baobaotao.mixdao.BaseService+)"/> <aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="hiberAdvice"/> </aop:config> <tx:advice id="hiberAdvice" transaction-manager="hiberManager"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice> </beans>
啓動Spring容器,執行UserService#logon()方法,能夠查看到以下的執行日誌:sql
引用
before userService.logon()..
①在執行userService.logon()後,Spring開啓一個事務
Creating new transaction with name
[com.baobaotao.mixdao.UserService.logon]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
opened session at timestamp: 13009379637
Opened new Session [org.hibernate.impl.SessionImpl@c5f468] for Hibernate transaction
…
Exposing Hibernate transaction as JDBC transaction [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost , MySQL-AB JDBC Driver]
before userService.updateLastLogonTime()..
②userService.updateLastLogonTime()執行時自動綁定到①處開啓的Session中
Found thread-bound Session for HibernateTemplate
loading entity: [com.baobaotao.User#tom]
about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
…
about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
Not closing pre-bound Hibernate Session after HibernateTemplate
end updateLastLogonTime()..
before scoreService.addScore()..
③scoreService.addScore()執行時綁定到①處開啓的Session中,並加入其所對應的事務中
Found thread-bound Session
[org.hibernate.impl.SessionImpl@c5f468] for Hibernate
transaction
Participating in existing transaction
…
SQL update affected 1 rows
④此時數據源只打開了一個鏈接
[scoreUserService.addScore]激活鏈接數量:1
end scoreService.addScore()..
Initiating transaction commit
⑤提交Hibernate的事務,它將觸發一級緩存到數據庫的同步
Committing Hibernate transaction on Session
[org.hibernate.impl.SessionImpl@c5f468]
commit
processing flush-time cascades
dirty checking collections
Flushed: 0 insertions, 0 updates, 0 deletions to 1 objects
Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections
listing entities:
com.baobaotao.User{lastLogonTime=1300937963882, score=10, userName=tom, password=123456}
re-enabling autocommit
⑥提效Session底層所綁定的JDBC Connection所對應的事務
committed JDBC Connection
transaction completed on session with on_close connection release mode; be sure to close the session to release JDBC resources!
Closing Hibernate Session [org.hibernate.impl.SessionImpl@c5f468] after transaction
Closing Hibernate Session
releasing JDBC connection [ (open PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)]
transaction completed on session with on_close connection release mode; be sure to close the session to release JDBC resources!
after userService.logon()..
仔細觀察這段輸出日誌,在①處UserService#logon()開啓一個新的事務。②處的UserService# updateLastLogonTime() 綁定到事務上下文的Session中。③處ScoreService#addScore()方法加入到①處開啓的事務上下文中。④處的輸出是 ScoreService #addScore()方法內部的輸出信息,彙報此時數據源激活的鏈接數爲1,這清楚地告訴咱們Hibernate和JDBC這兩種數據訪問技術在同一事 務上下文中「共用」一個鏈接。在⑤處,提交Hibernate事務,接着在⑥處觸發調用底層的Connection提交事務。
從以上的運行結果,咱們能夠得出這樣的結論:使用Hibernate事務管理器後,能夠混合使用Hibernate和Spring JDBC數據訪問技術,它們將工做於同一事務上下文中。可是使用Spring JDBC訪問數據時,Hibernate的一級或二級緩存得不到同步,此外,一級緩存延遲數據同步機制可能會覆蓋Spring JDBC數據更改的結果。
因爲混合數據訪問技術方案存在「事務同步而緩存不一樣步」的狀況,因此最好用Hibernate進行讀寫操做,而只用Spring JDBC進行讀操做。如用Spring JDBC進行簡要列表的查詢,而用Hibernate對查詢出的數據進行維護。
若是確實要同時使用Hibernate和Spring JDBC讀寫數據,則必須充分考慮到Hibernate緩存機制引起的問題:必須總體分析數據維護邏輯,根據須要及時調用Hibernate的 flush()方法,以避免覆蓋Spring JDBC的更改,在Spring JDBC更改數據庫時,維護Hibernate的緩存。因爲方法調用順序的不一樣均可能影響數據的同步性,所以很容易發生問題,這會極大提升數據訪問程序的 複雜性。因此筆者鄭重建議不要同時使用Spring JDBC和Hibernate對數據進行寫操做。
能夠將以上結論推廣到其餘混合數據訪問技術的方案中,如Hibernate+iBatis、JPA+Spring JDBC、JDO+Spring JDBC等。
注:以上內容摘自《Spring 3.x企業應用開發實戰》