Spring3數據庫事務管理機制


Spring對事務的解決辦法其實分為2種:編程式實現事務,AOP配置聲明式解決方案。
http://jinnianshilongnian.iteye.com/blog/1496953

Spring提供了許多內置事務管理器實現,常用的有以下幾種:

  • DataSourceTransactionManager:位於org.springframework.jdbc.datasource包中,數據源事務管理器,提供對單個javax.sql.DataSource事務管理,用於Spring JDBC抽象框架、iBATIS框架的事務管理;
  • HibernateTransactionManager:位 於org.springframework.orm.hibernate3或者hibernate4包中,提供對單個 org.hibernate.SessionFactory事務支持,用於集成Hibernate框架時的事務管理;該事務管理器只支持 Hibernate3+版本,且Spring3.0+版本只支持Hibernate 3.2+版本;
  • JtaTransactionManager:位於org.springframework.transaction.jta包中,提供對分布式事務管理的支持,並將事務管理委托給Java EE應用服務器事務管理器;

Spring不僅提供這些事務管理器,還提供對如JMS事務管理的管理器
兩個不依賴於應用服務器的開源JTA事務實現組件:JOTM和Atomikos Transactions Essentials
具體用法參考http://jinnianshilongnian.iteye.com/blog/1439900

這篇博客講解了對於JDBC和JTA分別用PlatformTransactionManager實現和使用TransactionTemplate實現編程式事務管理:http://jinnianshilongnian.iteye.com/blog/1441271

編程式實現事務
Spring提供兩種編程式事務支持:直接使用PlatformTransactionManager實現和使用TransactionTemplate模板類,用於支持邏輯事務管理。
如果采用編程式事務推薦使用TransactionTemplate模板類。

Spring框架支持事務管理的核心是事務管理器抽象,對於不同的數據訪問框架(如Hibernate)通過實現策略接口 PlatformTransactionManager,從而能支持各種數據訪問框架的事務管理,PlatformTransactionManager 接口定義如下:

Java代碼   收藏代碼
  1. public interface PlatformTransactionManager {  
  2.        //返回一個已經激活的事務或創建一個新的事務  
  3.        TransactionStatus getTransaction(TransactionDefinition definition)   
  4.                                           throws TransactionException;  
  5.        void commit(TransactionStatus status) throws TransactionException;  
  6.        void rollback(TransactionStatus status) throws TransactionException;  
  7. }  



關於TransactionDefinition接口和TransactionStatus接口:
http://jinnianshilongnian.iteye.com/blog/1439900

Spring聲明式事務
在日常開發中,用的最多的就是聲明式事務了,下面將介紹SpringJdbc的聲明式事務的配置方法:

Xml代碼   收藏代碼
  1. <context:component-scan base-package="com.chou.spring.jdbc"/>  
  2.   
  3. <!-- 配置數據源 -->      
  4. <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">  
  5.     <!-- Connection Info -->  
  6.     <property name="driverClass" value="${db.driverClass}" />  
  7.     <property name="jdbcUrl" value="${db.url}" />  
  8.     <property name="user" value="${db.username}" />  
  9.     <property name="password" value="${db.password}" />  
  10.           
  11.     <!-- Connection Pooling Info -->  
  12.     <property name="initialPoolSize" value="1" />  
  13.     <property name="minPoolSize" value="1" />  
  14.     <property name="maxPoolSize" value="15" />  
  15.     <property name="maxIdleTime" value="1800" />  
  16.     <property name="maxStatements" value="0" />  
  17. </bean>  
  18.       
  19. <bean id="jdbcTemplateDao" class="com.chou.spring.jdbc.dao.JdbcTemplateDao" >  
  20.     <property name="dataSource" ref="dataSource" />  
  21. </bean>  
  22.       
  23. <!-- JDBC事務管理器 -->  
  24. <bean id="jdbctTxManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
  25.     <property name="dataSource" ref="dataSource" />  
  26. </bean>  
  27.       
  28. <tx:advice id="txAdvice" transaction-manager="jdbctTxManager">  
  29.     <tx:attributes>  
  30.         <tx:method name="domain*"/>  
  31.     </tx:attributes>  
  32. </tx:advice>  
  33.       
  34. <aop:config proxy-target-class="true">  
  35.     <aop:advisor advice-ref="txAdvice"   
  36.     pointcut="execution(* com.chou.spring.jdbc.service.JdbcTemplateService.*(..))"/>  
  37. </aop:config>  

 

