1. 程式人生 > >Spring 事務原理和使用

Spring 事務原理和使用

[email protected]的配置

步驟一、在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">

步驟二、xml配置檔案中,新增事務管理器bean配置

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

步驟三、在使用事務的方法或者類上新增下面的註解

@Transactional(“pkgouTransactionManager”)

2.傳播行為和隔離級別

1> 事務註解方式: @Transactional

  • 標註在類前:標示類中所有方法都進行事務處理

  • 標註在介面、實現類的方法前:標示方法進行事務處理

2> 事務傳播行為介紹:

3> 事務超時設定:

@Transactional(timeout=30) //預設是30秒

4> 事務隔離級別:

  • 髒讀 : 一個事務讀取到另一事務未提交的更新資料

  • 不可重複讀 : 在同一事務中, 多次讀取同一資料返回的結果有所不同, 換句話說, 後續讀取可以讀到另一事務已提交的更新資料。相反,”可重複讀”在同一事務中多次讀取資料時,能夠保證所讀資料一樣,也就是後續讀取不能讀到另一事務已提交的更新資料

  • 幻讀 : 一個事務讀到另一個事務已提交的insert資料

@Transactional的屬性:

3.工作原理

自動提交

預設情況下,資料庫處於自動提交模式。每一條語句處於一個單獨的事務中,在這條語句執行完畢時,如果執行成功則隱式的提交事務,如果執行失敗則隱式的回滾事務。 事務管理,是一組相關的操作處於一個事務之中,因此必須關閉資料庫的自動提交模式。這點,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事務管理器會捕捉任何未處理的異常,然後依據規則決定是否回滾丟擲異常的事務。 預設配置下,Spring只有在丟擲的異常為執行時unchecked異常時才回滾該事務,也就是丟擲的異常為RuntimeException的子類(Errors也會導致事務回滾)。而丟擲checked異常則不會導致事務回滾。 Spring也支援明確的配置在丟擲哪些異常時回滾事務,包括checked異常。也可以明確定義哪些異常丟擲時不回滾事務。 還可以程式設計性的通過setRollbackOnly()方法來指示一個事務必須回滾,在呼叫完setRollbackOnly()後你所能執行的唯一操作就是回滾。

4.注意事項

由於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 註解標註的方法中不要出現網路呼叫、比較耗時的處理程式,因為,事務中資料庫連線是不會釋放的,如果每個事務的處理時間都非常長,那麼寶貴的資料庫連線資源將很快被耗盡。

5.自我呼叫中的問題 

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)注入到目標物件中,從而可以完成目標物件內部的自我呼叫。