Spring @Transactional原理及使用

 主要介紹Spring聲明式事務中使用註解@Transactional的原理及注意事項。java

本文主要討論Spring聲明式事務中使用註解@Transactional的方式、原理及注意事項,主要包括如下內容:spring

  • Spring @Transactional的配置使用;
  • Spring @Transactional的傳播行爲和隔離級別;
  • Spring @Transactional的工做原理;
  • Spring @Transactional的注意事項;
  • Spring @Transactional自我調用中的問題。

一、Spring @Transactional的配置

步驟1、在Spring配置文件中引入命名空間

<beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">

步驟2、xml配置文件中,添加事務管理器bean配置

<!-- 事務管理器配置,單數據源事務 -->
    <bean id="pkgouTransactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="pkGouDataSource" />
    </bean>
    <!-- 使用annotation定義事務 -->
    <tx:annotation-driven transaction-manager="pkgouTransactionManager" />

步驟3、在使用事務的方法或者類上添加 @Transactional(「pkgouTransactionManager」)註解

二、 Spring @Transactional的傳播行爲和隔離級別

1> 事務註解方式: @Transactional數據庫

  • 標註在類前:標示類中全部方法都進行事務處理
  • 標註在接口、實現類的方法前:標示方法進行事務處理

2> 事務傳播行爲介紹:編程

事務傳播行爲 說明
@Transactional(propagation=Propagation.REQUIRED) 若是有事務, 那麼加入事務, 沒有的話新建一個(默認狀況)
@Transactional(propagation=Propagation.NOT_SUPPORTED) 容器不爲這個方法開啓事務
@Transactional(propagation=Propagation.REQUIRES_NEW) 不論是否存在事務,都建立一個新的事務,原來的掛起,新的執行完畢,繼續執行老的事務
@Transactional(propagation=Propagation.MANDATORY) 必須在一個已有的事務中執行,不然拋出異常
@Transactional(propagation=Propagation.NEVER) 必須在一個沒有的事務中執行,不然拋出異常(與Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS) 若是其餘bean調用這個方法,在其餘bean中聲明事務,那就用事務。若是其餘bean沒有聲明事務,那就不用事務

3> 事務超時設置:
@Transactional(timeout=30) //默認是30秒網絡

4> 事務隔離級別:app

事務隔離級別 說明
@Transactional(isolation = Isolation.READ_UNCOMMITTED) 讀取未提交數據(會出現髒讀, 不可重複讀),基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED)(SQLSERVER默認) 讀取已提交數據(會出現不可重複讀和幻讀)
@Transactional(isolation = Isolation.REPEATABLE_READ) 可重複讀(會出現幻讀)
@Transactional(isolation = Isolation.SERIALIZABLE) 串行化
  • 髒讀 : 一個事務讀取到另外一事務未提交的更新數據
  • 不可重複讀 : 在同一事務中, 屢次讀取同一數據返回的結果有所不一樣, 換句話說, 後續讀取能夠讀到另外一事務已提交的更新數據。相反,」可重複讀」在同一事務中屢次讀取數據時,可以保證所讀數據同樣,也就是後續讀取不能讀到另外一事務已提交的更新數據
  • 幻讀 : 一個事務讀到另外一個事務已提交的insert數據

@Transactional的屬性:post

三、 Spring @Transactional的工做原理

  • 自動提交

默認狀況下,數據庫處於自動提交模式。每一條語句處於一個單獨的事務中,在這條語句執行完畢時,若是執行成功則隱式的提交事務,若是執行失敗則隱式的回滾事務。
事務管理,是一組相關的操做處於一個事務之中,所以必須關閉數據庫的自動提交模式。這點,Spring會在org/springframework/jdbc/datasource/DataSourceTransactionManager.java中將底層鏈接的自動提交特性設置爲false。ui

// 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事務管理器會捕捉任何未處理的異常,而後依據規則決定是否回滾拋出異常的事務。
默認配置下,Spring只有在拋出的異常爲運行時unchecked異常時纔回滾該事務,也就是拋出的異常爲RuntimeException的子類(Errors也會致使事務回滾)。而拋出checked異常則不會致使事務回滾。
Spring也支持明確的配置在拋出哪些異常時回滾事務,包括checked異常。也能夠明肯定義哪些異常拋出時不回滾事務。
還能夠編程性的經過setRollbackOnly()方法來指示一個事務必須回滾,在調用完setRollbackOnly()後你所能執行的惟一操做就是回滾。this

四、 Spring @Transactional的注意事項

  • 因爲Spring事務管理是基於接口代理或動態字節碼技術,經過AOP實施事務加強的。

(1)對於基於接口動態代理的AOP事務加強來講,因爲接口的方法是public的,這就要求實現類的實現方法必須是public的(不能是protected,private等),同時不能使用static的修飾符。因此,能夠實施接口動態代理的方法只能是使用「public」或「public final」修飾符的方法,其它方法不可能被動態代理,相應的也就不能實施AOP加強,也即不能進行Spring事務加強。spa

(2)基於CGLib字節碼動態代理的方案是經過擴展被加強類,動態建立子類的方式進行AOP加強植入的。因爲使用final,static,private修飾符的方法都不能被子類覆蓋,相應的,這些方法將不能被實施的AOP加強。

因此,必須特別注意這些修飾符的使用,@Transactional 註解只被應用到 public 可見度的方法上。 若是你在 protected、private 或者 package-visible 的方法上使用 @Transactional 註解,它也不會報錯,可是這個被註解的方法將不會展現已配置的事務設置。

  • 用 spring 事務管理器,由spring來負責數據庫的打開,提交,回滾。默認遇到運行期異常(throw new RuntimeException(「註釋」);)會回滾,即遇到不受檢查(unchecked)的異常時回滾;而遇到須要捕獲的異常(throw new Exception(「註釋」);)不會回滾,即遇到受檢查的異常(就是非運行時拋出的異常,編譯器會檢查到的異常叫受檢查異常或說受檢查異常)時,需咱們指定方式來讓事務回滾 要想全部異常都回滾,要加上 @Transactional( rollbackFor={Exception。class,其它異常}) 。若是讓unchecked異常不回滾: @Transactional(notRollbackFor=RunTimeException.class)以下:
@Transactional(rollbackFor=Exception.class) //指定回滾,遇到異常Exception時回滾
public void methodName() {
   throw new Exception("註釋");
}
@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 @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 在目標對象中注入代理對象:

1、定義BeanPostProcessor 須要使用的標識接口

public interface BeanSelfAware{
    public abstract void setSelf(Object obj);
}

2、定義本身的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;
    }

3、目標類實現

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)注入到目標對象中,從而能夠完成目標對象內部的自我調用。

相關文章
相關標籤/搜索