這裏面有幾點需要大家留意:
A. 一個功能是否要事務,必須納入設計、編碼考慮。不能僅僅完成了基本功能就ok。
B. 如果加了事務,必須做好開發環境測試(測試環境也儘量觸發異常、測試回滾),確保事務生效。
C. 以下列了事務使用過程的注意事項,請大家留意。
1. 不要在接口上聲明@Transactional ,而要在具體類的方法上使用 @Transactional 註解,否則註解可能無效。
2.不要圖省事,將@Transactional放置在類級的聲明中,放在類聲明,會使得所有方法都有事務。故@Transactional應該放在方法級別,不需要使用事務的方法,就不要放置事務,比如查詢方法。否則對性能是有影響的。
3.使用了@Transactional的方法,對同一個類裏面的方法調用, @Transactional無效。比如有一個類Test,它的一個方法A,A再調用Test本類的方法B(不管B是否public還是private),但A沒有聲明註解事務,而B有。則外部調用A之後,B的事務是不會起作用的。(經常在這裏出錯)
4.使用了@Transactional的方法,只能是public,@Transactional註解的方法都是被外部其他類調用纔有效,故只能是public。道理和上面的有關聯。故在 protected、private 或者 package-visible 的方法上使用 @Transactional 註解,它也不會報錯,但事務無效。
5.經過在ICORE-CLAIM中測試,效果如下:
A.拋出受查異常XXXException,事務會回滾。
B.拋出運行時異常NullPointerException,事務會回滾。
C.Quartz中,execute直接調用加了@Transactional方法,可以回滾;間接調用,不會回滾。(即上文3點提到的)
D.異步任務中,execute直接調用加了@Transactional方法,可以回滾;間接調用,不會回滾。(即上文3點提到的)
E.在action中加上@Transactional,不會回滾。切記不要在action中加上事務。
F.在service中加上@Transactional,如果是action直接調該方法,會回滾,如果是間接調,不會回滾。(即上文3提到的)
G.在service中的private加上@Transactional,事務不會回滾。
在同一個類中,一個方法調用另外一個有註解(比如@Async,@Transational)的方法,註解是不會生效的。
比如,下面代碼例子中,有兩方法,一個有@Transational註解,一個沒有。如果調用了有註解的addPerson()方法,會啓動一個Transaction;如果調用updatePersonByPhoneNo(),因爲它內部調用了有註解的addPerson(),如果你以爲系統也會爲它啓動一個Transaction,那就錯了,實際上是沒有的。
[java] view plain copy
- @Service
- public class PersonServiceImpl implements PersonService {
- @Autowired
- PersonDao personDao;
- @Override
- @Transactional
- public boolean addPerson(Person person) {
- boolean result = personDao.insertPerson(person)>0 ? true : false;
- return result;
- }
- @Override
- //@Transactional
- public boolean updatePersonByPhoneNo(Person person) {
- boolean result = personDao.updatePersonByPhoneNo(person)>0 ? true : false;
- addPerson(person); //測試同一個類中@Transactional是否起作用
- return result;
- }
- }
如何查看是否啓動了Transaction?
設置log leve爲debug,可以查看是否有下面這個log,判斷是否啓動了Transaction:
DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Creating new transaction with name...
同樣地,@Async等其他註解也有這樣的問題。
(關於@Async的用法,請參考:http://blog.csdn.net/clementad/article/details/47403185)
原因:
spring 在掃描bean的時候會掃描方法上是否包含@Transactional註解,如果包含,spring會爲這個bean動態地生成一個子類(即代理類,proxy),代理類是繼承原來那個bean的。此時,當這個有註解的方法被調用的時候,實際上是由代理類來調用的,代理類在調用之前就會啓動transaction。然而,如果這個有註解的方法是被同一個類中的其他方法調用的,那麼該方法的調用並沒有通過代理類,而是直接通過原來的那個bean,所以就不會啓動transaction,我們看到的現象就是@Transactional註解無效。
爲什麼一個方法a()調用同一個類中另外一個方法b()的時候,b()不是通過代理類來調用的呢?可以看下面的例子(爲了簡化,用僞代碼表示):
[java] view plain copy
- @Service
- class A{
- @Transactinal
- method b(){...}
- method a(){ //標記1
- b();
- }
- }
- //Spring掃描註解後,創建了另外一個代理類,併爲有註解的方法插入一個startTransaction()方法:
- class proxy$A{
- A objectA = new A();
- method b(){ //標記2
- startTransaction();
- objectA.b();
- }
- method a(){ //標記3
- objectA.a(); //由於a()沒有註解,所以不會啓動transaction,而是直接調用A的實例的a()方法
- }
- }
當我們調用A的bean的a()方法的時候,也是被proxy$A攔截,執行proxy$A.a()(標記3),然而,由以上代碼可知,這時候它調用的是objectA.a(),也就是由原來的bean來調用a()方法了,所以代碼跑到了“標記1”。由此可見,“標記2”並沒有被執行到,所以startTransaction()方法也沒有運行。
瞭解了失效的原因,解決的方法就簡單了(兩種):
-
- 把這兩個方法分開到不同的類中;
- 把註解加到類名上面;