Spring的事務管理

 

 

雖然我們在用Spring的事務管理,但總體感覺使用的時候未知(未搞懂)的概念還是挺多的,心裏總是沒有底,在使用的過程中也比較容易用錯誤的方式來實現。
 
關於Spring聲明式事務,具體可以參考下面這篇文章:
 
 
目前我們使用的是第五種方式,即全註解方式,這種方式使用比較方便,在spring的配置文件中聲明:
 
......
    <context:annotation-config />
    <context:component-scan base-package="com.bluesky" />

    <tx:annotation-driven transaction-manager="transactionManager" />

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="configLocation" value="classpath:hibernate.cfg.xml" />
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean>

    <!-- 定義事務管理器(聲明式的事務) -->
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
 
 
在Service層中需要加入事務的地方加入:
 
@Transactional
@Component("userDao")
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
    public List<User> listUsers() {
        return this.getSession().createQuery("from User").list();
    }
    ......
}
 
 
我們使用@Transactional的時候,都是使用其默認值,但其中的行爲如下:
 

傳播屬性

 
@Transactional註解中,默認的傳播屬性可以在源碼中看到,屬於PROPAGATION_REQUIRED--支持當前事務,如果當前沒有事務,就新建一個事務,這是最常見的選擇。
 
Propagation propagation() default Propagation.REQUIRED;
 
 
  • PROPAGATION_REQUIRED--支持當前事務,如果當前沒有事務,就新建一個事務。這是最常見的選擇。
  • PROPAGATION_SUPPORTS--支持當前事務,如果當前沒有事務,就以非事務方式執行。
  • PROPAGATION_MANDATORY--支持當前事務,如果當前沒有事務,就拋出異常。
  • PROPAGATION_REQUIRES_NEW--新建事務,如果當前存在事務,把當前事務掛起。
  • PROPAGATION_NOT_SUPPORTED--以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
  • PROPAGATION_NEVER--以非事務方式執行,如果當前存在事務,則拋出異常。
 
此外,在Spring中額外提供了另外一個事務管理方式,在EJB中並未對應,只在Spring的事務管理中存在:
 
PROPAGATION_NESTED:理解Nested的關鍵是savepoint。他與PROPAGATION_REQUIRES_NEW的區別是,PROPAGATION_REQUIRES_NEW另起一個事務,將會與他的父事務相互獨立,而Nested的事務和他的父事務是相依的,他的提交是要等和他的父事務一塊提交的。也就是說,如果父事務最後回滾,他也要回滾的,而Nested事務的好處是他有一個savepoint。
 
ServiceA {
    //事務屬性配置爲 PROPAGATION_REQUIRED
    void methodA() {
        try {
            //savepoint
            ServiceB.methodB(); //PROPAGATION_NESTED 級別
        } catch (SomeException) {
            // 執行其他業務, 如 ServiceC.methodC();
        }
    }
}
 
 
也就是說ServiceB.methodB失敗回滾,那麼ServiceA.methodA也會回滾到savepoint點上,ServiceA.methodA可以選擇另外一個分支,比如ServiceC.methodC,繼續執行,來嘗試完成自己的事務。但是這個事務並沒有在EJB標準中定義。
 
問題:我們當前的所有查詢其實都使用的默認傳播屬性REQUIRED,其實是不合理的,這會造成性能的略微下降;
 

 隔離級別

 
事務的隔離級別屬於數據庫的支持,通常分爲下面幾類,由隔離級別從低到高: 
 
  1. ISOLATION_DEFAULT:這是一個PlatfromTransactionManager默認的隔離級別,使用數據庫默認的事務隔離級別。另外四個與JDBC的隔離級別相對應
  2. ISOLATION_READ_UNCOMMITTED:這是事務最低的隔離級別,它充許令外一個事務可以看到這個事務未提交的數據。這種隔離級別會產生髒讀,不可重複讀和幻像讀。
  3. ISOLATION_READ_COMMITTED: 保證一個事務不能讀到另一個並行事務已修改但未提交的數據。數據提交後才能被讀取。避免了髒數據。該級別適應於大多數系統。大多數主流數據庫默認的級別。
  4. ISOLATION_REPEATABLE_READ:它除了保證一個事務不能讀取另一個事務未提交的數據外,還保證了避免下面的情況產生(不可重複讀)。避免了髒讀,不可重複讀。但是可能出現幻像讀。但是也帶來更多的性能損失。
  5. ISOLATION_SERIALIZABLE 事務被處理爲順序執行。除了防止髒讀,不可重複讀外,還避免了幻像讀。這是花費最高代價但是最可靠的事務隔離級別。
 
