Spring原始碼學習之:spring註解@Transactional

Love Lenka發表於2016-10-13

在分析深入分析@Transactional的使用之前,我們先回顧一下事務的一些基本內容。

事務的基本概念

先來回顧一下事務的基本概念和特性。資料庫事務(Database Transaction) ,是指作為單個邏輯工作單元執行的一系列操作,要麼完全地執行,要麼完全地不執行。事務,就必須具備ACID特性,即原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和永續性(Durability)。

程式設計式事務與宣告式事務

Spring 與Hibernate的整合實現的事務管理是常用的一種功能。Hibernate 建議所有的資料庫訪問都應放在事務內進行,即使只進行只讀操作。事務又應該儘可能短,因為長事務會導致長時間無法釋放表內行級鎖,從而降低系統併發的性 能。 Spring 同時支援程式設計式事務和宣告式事務。

 

程式設計式事務需要在程式碼中顯式呼叫beginTransaction()、commit()、rollback()等事務管理相關的方法。Spring 的宣告式事務管理在底層是建立在 AOP 的基礎之上的,其本質是對方法前後進行攔截,然後在目標方法開始之前建立或者加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務。事務增強也是AOP的一大用武之處。

使用宣告式事務

聲 明式事務最大的優點就是不需要透過程式設計的方式管理事務,這樣就不需要在業務邏輯程式碼中摻雜事務管理的程式碼,只需在配置檔案中做相關的事務規則宣告,很好的 分離了業務邏輯和事務管理邏輯。以spring+jpa整合增加宣告式事務為例,下面是使用宣告式事務的配置方式以及使用。

applicationContext.xml配置

<!-- 配置entityManagerFactory 配置 -->

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">

     <property name="dataSource" ref="dataSource" />

     <property name="persistenceXmlLocation" value="/WEB-INF/classes/persistence.xml" />

     <property name="loadTimeWeaver">

             <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />

     </property>

</bean>

 

<!-- 配置宣告式事務管理,使用JpaTransactionManager作為事務管理器的實現類 –>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">

          <property name="entityManagerFactory" ref="entityManagerFactory" />

</bean>

 

<!--宣告業務元件使用註解生成事務代理 -->

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

使用@Transactional 配置宣告式事務管理

在spirng 的配置檔案中配置了事務管理和註解驅動之後,我們就可以在業務層使用@Transactional 配置宣告式事務管理了。如下

 

@Service

@Transactional

public class BaseService<T> implements IBaseService<T> {

 

@Autowired

private IBaseDao<T> baseDao;

......

}

@Transactional 深入使用

上面我們已經知道了宣告式事務管理的用法了,下面我們將進一步分析@Transactional在各種場景下用法。

事務的傳播行為

@Transactional註解支援9個屬性的設定,其中Propagation屬性用來列舉事務的傳播行為。所謂事務傳播行為就是多個事務方法相互呼叫時,事務如何在這些方法間傳播。Spring 支援 7 種事務傳播行為:

 

 

 

REQUIRED 是常用的事務傳播行為,如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。其它傳播行為大家可另查閱。

多個事務方法的巢狀呼叫時的事務傳播

有三個事務方法呼叫巢狀關係如下:

 

PayHisInfoService.update()->PayHisInfoService.save()->PayInfoService.update()

 

@Transactional(propagation=Propagation.REQUIRED)

public abstract class BaseService<E>{

          public abstract List<E> findAll();

}

 

@Component

public class PayHisInfoService extends BaseService<PayHisInfo>{

       @Resource

       private PayHisInfoDao payHisInfoDao;

@Resource

private PayInfoService payInfoService;

 

       public  void update(PayHisInfo payHisInfo,PayInfo payInfo){

     save(payHisInfo);

            payInfoService.update(payInfo);

      }

      public int save(PayHisInfo payHisInfo){

         return payHisInfoDao.insert(payHisInfo);

      }

}

 

@Component

public class PayInfoService extends BaseService<PayInfo>{

     @Resource

     private transient PayInfoDao payInfoDao;

 

     @Override

     @Transactional(propagation=Propagation.SUPPORTS, readOnly=true)

     public List<PayInfo> findAll() {

            return payInfoDao.findAll();

     }

 

     public int update(PayInfo payInfo){

            return payInfoDao.update(payInfo);

     }

}

 

 

當我們按照上面的呼叫巢狀關係執行時,結果如下

 

 

 

 

 

從 日誌中我們可以看出,PayHisInfoService.update()執行時開啟了一個事務,而PayHisInfoService.save() 方法和PayHisInfoService.update()方法處於同一個類中,呼叫時沒有發生事務傳播,就像已經處於 PayHisInfoService.update()方法的事務當中一樣。而當執行PayInfoService.update()時,提示 “Participating in existing transaction”,這說明發生了事務傳播,即PayInfoService.update()並沒有新建一個事務,而是加入到已有的 PayHisInfoService.update()事務中。