Java代碼   收藏代碼
  1. public class JdbcTemplateDao extends JdbcDaoSupport{  
  2.   
  3.     public void save() {  
  4.         String insertSql = "insert into tab_item values(?,?,?)";  
  5.         Assert.isTrue(getJdbcTemplate().update(insertSql, new Object[]{6"HP""PT540"}) == 1"插入失敗");  
  6.     }  
  7.       
  8.     public void delete() {  
  9.         String deleteSql = "delete tab_item where id = ?";  
  10.         Assert.isTrue(getJdbcTemplate().update(deleteSql, new Object[]{6}) == 1"刪除失敗");  
  11.     }  
  12.       
  13.     public void update() {  
  14.         String updateSql = "update tab_item set itemno = ?, itemname = ? where id = ?";  
  15.         Assert.isTrue(getJdbcTemplate().update(updateSql, new Object[]{"HP""PT555"6}) == 1"修改失敗");  
  16.     }  
  17. }  

 

Java代碼   收藏代碼
  1. /** 
  2.  *  
  3.  * @author Chou 
  4.  * @since 2012-9-9 
  5.  * 把事務定義在Service層是為了避免報錯: 
  6.  * All calls to this method via a proxy will be routed directly to the proxy. 
  7.  * 這是是事務轉移問題,你如果在控制層加入事務就不會有提示了,也沒有警告, 
  8.  * 一般很多人在final DAO里加入事務那是有警告的, 
  9.  * 如果配置文件定義了AOP獲取代理對象是proxy-target-class="true"即采用CGLIB方式 
  10.  * 而cglib動態代理是利用asm開源包,對代理對象類的class文件加載進來, 
  11.  * 通過修改其字節碼生成子類並繼承你寫的類,然后在你的基礎上加事物管理, 
  12.  * 而JdbcDaoSupport中的setDataSource是final的他繼承不了 
  13.  * 當然你可以無視它,也沒有問題。 
  14.  */  
  15. @Service  
  16. public class JdbcTemplateService {  
  17.       
  18.     @Autowired  
  19.     private JdbcTemplateDao jdbcTemplateDao;  
  20.       
  21.     public void domain(){  
  22.         jdbcTemplateDao.save();  
  23.         int i = 2/0;//這里出錯了,事務就會回滾,之前的save就無效了  
  24.         jdbcTemplateDao.update();  
  25.         jdbcTemplateDao.delete();  
  26.     }  
  27. }  
  28.   
  29. //main方法  
  30. String[] configLocations = new String[] {"applicationContext.xml"};    
  31. ApplicationContext ctx = new ClassPathXmlApplicationContext(configLocations);  
  32. JdbcTemplateService j = ctx.getBean(JdbcTemplateService.class);  
  33. j.domain();  



<tx:advice/>配置詳解

Xml代碼   收藏代碼
  1. <tx:advice id="……" transaction-manager="……">  
  2. <tx:attributes>  
  3.         <tx:method name="*"  
  4.                            propagation="REQUIRED"  
  5.                            isolation="DEFAULT"  
  6.                            timeout="-1"  
  7.                            read-only="true"  
  8.                            no-rollback-for=""   
  9.                            rollback-for="java.lang.Exception"/>  
  10.     </tx:attributes>  
  11. </tx:advice>  
  12.   
  13. <!-- 最常用的配置 -->  
  14. <tx:advice id="txAdvice" transaction-manager="txManager">  
  15. <tx:attributes>  
  16.            <tx:method name="save*" propagation="REQUIRED" />  
  17.            <tx:method name="add*" propagation="REQUIRED" />  
  18.            <tx:method name="create*" propagation="REQUIRED" />  
  19.            <tx:method name="insert*" propagation="REQUIRED" />  
  20.            <tx:method name="update*" propagation="REQUIRED" />  
  21.            <tx:method name="merge*" propagation="REQUIRED" />  
  22.            <tx:method name="del*" propagation="REQUIRED" />  
  23.            <tx:method name="remove*" propagation="REQUIRED" />  
  24.            <tx:method name="put*" propagation="REQUIRED" />  
  25.            <tx:method name="get*" propagation="SUPPORTS" read-only="true" />  
  26.            <tx:method name="count*" propagation="SUPPORTS" read-only="true" />  
  27.            <tx:method name="find*" propagation="SUPPORTS" read-only="true" />  
  28.            <tx:method name="list*" propagation="SUPPORTS" read-only="true" />  
  29.            <tx:method name="*" propagation="SUPPORTS" read-only="true" />  
  30.            <tx:method name="batchSaveOrUpdate" propagation="REQUIRES_NEW" />  
  31.        </tx:attributes>  
  32. </tx:advice>  
  33. <aop:config>  
  34.        <aop:pointcut id="txPointcut" expression="execution(* cn.javass..service.*.*(..))" />  
  35.        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />  
  36. </aop:config>  




