貓頭鷹的深夜翻譯:spring事務管理

簡介

  • 大多數時候,開發者極少關注事務管理從而致使大量代碼須要從新開發,或是實現事務的時候沒有注意事務到底是如何實現的以及在這些場景中須要關注的維度。
  • 事務管理的一個重要方面是定義正確的事務邊界,例如事務什麼時候開始,何時應該結束,何時應該在數據庫中提交數據,何時應該回滾(在出現異常的時候)。
  • 對於開發人員而言,最重要的是瞭解如何在應用程序中更好的實現事務管理。因此如今讓咱們用不一樣的方式探索事務。

管理事務的方法

事務能夠用如下方式管理:mysql

1. 以編程方式,以下所示

EntityManagerFactory factory = Persistence.createEntityManagerFactory("PERSISTENCE_UNIT_NAME");                                       EntityManager entityManager = entityManagerFactory.createEntityManager();                   
Transaction transaction = entityManager.getTransaction()                  
try                                       
{  
   transaction.begin();                   
   someBusinessCode();                    
   transaction.commit();  
}                  
catch(Exception ex)                   
{                     
   transaction.rollback();  
   throw ex;                  
}

優勢:web

  • 代碼中事務的邊界很清晰

缺點:面試

  • 重複的代碼,容易出錯
  • 任何錯誤都會產生很大的影響
  • 須要編寫大量樣板文件,若是要今後方法調用另外一個方法,則還須要在那段代碼中進行管理。

2. 使用Spring管理事務

Spring支持兩類事務管理spring

  1. 編程式事務管理:這意味着必須在編程的幫助下管理事務。這提供了極大的靈活性,但很難維護。
  2. 聲明式事務管理:意味着您將事務管理與業務代碼分開。只能使用註釋或基於XML的配置來管理事務。
強烈建議使用聲明式事務。若是想知道其緣由,請閱讀下面的內容,不然,能夠直接跳轉到聲明式事務管理實現的部分。

如今,讓咱們細緻的分析每一種事務管理方法。sql

編程式事務管理

Spring Framework提供了兩種編程式事務管理方法。
a. 使用TransactionTemplate (Spring推薦這種實現):
Context Xml file:數據庫

<!-- Initialization for data source -->   
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">              
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>      
    <property name="url" value="jdbc:mysql://localhost:3306/TEST"/>      
    <property name="username" value="root"/>      
    <property name="password" value="password"/>   
</bean>
<!-- Initialization for TransactionManager -->   
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">    
    <property name="dataSource"  ref="dataSource" />  
</bean>
<!-- Definition for ServiceImpl bean -->   
<bean id="serviceImpl" class="com.service.ServiceImpl">    
    <constructor-arg ref="transactionManager"/>   
</bean>

Service類:編程

public class ServiceImpl implements Service
{        
  private final TransactionTemplate transactionTemplate;
  // 使用構造器注入來使用PlatfromTransactionManager  
  public ServiceImpl(PlatformTransactionManager transactionManager)
  {     
       this.transactionTemplate = new TransactionTemplate(transactionManager);   
  }
  
  public Object someServiceMethod()
  {        
    return transactionTemplate.execute(new TransactionCallback() 
    {
     //這段代碼在事務上下文中執行
     public Object doInTransaction(TransactionStatus status)
     {                
         updateOperation1();     
         return resultOfUpdateOperation2();    
     }
   });   
}}

若是沒有返回值,就使用TransactionCallbackWithoutResult匿名類。安全

transactionTemplate.execute(new TransactionCallbackWithoutResult()
{    
    protected void doInTransactionWithoutResult(TransactionStatus status)
    {       
       updateOperation1();      
       updateOperation2();  
    } 
});
  • TransactionTemplate類的實例是線程安全的,這些實例不包含任何會話狀態。
  • 然而TransactionTemplate實例確實會維持配置信息狀態,因此即便多個類共享同一個TransactionTemplate實例,若是一個類須要使用另外一種配置的TransactionTemplate(好比不一樣的隔離級別),那麼就須要配置兩個不一樣的實例。

b. 直接使用PlatformTransactionManager實現微信

<!-- Initialization for data source -->  
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">    
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>     
    <property name="url" value="jdbc:mysql://localhost:3306/TEST"/>      
    <property name="username" value="root"/>      
    <property name="password" value="password"/>  
</bean>
<!-- Initialization for TransactionManager -->   
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">      
    <property name="dataSource"  ref="dataSource" />       
</bean>
public class ServiceImpl implements Service
{    
    private PlatformTransactionManager transactionManager;
    public void setTransactionManager( PlatformTransactionManager transactionManager)
 {    
       this.transactionManager = transactionManager;  
 }
    DefaultTransactionDefinition def = new     DefaultTransactionDefinition();
    // explicitly setting the transaction name is something that can only be done programmatically
    def.setName("SomeTxName");
 def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    TransactionStatus status = txManager.getTransaction(def);
    try 
    {    
    // execute your business logic here
    }
    catch (Exception ex)
    {    
        txManager.rollback(status);   
    throw ex;
    }
    txManager.commit(status);
}

在進入聲明式事務管理以前,讓咱們看看如何選擇事務管理方式:ui

  • 只有在少許事務操做時,編程式事務管理更佳合適。
  • 只能經過編程式事務管理設置事務的名稱
  • 當但願顯示管理事務時,應當使用編程式事務管理
  • 另外一方面,若是您的應用程序具備大量事務操做,則聲明式事務管理是值得的。
  • 聲明式事務管理使事務代碼也業務代碼分離,而且配置難度不大。

聲明式事務管理(幾乎用於全部web應用場景)

第一步:在spring應用程序上下文xml文件中定義事務管理器。

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"/>

第二步:經過在spring應用程序上下文XML文件中添加如下條目,打開對事務註釋的支持。

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

或是在配置類中添加@EnableTransactionManagement

@Configuration
@EnableTransactionManagement
public class AppConfig
{
 ...
}
Spring建議只使用@Transactional來註解具體類(以及具體類的方法),而不是接口。

緣由是若是在接口上註解,而且使用基於類的代理(proxy-target-class="true")或是aop(mode="aspectj"),那麼事務註解將沒法被識別。

第三步:將註解添加在類(或是類的方法)或是接口(或是接口的方法上)

<tx:annotation-driven proxy-target-class="true">

默認配置爲proxy-target-class="false"

  • @Transactional註解能夠放在接口,接口方法,類或是類方法上
  • 若是你但願被註解在方法上的事務和類的事務配置不一樣,如隔離級別或傳播級別,那麼就在方法上覆蓋類的配置
  • 在代理模式中,只有經過代理進入的「外部」方法調用纔會被截獲。這意味着「自我調用」,即目標對象中調用目標對象的其餘方法的方法,即便被調用的方法用@Transactional標記,也不會在運行時觸發事務。

如今讓咱們瞭解一下@Transactional的屬性:
@Transactional (isolation=Isolation.READ_COMMITTED)

  • 默認值爲Isolation.DEFAULT
  • 大多數場景下,使用默認值便可
  • 須要在事務開始以前配置。由於一旦事務開始,就沒法進行配置

READ_COMMITTED
防止髒讀;會發生不可重複的讀取和幻讀。

READ_UNCOMMITTED
會出現髒讀,不可重複讀和幻讀。便可以看到事務還沒有提交的數據

REPEATABLE_READ
可重複讀。會出現幻讀

序列化
防止髒讀,幻讀和不可重複讀

@Transactional(timeout=60)
默認爲底層事務系統的默認超時。
當事務超過該時間沒有響應時,則會對底層系統發出回滾請求

@Transactional(propagation=Propagation.REQUIRED)
默認的傳播級別爲Required。其它的選項如REQUIRES_NEW, MANDATORY, SUPPORTS, NOT_SUPPORTED, NEVER, 和NESTED

REQUIRED
表示若是當前沒有活躍的事務上下文,目標方法將沒法運行。若是在調用此方法以前已經啓動了事務管理,那麼它將在相同的事務中繼續,或者在調用此方法時將當即開始新的事務。

REQUIRES_NEW
表示每次調用目標方法時都必須啓動新的事務。若是已有事務,它將暫停。

MANDATORY
表示目標方法須要運行中的事務。若是沒有事務,它將拋出異常。

SUPPORTS
不管是否有事務上下文,目標方法能夠執行。若是當前有事務上下文,它將在同一個上下文中運行。若是沒有,它仍將執行。這個選項適合獲取數據的方法。

NOT_SUPPORTED
目標方法無需傳播事務上下文。

NEVER
若是在事務上下文中執行目標方法,則拋出異常

@Transactional (rollbackFor=Exception.class)

  • 默認爲rollbackFor=RunTimeException.class
  • 在spring中,這意味着只要事務上下文中拋出RuntimeException,事務就會回滾。
  • 可用於顯示聲明在某個異常出現時回滾

@Transactional (noRollbackFor=IllegalStateException.class)
若是該異常出現時,則不進行回滾

最後,也是最重要的一個問題,@Transactional註解究竟應該放在哪一層?Service層仍是Dao層?

  • Service層是最合適的。服務層應該包含邏輯上進入事務的用戶交互的詳細級用例行爲。
  • 在一些CRUD應用中,Service層的業務代碼並不複雜,和Dao層的代碼差很少。在這種場景下能夠放置在DAO層
  • 若是在DAO層設置事務,而又有多個Service調用了DAO層的方法,那麼將很難管理
  • 假如你的Service層是使用Hibernate在獲取對象,並且你還使用懶加載獲取集合。那麼你須要在Service層開啓事務,不然會拋出LazyInitializationException

clipboard.png
想要了解更多開發技術,面試教程以及互聯網公司內推,歡迎關注個人微信公衆號!將會不按期的發放福利哦~

相關文章
相關標籤/搜索