我們當前的數據庫隔離級別,如果沒有設置,就爲Isolation.DEFAULT,定義顯示爲使用底層數據源的默認隔離級別。除非DBA有額外設置,對於MySQL InnoDB引擎默認是可重複讀的(REPEATABLE READ)
 
/**
     * Use the default isolation level of the underlying datastore.
     * All other levels correspond to the JDBC isolation levels.
     * @see java.sql.Connection
     */
    int ISOLATION_DEFAULT = -1;
 
 

READ-ONLY

 
對於@Transactional註解中的read-only屬性,之前感覺我們的理解都不算太透徹,可以參考下面這篇文章:
 
 
其概念爲:從這一點設置的時間點開始(時間點a)到這個事務結束的過程中,其他事務所提交的數據,該事務將看不見!(查詢中不會出現別人在時間點a之後提交的數據);
 
對於只讀查詢,可以指定事務類型爲readonly,即只讀事務,據同事講,這個時候可以在其中操縱數據庫,但是並不會commit到數據源中。
 

關於只讀事務和無事務

 
那麼問題來了,什麼時候使用只讀事務,什麼時候將事務的傳播屬性改爲SUPPORT?
 
由於只讀事務不存在數據的修改,因此數據庫將會爲只讀事務提供一些優化手段,例如Oracle對於只讀事務,不啓動回滾段,不記錄回滾log。
 
但是如果連事務都不啓動,從理論上講性能應該更佳,參考其中的一篇blog來講述這個問題:
 
  • 如果你一次執行單條查詢語句,則沒有必要啓用事務支持,數據庫默認支持SQL執行期間的讀一致性;  
  • 如果你一次執行多條查詢語句,例如統計查詢,報表查詢,在這種場景下,多條查詢SQL必須保證整體的讀一致性,否則,在前條SQL查詢之後,後條SQL查詢之前,數據被其他用戶改變,則該次整體的統計查詢將會出現讀數據不一致的狀態,此時,應該啓用事務支持。 【注意是一次執行多次查詢來統計某些信息,這時爲了保證數據整體的一致性,要用只讀事務】

 多數據源

 
如果多個數據源之間不存在分佈式事務的話(即此時不會跨數據源對某個服務執行增刪改等操作,查詢無所謂),數據源之間可以採用直接在spring的配置文件中配置多個數據源,然後分別進行事務的配置。
 
 
在單數據源時,如果事務配置類型爲SUPPORTS且沒有嵌套在事務級別中,無論成功執行與否,方法都不會報錯,只是無法進行數據庫的增刪改操作。
 
如果沒有配置事務(服務上沒有定義@Transactional註解),則會執行每一步就提交,異常不會影響之前已經提交的修改。
 
如果設置成REQUIRED,出現異常會導致整體事務的回退,如果不出現異常,一起提交。
 
 
比如我們有兩個數據源,一個爲master,一個爲slave,這時就需要聲明將兩個transactionManager納入事務管理:
<tx:annotation-driven transaction-manager="transactionManager"/>
<tx:annotation-driven transaction-manager="transactionManagerSlave"/>
 
在其中定義兩套dataSource, transactionManager, sqlSessionFactory,最重要的是兩個MapperScannerConfigurer,通過這個能夠正確區分出mapper包使用的事務:
 
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.api.example.pub.dao"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
 
 
這就要求我們要將不同數據源之間使用的Mapper接口,實體類型,以及mybatis mapper文件隔離,以數據源區分。
 
但此時使用註解聲明式事務還測試,還是不能保證正確的事務已經被加上,這是因爲當存在多個TransactionManager時,Spring貌似默認使用第一個定義的(加載的)事務管理器,其不知道到底該使用哪個事務管理器。
 
@Transactional註解加入了一個value屬性,用來手動來指定具體使用的事務管理器,可以參考:http://stackoverflow.com/questions/3333133/multiple-transaction-managers-with-transactional-annotation
 
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

	/**
	 * A qualifier value for the specified transaction.
	 * <p>May be used to determine the target transaction manager,
	 * matching the qualifier value (or the bean name) of a specific
	 * {@link org.springframework.transaction.PlatformTransactionManager}
	 * bean definition.
	 */
	String value() default "";
 
加入該屬性,就可以正常使用@Transactional對應的事務管理器了,需要注意的是,這個我們所說的多個數據源管理並沒有涉及到分佈式事務,如果需要分佈式事務的話這種配置方式肯定是不支持的。 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章