XML形式的事務配置<tx:method >的屬性詳解

屬性
類型
默認值
說明
propagation Propagation枚舉 REQUIRED 事務傳播屬性
isolation isolation枚舉 DEFAULT(所用數據庫默認級別) 事務隔離級別
readOnly boolean false 是否才用優化的只讀事務
timeout int -1 超時(秒)
rollbackFor Class[] {} 需要回滾的異常類
rollbackForClassName String[] {} 需要回滾的異常類名
noRollbackFor Class[] {} 不需要回滾的異常類
noRollbackForClassName String[] {} 不需要回滾的異常類名



readOnly
事務屬性中的readOnly標志表示對應的事務應該被最優化為只讀事務。如果值為true就會告訴Spring我這個方法里面沒有insert或者update,你只需要提供只讀的數據庫Connection就行了,這種執行效率會比read-write的Connection高,所以這是一個最優化提示。在一些情況下,一些事務策略能夠起到顯著的最優化效果,例如在使用Object/Relational映射工具(如:Hibernate或TopLink)時避免dirty checking(試圖“刷新”)。

timeout
在屬性中還有定義“timeout”值的選項,指定事務超時為幾秒。一般不會使用這個屬性。在JTA中,這將被簡單地傳遞到J2EE服務器的事務協調程序,並據此得到相應的解釋。

Isolation Level(事務隔離等級)的5個枚舉值
為什么事務要有Isolation Level這個屬性?先回顧下數據庫事務的知識:
第一類丟失更新(lost update):在完全未隔離事務的情況下,兩個事物更新同一條數據資源,某一事物異常終止,回滾造成第一個完成的更新也同時丟失。
第二類丟失更新(second lost updates):是不可重復讀的特殊情況,如果兩個事務都讀取同一行,然后兩個都進行寫操作,並提交,第一個事務所做的改變就會丟失。
臟讀(dirty read):如果第二個事務查詢到第一個事務還未提交的更新數據,形成臟讀。因為第一個事務你還不知道是否提交,所以數據不一定是正確的。
虛讀(phantom read):一個事務執行兩次查詢,第二次結果集包含第一次中沒有或者某些行已被刪除,造成兩次結果不一致,只是另一個事務在這兩次查詢中間插入或者刪除了數據造成的。
不可重復讀(unrepeated read):一個事務兩次讀取同一行數據,結果得到不同狀態結果,如中間正好另一個事務更新了該數據,兩次結果相異,不可信任。

具體關於事務機制可以看我以前的博客:http://zhou137520.iteye.com/admin/blogs/1638574

當遇到以上這些情況時我們可以設置isolation下面這些枚舉值:
DEFAULT:采用數據庫默認隔離級別
SERIALIZABLE:最嚴格的級別,事務串行執行,資源消耗最大;
REPEATABLE_READ:保證了一個事務不會修改已經由另一個事務讀取但未提交(回滾)的數據。避免了“臟讀取”和“不可重復讀取”的情況,但是帶來了更多的性能損失。
READ_COMMITTED:大多數主流數據庫的默認事務等級,保證了一個事務不會讀到另一個並行事務已修改但未提交的數據,避免了“臟讀取”。該級別適用於大多數系統。
READ_UNCOMMITTED:保證了讀取過程中不會讀取到非法數據。隔離級別在於處理多事務的並發問題。

