作者:郭松 來自: http://tech./2017/02/06/rd/server/SpringTransactional/ 步驟一、在Spring配置文件中引入命名空間 步驟二、xml配置文件中,添加事務管理器bean配置 步驟三、在使用事務的方法或者類上添加下面的注解 @Transactional(“pkgouTransactionManager”) 1> 事務注解方式: @Transactional
2> 事務傳播行為介紹: 3> 事務超時設置: @Transactional(timeout=30) //默認是30秒 4> 事務隔離級別:
@Transactional的屬性: 默認情況下,數據庫處于自動提交模式。每一條語句處于一個單獨的事務中,在這條語句執行完畢時,如果執行成功則隱式的提交事務,如果執行失敗則隱式的回滾事務。 事務管理,是一組相關的操作處于一個事務之中,因此必須關閉數據庫的自動提交模式。這點,Spring會在org/springframework/jdbc/datasource/DataSourceTransactionManager.java中將底層連接的自動提交特性設置為false。 // switch to manual commit if necessary。 this is very expensive in some jdbc drivers,// so we don't want to do it unnecessarily (for example if we've explicitly// configured the connection pool to set it already)。if (con。getautocommit()) { txobject.setmustrestoreautocommit(true); if (logger.isdebugenabled()) { logger.debug('switching jdbc connection [' + con + '] to manual commit'); } //首先將自動提交屬性改為false con.setautocommit(false);} Spring事務管理器回滾一個事務的推薦方法是在當前事務的上下文內拋出異常。Spring事務管理器會捕捉任何未處理的異常,然后依據規則決定是否回滾拋出異常的事務。 默認配置下,Spring只有在拋出的異常為運行時unchecked異常時才回滾該事務,也就是拋出的異常為RuntimeException的子類(Errors也會導致事務回滾)。而拋出checked異常則不會導致事務回滾。 Spring也支持明確的配置在拋出哪些異常時回滾事務,包括checked異常。也可以明確定義哪些異常拋出時不回滾事務。 還可以編程性的通過setRollbackOnly()方法來指示一個事務必須回滾,在調用完setRollbackOnly()后你所能執行的唯一操作就是回滾。 由于Spring事務管理是基于接口代理或動態字節碼技術,通過AOP實施事務增強的。 (1)對于基于接口動態代理的AOP事務增強來說,由于接口的方法是public的,這就要求實現類的實現方法必須是public的(不能是protected,private等),同時不能使用static的修飾符。所以,可以實施接口動態代理的方法只能是使用“public” 或 “public final”修飾符的方法,其它方法不可能被動態代理,相應的也就不能實施AOP增強,也即不能進行Spring事務增強。 (2)基于CGLib字節碼動態代理的方案是通過擴展被增強類,動態創建子類的方式進行AOP增強植入的。由于使用final,static,private修飾符的方法都不能被子類覆蓋,相應的,這些方法將不能被實施的AOP增強。 所以,必須特別注意這些修飾符的使用,@Transactional 注解只被應用到 public 可見度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不會報錯,但是這個被注解的方法將不會展示已配置的事務設置。 用 spring 事務管理器,由spring來負責數據庫的打開,提交,回滾。默認遇到運行期異常(throw new RuntimeException(“注釋”);)會回滾,即遇到不受檢查(unchecked)的異常時回滾; @Transactional(rollbackFor=Exception.class) //指定回滾,遇到異常Exception時回滾public void methodName() {throw new Exception('注釋');} 而遇到需要捕獲的異常(throw new Exception(“注釋”);)不會回滾,即遇到受檢查的異常(就是非運行時拋出的異常,編譯器會檢查到的異常叫受檢查異常或說受檢查異常)時,需我們指定方式來讓事務回滾 要想所有異常都回滾,要加上 @Transactional(rollbackFor={Exception。class,其它異常}) 。如果讓unchecked異常不回滾:@Transactional(notRollbackFor=RunTimeException.class @Transactional(noRollbackFor=Exception.class)//指定不回滾,遇到運行期異常(throw new RuntimeException('注釋');)會回滾public ItimDaoImpl getItemDaoImpl() {throw new RuntimeException('注釋');} 僅僅 @Transactional注解的出現不足于開啟事務行為,它僅僅是一種元數據,能夠被可以識別 @Transactional注解和上述的配置適當的具有事務行為的beans所使用。其實,根本上是 元素的出現 開啟了事務行為。 Spring團隊的建議是你在具體的類(或類的方法)上使用 @Transactional 注解,而不要使用在類所要實現的任何接口上。你當然可以在接口上使用 @Transactional 注解,但是這將只能當你設置了基于接口的代理時它才生效。因為注解是不能繼承的,這就意味著如果你正在使用基于類的代理時,那么事務的設置將不能被基于類的代理所識別,而且對象也將不會被事務代理所包裝(將被確認為嚴重的)。因此,請接受Spring團隊的建議并且在具體的類火方法上使用 @Transactional 注解。 @Transactional 注解標識的方法,處理過程盡量的簡單。尤其是帶鎖的事務方法,能不放在事務里面的最好不要放在事務里面。可以將常規的數據庫查詢操作放在事務前面進行,而事務內進行增、刪、改、加鎖查詢等操作。 @Transactional 注解的默認事務管理器bean是“transactionManager”,如果聲明為其他名稱的事務管理器,需要在方法上添加@Transational(“managerName”)。 @Transactional 注解標注的方法中不要出現網絡調用、比較耗時的處理程序,因為,事務中數據庫連接是不會釋放的,如果每個事務的處理時間都非常長,那么寶貴的數據庫連接資源將很快被耗盡。 Spring事務使用AOP代理后的方法調用執行流程,如圖所示: 從圖中可以看出,調用事務時首先調用的是AOP代理對象而不是目標對象,首先執行事務切面,事務切面內部通過TransactionInterceptor環繞增強進行事務的增強。即進入目標方法之前開啟事務,退出目標方法時提交/回滾事務。 這樣在自我調用時,則會出現無法開啟事務的問題,比如: public interface TargetService { public void a(); public void b(); } @Service public class TargetServiceImpl implements TargetService{ public void a() { this.b(); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void b() { //執行數據庫操作 } } 此處的this指向目標對象,因此調用this.b()將不會執行b事務切面,即不會執行事務增強,因此b方法的事務定義“@Transactional(propagation = Propagation.REQUIRES_NEW)”將不會實施,即結果是b和a方法的事務是方法的事務定義是一樣的。 解決方法 通過BeanPostProcessor 在目標對象中注入代理對象: 一、定義BeanPostProcessor 需要使用的標識接口 public interface BeanSelfAware{ public abstract void setSelf(Object obj);} 二、定義自己的BeanPostProcessor(InjectBeanSelfProcessor) public class InjectBeanSelfProcessor implements BeanPostProcessor, ApplicationContextAware{ ApplicationContext context;private static Log log = LogFactory.getLog(com/netease/lottery/base/common/BeanSelf/InjectBeanSelfProcessor);public InjectBeanSelfProcessor(){}public void setApplicationContext(ApplicationContext context) throws BeansException{ this.context = context;}public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException{ if(bean instanceof BeanSelfAware) {//如果Bean實現了BeanSelfAware標識接口,就將代理對象注入 BeanSelfAware myBean = (BeanSelfAware)bean; Class cls = bean.getClass(); if(!AopUtils.isAopProxy(bean)) { Class c = bean.getClass(); Service serviceAnnotation = (Service)c.getAnnotation(org/springframework/stereotype/Service); if(serviceAnnotation != null) try { bean = context.getBean(beanName); if(AopUtils.isAopProxy(bean)); } catch(BeanCurrentlyInCreationException beancurrentlyincreationexception) { } catch(Exception ex) { log.fatal((new StringBuilder()).append('No Proxy Bean for service ').append(bean.getClass()).append(' ').append(ex.getMessage()).toString(), ex); } } myBean.setSelf(bean); return myBean; } else { return bean; }}public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException{ return bean;} 三、目標類實現 public interface TargetService { public void a(); public void b(); } @Service public class TargetServiceImpl implements TargetService,BeanSelfAware{ private TargetService self; public void setSelf(Object proxyBean) { //通過InjectBeanSelfProcessor注入自己(目標對象)的AOP代理對象 this.self = (TargetService) proxyBean; } public void a() { self.b(); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void b() {//執行數據庫操作} } postProcessAfterInitialization根據目標對象是否實現BeanSelfAware標識接口,通過setSelf(bean)將代理對象(bean)注入到目標對象中,從而可以完成目標對象內部的自我調用。 轉發是對作者最大的支持!!! |
|