在分析深入分析@Transactional的使用之前,我們先回顧一下事務的一些基本內容。
先來回顧一下事務的基本概念和特性。數據庫事務(Database Transaction) ,是指作為單個邏輯工作單元執行的一系列操作,要麼完全地執行,要麼完全地不執行。事務,就必須具備ACID特性,即原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持久性(Durability)。
Spring與Hibernate的整合實現的事務管理是常用的一種功能。Hibernate 建議所有的數據庫訪問都應放在事務內進行,即使只進行只讀操作。事務又應該盡可能短,因為長事務會導致長時間無法釋放表內行級鎖,從而降低系統並發的性能。 Spring 同時支持編程式事務和聲明式事務。
編程式事務需要在代碼中顯式調用beginTransaction()、commit()、rollback()等事務管理相關的方法。Spring 的聲明式事務管理在底層是建立在 AOP 的基礎之上的,其本質是對方法前後進行攔截,然後在目標方法開始之前創建或者加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務。事務增強也是AOP的一大用武之處。
聲明式事務最大的優點就是不需要通過編程的方式管理事務,這樣就不需要在業務邏輯代碼中摻雜事務管理的代碼,只需在配置文件中做相關的事務規則聲明,很好的分離了業務邏輯和事務管理邏輯。以spring+jpa整合增加聲明式事務為例,下面是使用聲明式事務的配置方式以及使用。
<!-- 配置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">
<beanclass="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" />
在spirng 的配置文件中配置了事務管理和注解驅動之後,我們就可以在業務層使用@Transactional 配置聲明式事務管理了。如下
@Service
@Transactional
public class BaseService<T> implements IBaseService<T> {
@Autowired
private IBaseDao<T> baseDao;
......
}
上面我們已經知道了聲明式事務管理的用法了,下面我們將進一步分析@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 的事務管理是線程安全的。
在相同線程中進行相互嵌套調用的事務方法工作於相同的事務中。如果這些相互嵌套調用的方法工作在不同的線程中,不同線程下的事務方法工作在獨立的事務中。
或許你已經在上面的例子中看出了,我們只在父類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去掉。在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 可以作用於接口、接口方法、類以及類方法上。但是 Spring 小組建議不要在接口或者接口方法上使用該注解,因為這只有在使用基於接口的代理時它才會生效。另外, @Transactional 注解應該只被應用到 public 方法上,這是由 Spring AOP 的本質決定的(從上面的Spring AOP 事務增強可以看出,就是針對方法的)。如果你在 protected、private 或者默認可見性的方法上使用 @Transactional 注解,這將被忽略,也不會拋出任何異常。
(1)聲明式事務優於編程式事務
(2)事務方法的嵌套調用會產生事務傳播
(3)spring 的事務管理是線程安全的
(4)父類的聲明的@Transactional會對子類的所有方法進行事務增強
(5)從Spring AOP本質看,@Transactional 注解應該只被應用到 public 方法上
參考文檔
http://www.ibm.com/developerworks/cn/java/j-lo-spring-ts3/
http://www.ibm.com/developerworks/cn/education/opensource/os-cn-spring-trans/