關於propagation屬性的7個傳播行為
REQUIRED:指定當前方法必需在事務環境中運行,如果當前有事務環境就加入當前正在執行的事務環境,如果當前沒有事務,就新建一個事務。這是默認值。
SUPPORTS:指定當前方法加入當前事務環境,如果當前沒有事務,就以非事務方式執行。
MANDATORY:指定當前方法必須加入當前事務環境,如果當前沒有事務,就拋出異常。
REQUIRES_NEW:指定當前方法總是會為自己發起一個新的事務,如果發現當前方法已運行在一個事務中,則原有事務被掛起,我自己創建一個屬於自己的事務,直我自己這個方法commit結束,原先的事務才會恢復執行。
NOT_SUPPORTED:指定當前方法以非事務方式執行操作,如果當前存在事務,就把當前事務掛起,等我以非事務的狀態運行完,再繼續原來的事務。
NEVER:指定當前方法絕對不能在事務范圍內執行,如果方法在某個事務范圍內執行,容器就拋異常,只有沒關聯到事務,才正常執行。
NESTED:指定當前方法執行時, 如果已經有一個事務存在,則運行在這個嵌套的事務中.如果當前環境沒有運行的事務,就新建一個事務,並與父事務相互獨立,這個事務擁有多個可以回滾的保證 點。就是指我自己內部事務回滾不會對外部事務造成影響,只對DataSourceTransactionManager事務管理器起效。

注解形式@Transactional實現事務管理
注意@Transactional只能被應用到public方法上,對於其它非public的方法,如果標記了@Transactional也不會報錯,但方法沒有事務功能。

默認情況下,一個有事務的方法,遇到RuntiomeException時會回滾。遇到受檢查的異常是不會回滾的,要想所有異常都回滾,要加上屬性rollbackFor={Exception.class}

Xml代碼   收藏代碼
  1. <!-- 事務管理器配置 -->  
  2. <bean id="txManager"     class="org.springframework.orm.hibernate3.HibernateTransactionManager">  
  3.     <property name="sessionFactory" ref="sessionFactory" />  
  4. </bean>  
  5.   
  6. <!-- 使用annotation定義事務 -->  
  7. <tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />  


transaction-manager:指定事務管理器名字,默認為transactionManager,當使用其他名字時需要明確指定;
proxy-target-class:默認false表示使用JDK代理,如果為true將使用CGLIB代理
order:定義事務通知順序,默認Ordered.LOWEST_PRECEDENCE,表示將順序決定權交給AOP來處理。

建議只在實現類或實現類的方法上使用@Transactional,而不要在接口上使用,這是因為如果使用JDK代理機制是沒問題,因為其使用基於接口的代理;而使用使用CGLIB代理機制時就會遇到問題,因為其使用基於類的代理而不是接口,這是因為接口上的@Transactional注解是“不能繼承的”。
http://jinnianshilongnian.iteye.com/blog/1508018這篇博客講解了基於JDK動態代理和CGLIB動態代理的實現Spring注解管理事務(@Trasactional)到底有什么區別。

Java代碼   收藏代碼
  1. @Transactional//放在這里表示所有方法都加入事務管理  
  2. public class AnnotationUserServiceImpl implements IUserService {  
  3.     private IUserDao userDao;  
  4.     private IAddressService addressService;  
  5.       
  6.     @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED)  
  7.     public void save(UserModel user) {  
  8.         userDao.save(user);  
  9.         user.getAddress().setUserId(user.getId());  
  10.         addressService.save(user.getAddress());  
  11.     }  
  12.   
  13.     @Transactional(propagation=Propagation.REQUIRED, readOnly=true,  
  14.                    isolation=Isolation.READ_COMMITTED)  
  15.     public int countAll() {  
  16.         return userDao.countAll();  
  17.     }  
  18.     //setter...  
  19. }  


總結


       編程式事務是不推薦的,即使有很少事務操作,Spring發展到現在,沒有理由使用編程式事務,只有在為了深入理解Spring事務管理才需要學習編程式事務使用。
       推薦使用聲明式事務,而且強烈推薦使用<tx:tags>方式的聲明式事務,因為其是無侵入代碼的,可以配置模板化的事務屬性並運用到多個項目中。
       而@Transaction注解事務,不過如果一個項目模塊太多,service方法太多導致每個方法都要手動去加注解,是不是很麻煩,也容易出錯。如果一個項目結構清晰,分層明確,那么標簽形式的配置將是最直觀和方便的辦法。
       總之,能保證項目正常工作的事務配置就是最好的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM