在分析深入分析@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 方法上