Spring事務管理使用


發現問題

最近,碰到一個問題,再用spring實現事務管理的時候,發現不起作用,在出異常時,並不會回滾數據庫操作。

我想實現的功能如下:

@Transactional(isolation=Isolation.DEFAULT,readOnly=false,propagation=Propagation.REQUIRED,rollbackFor=Exception.class)
public boolean someService() {
    mapper.insert();
    mapper.insert();
}

 

TransactionManager的配置如下:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

 

但是在測試中,在service中手動拋出異常后,第一條insert語句並不會回滾。

之后研究了很久,在控制台打印的消息顯示,並沒有開啟事務。那么我分析應該有下面幾種可能:

  1. 配置錯誤
  2. JDBC的AutoCommit沒有設置為false
  3. 控制台信息顯示,在Service中,insertA在jdbt連接池中取出了一個連接,插入完后,便將連接返回給了連接池,所以在service中,有多個數據庫連接分別處理不同的insert語句。這也可能導致事務失敗。

 

下面來逐步排除:

AutoCommit=true,這個是有可能的,但是通過查看Spring的源碼,發現Spring在事務中,是會檢測當前連接的AutoCommit選項並把它設置為False,所以排除

 

最后,通過查看Spring關於Transaction的文檔,發現上面說了,如果使用的是DataSourceTransactionManager,那么只能在一個數據庫連接上開啟事務。那么這么說,應該是數據庫連接池的問題了?或者是Spring自己並沒有開啟事務管理,而是將事務管理委托給了Mysql去實現?這個是有可能的,所以后面再看。

 

解決問題

然后,我選擇使用了切面Aop來實現Spring的事務管理。步驟如下:

 

//注入transactionManager
@Autowired() @Qualifier("transactionManager1")
private PlatformTransactionManager  transactionManager;

 

這一步是獲得自動注入的 transactionManager實例,之后使用TransactionTemplate

//編程式開啟事務
TransactionTemplate template = new TransactionTemplate(transactionManager);

 

是不是很像JDBCTemplate?那么一樣的使用內部類實現回調:

        //在這邊也可以取消返回值,設為void
        boolean result = (Boolean) template.execute(new TransactionCallback<Object>() {
        public Object doInTransaction(TransactionStatus transactionStatus) {
            try {
                mapper.insertA();
                throw new SQLException();
                mapper.insertB();
            } catch (Exception e) {mapper.insertB();
                //出異常時,手動設置回滾
                transactionStatus.setRollbackOnly();  
                e.printStackTrace();
                return false;
            }
            return true;
          }
        });

 

這樣,就可以使用Aop手動開啟事務了。

 

經過測試,在兩個插入之間手動拋出了異常,insertA的插入被回滾了。從控制台輸出信息來看,insertA插入成功后,即把jdbc返回到連接池了,並且事務管理配置的依然是DataSourceTransactionManager,說明前面的數據庫連接池的問題被排除了。

 

分析原因

那么就剩下配置錯誤了。

實驗多次以后,我發現如下配置是可以讓聲明式事務管理起作用的:

Spring的配置中,取消注入Controller

<context:component-scan base-package="com.test">  
      <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />   
</context:component-scan> 

SpringMVC的配置,取消注入Service

<context:component-scan base-package="com.test">  
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />  
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />  
</context:component-scan>  

 

配置事務的Service實現某個接口

@Service
public class Test() {
    @Transactional(isolation=Isolation.DEFAULT,readOnly=false,propagation=Propagation.REQUIRED,rollbackFor=Exception.class)
    public boolean someService() {
        mapper.insert();
        mapper.insert();
    }
}

 

這樣便可以開啟事務,並在遇到異常時回滾了。那為什么原來的配置就不行呢?

根本原因是因為spring的context是父子容器,由ServletContextListener 加載spring配置文件產生的是父容器,springMVC加載配置文件產生的是子容器,子容器對Controller進行掃描裝配時裝配了@Service注解的實例 (@Controller 實例依賴@Service實例),而該實例理應由父容器進行初始化以保證事務的增強處理,所以此時得到的將是原樣的Service,沒有經過事務加強處理,故而沒有事務處理能力

所以在spring的配置中我們要手動exclude controller的注入,不掃描帶有@Controller注解的類。因為這些類已經隨容器啟動時,在servlet-context中掃描過一遍了

在springMVC的配置中掃描業務組件,讓springMVC不掃描帶有@Service注解的類(留在spring中掃描@Service注解的類),防止事務失效。

在需要配置事務管理的Service類中,需要讓類繼承一個或幾個接口,是因為如果不繼承接口,那么會使用cglib動態代理去實現,那么事務管理也會失效。如果實現了接口,那么便會使用jdk自帶的動態代理去實現,這樣便可以成功的開啟事務了。


免責聲明!

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



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