1. 程式人生 > >Spring JDBC-混合框架的事務管理

Spring JDBC-混合框架的事務管理

組合 manager 延遲 發生 required 應用 conf 一個 研究

  • ?

Spring 抽象的 DAO 體系兼容多種數據訪問技術,它們各有特色,各有千秋。

  • Hibernate 是非常優秀的 ORM 實現方案,但對底層 SQL 的控制不太方便

  • MyBatis 則通過模板化技術讓我們能方便地控制 SQL,但沒有 Hibernate 那樣高的開發效率

  • 自由度最高的當然是直接使用 Spring JDBC 莫屬了,但是它也是最底層的,靈活的代價是代碼的繁復

很難說哪種數據訪問技術是最優秀的,只有在某種特定的場景下,才能給出答案。所以在一個應用中,往往采用多個數據訪問技術:一般是兩種,一種采用 ORM 技術框架,而另一種采用偏 JDBC 的底層技術。


問題

當我們采用:ORM 技術框架+ 偏 JDBC 的底層技術如何應對事務管理的問題呢? 我們知道 Spring 為每種數據訪問技術提供了相應的事務管理器,難道需要分別為它們配置對應的事務管理器嗎?它們到底是如何協作,如何工作的呢?


解決方案

Spring 事務管理的為我們的提供了解決方案。

當我們采用了一個高端 ORM 技術(Hibernate,JPA,JDO),同時采用一個 JDBC 技術(Spring JDBC,MyBatis),由於前者的會話(Session)是對後者連接(Connection)的封裝,Spring 會“足夠智能地”在同一個事務線程讓前者的會話封裝後者的連接。所以,我們只要直接采用前者的事務管理器就可以了。


我們列舉下混合數據訪問技術所對應的事務管理器:

技術分享


示例:Hibernate + Spring JDBC

由於一般不會出現同時使用多個 ORM 框架的情況(如 Hibernate + JPA),我們不擬對此命題展開論述,只重點研究 ORM 框架 + JDBC 框架的情況。

Hibernate + Spring JDBC 可能是被使用得最多的組合,我們通過實例來觀察事物的運行情況。


User 使用了註解聲明的實體類

import javax.persistence.Entity; import javax.persistence.Table; import javax.persistence.Column; import javax.persistence.Id; import java.io.Serializable; @Entity @Table(name="T_USER") public class User implements Serializable{ @Id @Column(name = "USER_NAME") private String userName; private String password; private int score; @Column(name = "LAST_LOGON_TIME") private long lastLogonTime = 0;  
}