多執行緒環境下的事務傳播

現在我們將上面例子中的PayHisInfoService.update()用另一個執行緒來執行,如下

 

@Component

public class PayHisInfoService extends BaseService<PayHisInfo>{

    @Resource

    private PayHisInfoDao payHisInfoDao;

    @Resource

    private PayInfoService payInfoService;

 

    public  void update(PayHisInfo payHisInfo,PayInfo payInfo){

          save(payHisInfo);

           //payInfoService.update(payInfo);

         PayInfoThread pifth = new PayInfoThread(payInfoService,payInfo);

         pifth.start();

     }

 

     public int save(PayHisInfo payHisInfo){

          return payHisInfoDao.insert(payHisInfo);

     }

 

     private class PayInfoThread extends Thread{

        private PayInfoService payInfoService;

        private PayInfo payInfo;

       

        private PayInfoThread(PayInfoService payInfoService,PayInfo payInfo) {

            this.payInfoService = payInfoService;

            this.payInfo = payInfo;

        }

        public void run() {

               System.out.println("PayInfoThread updating payInfo...");

               payInfoService.update(payInfo);

        }

  }

}

 

當我們按照上面的呼叫巢狀關係執行時,結果如下

 

 

 

 

從 日誌結果可以看出,在執行PayHisInfoService.update()和PayInfoService.update()時分別建立了各自的事 務。而PayHisInfoService.save()和PayHisInfoService.update()則在同一個執行緒中執行。所以spring 的事務管理是執行緒安全的。

在相同執行緒中進行相互巢狀呼叫的事務方法工作於相同的事務中。如果這些相互巢狀呼叫的方法工作在不同的執行緒中,不同執行緒下的事務方法工作在獨立的事務中。

@Transactional的繼承關係

或許你已經在上面的例子中看出了,我們只在父類BaseService中宣告瞭@Transactional,子類就自然得到事務增強。註解並沒有繼承這種說法,但此處用“繼承關係”來形容父類@Transactional和子類方法之間的關係最恰當不過了:父類Service宣告瞭@Transactional,子類繼承父類,父類的宣告的@Transactional會對子類的所有方法進行事務增強。這個還有個很實用的用法,例如測試基類繼承

AbstractTransactionalJUnit4SpringContextTests

宣告@Transactional可方便進行事務測試

 

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration("/applicationContext.xml")

@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)

@Transactional

public class BaseTest extends AbstractTransactionalJUnit4SpringContextTests {

        public Object getBean(String beanName){

                return applicationContext.getBean(beanName);

         }

}

 

給BaseTest 宣告瞭@Transactional了之後,就會自動給我們自己編寫的測試類中的所有測試方法進行事務增強。

@Transactional的優先順序

如果子類的方法重寫了父類的方法並且宣告瞭@Transactional,那麼子類的事務宣告會優先於父類的事務宣告。

利用spring的AOP進行事務切面增強

還是上面的列子,把父類宣告的@Transactional去掉。在applicationContext.xml中配置事務增強切面,配置如下:

 

<!--②使用aop和tx名稱空間語法為PayHisInfoService所有公用方法新增事務增強 -->

    <aop:config proxy-target-class="true">

        <aop:pointcut id="serviceJdbcMethod"

        expression="execution(public * com.xxx.service.PayHisInfoService.*(..))"/>

        <aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="jdbcAdvice" order="0"/>

    </aop:config>

    <tx:advice id="jdbcAdvice" transaction-manager="transactionManager">

        <tx:attributes>

            <tx:method name="*"/>

        </tx:attributes>

    </tx:advice>

 

執行測試用例,結果如下:

 

 

 

 

當有一條資料更新失敗時,事務會自動回滾,如下:

 

 

@Transactional方法的可見度

上面為了測試演示方便,我們把@Transactional都宣告在了類上。實際上@Transactional 可以作用於介面、介面方法、類以及類方法上。但是 Spring 小組建議不要在介面或者介面方法上使用該註解,因為這隻有在使用基於介面的代理時它才會生效。另外, @Transactional 註解應該只被應用到 public 方法上,這是由 Spring AOP 的本質決定的(從上面的Spring AOP 事務增強可以看出,就是針對方法的)。如果你在 protected、private 或者預設可見性的方法上使用 @Transactional 註解,這將被忽略,也不會丟擲任何異常。

總結

(1)宣告式事務優於程式設計式事務

(2)事務方法的巢狀呼叫會產生事務傳播

(3)spring 的事務管理是執行緒安全的

(4)父類的宣告的@Transactional會對子類的所有方法進行事務增強

(5)從Spring AOP本質看,@Transactional 註解應該只被應用到 public 方法上

相關文章