大事務引發的問題
從上圖可以看出如果系統中出現大事務時,問題還不小,所以我們在實際項目開發中應該儘量避免大事務的情況。如果我們已有系統中存在大事務問題,該如何解決呢?
解決辦法
少用@Transactional註解
部分代碼如下:
@Transactional(rollbackFor=Exception.class) public void save(User user) { doSameThing()... }
然而,我要說的第一條是:少用@Transactional註解。
爲什麼?
- 我們知道@Transactional註解是通過spring的aop起作用的,但是如果使用不當,事務功能可能會失效。如果恰巧你經驗不足,這種問題不太好排查。至於事務哪些情況下會失效
- @Transactional註解一般加在某個業務方法上,會導致整個業務方法都在同一個事務中,粒度太粗,不好控制事務範圍,是出現大事務問題的最常見的原因。
可以使用編程式事務,在spring項目中使用TransactionTemplate類的對象,手動執行事務。(推薦)
部分代碼如下:
@Autowired private TransactionTemplate transactionTemplate; ... public void save(final User user) { transactionTemplate.execute(new TransactionCallback(){ @Override public Boolean doInTransaction(TransactionStatus status) { doSameThing()... return Boolean.TRUE; } }); }
從上面的代碼中可以看出,使用TransactionTemplate的編程式事務功能自己靈活控制事務的範圍,是避免大事務問題的首選辦法。
將查詢(select)方法放到事務外
如果出現大事務,可以將查詢(select)方法放到事務外,也是比較常用的做法,因爲一般情況下這類方法是不需要事務的。
比如出現如下代碼(不推薦使用)
@Transactional(rollbackFor=Exception.class) public void save(User user) { queryData1(); queryData2(); insertA(user); updateB(user); }
可以將queryData1和queryData2兩個查詢方法放在事務外執行,將真正需要事務執行的代碼才放到事務中,比如:addData1和updateData2方法,這樣就能有效的減少事務的粒度。
改寫如下(推薦使用):
@Autowired private TransactionTemplate transactionTemplate; ... public void save(final User user) { queryData1(); queryData2(); transactionTemplate.execute(new TransactionCallback(){ @Override public Boolean doInTransaction(TransactionStatus status) { addData1(); updateData2(); return Boolean.TRUE; } }); }
但是如果你實在還是想用@Transactional註解,該怎麼拆分呢?
這個寫法是否有問題?
public void save(User user) { queryData1(); queryData2(); doSave(); } @Transactional(rollbackFor=Exception.class) public void doSave(User user) { insertA(user); updateB(user); }
這個例子是非常經典的錯誤,這種直接方法調用的做法事務不會生效,給正在坑中的朋友提個醒。因爲@Transactional註解的聲明式事務是通過spring aop起作用的,而spring aop需要生成代理對象,直接方法調用使用的還是原始對象,所以事務不會生效。
有沒有辦法解決這個問題呢?
1.新加一個Service方法
@Service public class ServiceA { @Autowired prvate ServiceB serviceB; public void save(User user) { queryData1(); queryData2(); serviceB.doSave(user); } } @Service public class ServiceB { @Transactional(rollbackFor=Exception.class) public void doSave(User user) { insertA(user); updateB(user); } }
2.在該Service類中注入自己
可以使用這樣(可以使用):
@Service public class ServiceA { @Autowired prvate ServiceA serviceA; public void save(User user) { queryData1(); queryData2(); serviceA.doSave(user); } @Transactional(rollbackFor=Exception.class) public void doSave(User user) { insertA(user); updateB(user); } }
3.在該Service類中使用AopContext.currentProxy()獲取代理對象
還可以通過在該Service類中使用AOPProxy獲取代理對象,實現相同的功能。具體代碼如下(可以使用):
@Servcie public class ServiceA { public void save(User user) { queryData1(); queryData2(); ((ServiceA)AopContext.currentProxy()).doSave(user); } @Transactional(rollbackFor=Exception.class) public void doSave(User user) { insertA(user); updateB(user); } }
事務中避免遠程調用
如果遠程調用的代碼放在某個事物中,這個事物就可能是大事務。當然,遠程調用不僅僅是指調用接口,還有包括:發MQ消息,或者連接redis、mongodb保存數據等
(不推薦使用):
@Transactional(rollbackFor=Exception.class) public void save(User user) { callRemoteApi(); addData1(); }
遠程調用的代碼可能耗時較長,切記一定要放在事務之外。(推薦使用):
@Autowired private TransactionTemplate transactionTemplate; ... public void save(final User user) { callRemoteApi(); transactionTemplate.execute((status) => { addData1(); return Boolean.TRUE; }) }
異步處理
(不推薦使用):
@Autowired private TransactionTemplate transactionTemplate; ... public void save(final User user) { transactionTemplate.execute((status) => { order(); delivery(); return Boolean.TRUE; }) }
答案是否定的。
這裏發貨功能其實可以走mq異步處理邏輯。
(推薦使用):
@Autowired private TransactionTemplate transactionTemplate; ... public void save(final User user) { transactionTemplate.execute((status) => { order(); return Boolean.TRUE; }) sendMq(); }
總結
本人結合自己實際的工作經驗分享了處理大事務的6種辦法:
- 少用@Transactional註解,減少事務的粒度,推薦使用TransactionTemplate
- 將查詢(select)方法放到事務外
- 事務中避免遠程調用
- 事務中避免一次性處理太多數據
- 非事務執行
- 異步處理