UserService 使用 Hibernate 數據訪問技術

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.stereotype.Service; import org.springframework.orm.hibernate3.HibernateTemplate; import org.apache.commons.dbcp.BasicDataSource; import user.User; @Service("userService") public class UserService extends BaseService { @Autowired private HibernateTemplate hibernateTemplate; @Autowired private ScoreService scoreService; public void logon(String userName) {
        System.out.println("logon method...");
        updateLastLogonTime(userName); //①使用Hibernate數據訪問技術 scoreService.addScore(userName, 20); //②使用Spring JDBC數據訪問技術 } public void updateLastLogonTime(String userName) {
        System.out.println("updateLastLogonTime...");
        User user = hibernateTemplate.get(User.class,userName);
        user.setLastLogonTime(System.currentTimeMillis());
        hibernateTemplate.flush(); //③請看下文的分析 }
}

在①處,使用 Hibernate 操作數據,而在②處調用 ScoreService#addScore(),該方法內部使用 Spring JDBC 操作數據。

在③處,我們顯式調用了 flush() 方法,將 Session 中的緩存同步到數據庫中,這個操作將即時向數據庫發送一條更新記錄的 SQL 語句

之所以要在此顯式執行 flush() 方法,原因是:默認情況下,Hibernate 要在事務提交時才將數據的更改同步到數據庫中,而事務提交發生在 logon() 方法返回前。

如果所有針對數據庫的更改都使用 Hibernate,這種數據同步延遲的機制不會產生任何問題。但是,我們在 logon() 方法中同時采用了 Hibernate 和 Spring JDBC 混合數據訪問技術。

Spring JDBC 無法自動感知 Hibernate 一級緩存,所以如果不及時調用 flush() 方法將數據更改同步到數據庫,則②處通過 Spring JDBC 進行數據更改的結果將被 Hibernate 一級緩存中的更改覆蓋掉,武漢試管嬰兒因為,一級緩存在 logon() 方法返回前才同步到數據庫!


ScoreService :使用 Spring JDBC 數據訪問技術

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("scoreUserService") public class ScoreService extends BaseService{ @Autowired private JdbcTemplate jdbcTemplate; public void addScore(String userName, int toAdd) {
        System.out.println("addScore...");
        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("激活連接數量:"+basicDataSource.getNumActive());
    }
}

關鍵配置文件

<!-- 使用Hibernate事務管理器 --> <bean id="hiberManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" p:sessionFactory-ref="sessionFactory"/> <!-- 對所有繼承BaseService類的公用方法實施事務增強 --> <aop:config proxy-target-class="true"> <aop:pointcut id="serviceJdbcMethod" expression="within(com.artisan.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>

日誌:

21:37:57,062 (AbstractPlatformTransactionManager.java:365) - Creating new transaction 
    with name [com.artisan.UserService.logon]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT 21:37:57,093 (SessionImpl.java:220) - opened session at timestamp: 12666407370 21:37:57,093 (HibernateTransactionManager.java:493) - Opened new Session 
    [org.hibernate.impl.SessionImpl@83020] for Hibernate transaction ① 21:37:57,093 (HibernateTransactionManager.java:504) - Preparing JDBC Connection 
    of Hibernate Session [org.hibernate.impl.SessionImpl@83020] 21:37:57,109 (JDBCTransaction.java:54) - begin

…

logon method...
updateLastLogonTime...
… 21:37:57,109 (AbstractBatcher.java:401) - select user0_.USER_NAME as USER1_0_0_, 
    user0_.LAST_LOGON_TIME as LAST2_0_0_, user0_.password as password0_0_, 
    user0_.score as score0_0_ from T_USER user0_ where user0_.USER_NAME=? Hibernate: select user0_.USER_NAME as USER1_0_0_, 
    user0_.LAST_LOGON_TIME as LAST2_0_0_, user0_.password as password0_0_, 
    user0_.score as score0_0_ from T_USER user0_ where user0_.USER_NAME=?

… 21:37:57,187 (HibernateTemplate.java:422) - Not closing pre-bound 
    Hibernate Session after HibernateTemplate 21:37:57,187 (HibernateTemplate.java:397) - Found thread-bound Session
    for HibernateTemplate Hibernate: update T_USER set LAST_LOGON_TIME=?, password=?, score=? where USER_NAME=?

… 2017-09-26 21:37:57,203 DEBUG [main] (AbstractPlatformTransactionManager.java:470) 
    - Participating in existing transaction ②
addScore... 2017-09-26 21:37:57,203 DEBUG [main] (JdbcTemplate.java:785) 
    - Executing prepared SQL update 2017-09-26 21:37:57,203 DEBUG [main] (JdbcTemplate.java:569)
    - Executing prepared SQL statement 
    [UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?] 2017-09-26 21:37:57,203 DEBUG [main] (JdbcTemplate.java:794) 
    - SQL update affected 1 rows

激活連接數量:1 ③ 2017-09-26 21:37:57,203 DEBUG [main] (AbstractPlatformTransactionManager.java:752) 
    - Initiating transaction commit 2017-09-26 21:37:57,203 DEBUG [main] (HibernateTransactionManager.java:652) 
    - Committing Hibernate transaction on Session 
    [org.hibernate.impl.SessionImpl@83020] ④ 2017-09-26 21:37:57,203 DEBUG [main] (JDBCTransaction.java:103) - commit ⑤ 

在①處 UserService#logon() 開啟一個新的事務,
在②處 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 的緩存。

可以將以上結論推廣到其它混合數據訪問技術的方案中,如 Hibernate+MyBatis,JPA+Spring JDBC,JDO+Spring JDBC 等

Spring JDBC-混合框架的事務管理