Spring事務註解@Transactional 原理及不生效原因分析
看了網上很多篇介紹Spring事務註解@Transactional不生效的文章,
大都只是講了不生效的場景,對於不生效的原因都是一筆帶過。
下面我們一步步地分析一下為什麼有些場景該註解的事務是不生效的。
要想知道原因我們首先要了解該方式的事務是如何實現的。
一、Spring事務註解@Transactional的實現原理:
Spring中事務註解@Transactional是AOP程式設計思想的一種體現,
AOP(Aspect Oriented Programming)面向切面程式設計的具體實現方式之一就是動態代理技術。
Spring中AOP的底層實現方式就是動態代理,也就是說@Transactional事務註解是使用動態代理技術實現的。
當我們某個類的某個方法上使用了@Transactional註解時,我們在呼叫這個這個類的這個方法的時候,
Spring給我們使用的物件實際上是一個代理物件,呼叫的方法也是代理物件的方法,從而可以做到在代理物件的方法中先開啟事務,再呼叫真實物件的方法做資料更新,最後做事務的提交和回滾。
Spring中對使用JDK和CGLib兩種方式的動態代理技術都進行了實現,下面我們分別使用這兩種方式實現一下面向切面的事務。
假設我們有這麼一個業務類UserService,方法update中有三步資料更新,我們可以透過代理讓這三步放入一個事務中:
interface UserService { void update();}class UserSericeImpl implements UserService { @Resource private UserDao userDao; @Resource private RoleDao roleDao; @Resource private XxxDao xxxDao; /** * 此方法中沒有任何事務相關的邏輯, * 事務的實現我們透過動態代理,在代理類中實現 **/ @Override public void update() { // 資料更新操作1 userDao.update(); // 資料更新操作2 roleDao.update(); // 資料更新操作3 xxxDao.update(); }}// JDK方式class JdkProxy { @Autowired @Qualifier("userServiceImpl") private UserService userService; @Autowired private Connection connection; @Bean public UserService createJdkProxy() { return (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object obj = null; try { // 開啟事務 connection.setAutoCommit(false); // 呼叫被代理物件的方法進行資料更新 obj = method.invoke(userService, args); // 提交事務 connection.commit(); } catch (RuntimeException ex) { // 回滾事務 connection.rollback(); } finally { // 關閉連線 connection.close(); } return obj; } }); }}// CGLib方式class CglibProxy { @Autowired @Qualifier("userServiceImpl") private UserService userService; @Autowired private Connection connection; @Bean public UserService createCglibProxy() { return (UserService) Enhancer.create(UserService.class, new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { Object obj = null; try { // 開啟事務 connection.setAutoCommit(false); // 呼叫被代理物件的方法進行資料更新 obj = method.invoke(userService, args); // 提交事務 connection.commit(); } catch (RuntimeException ex) { // 回滾事務 connection.rollback(); } finally { // 關閉連線 connection.close(); } return obj; } }); }}
我們呼叫時候只需要使用兩個代理類的代理物件即可。
注:上面的程式碼只是對原理的描述,直接使用無法執行(Connection類我這裡是沒有實現的)。
從上面的例子中我們可以看出,只有呼叫了代理類中的方法才能使事務生效,如果直接呼叫UserService.update,是沒有事務的邏輯的。
事實上,Spring中的@Transactional就是這麼一個原理實現的。大家可以自行去扒原始碼,我這裡只是做一個理解性的原理解析。
二、為什麼有時我們加了事務註解也不生效
根據上面的原理分析,我們可以得到如下的結論:
如果一個類不能被動態代理,那麼就一定無法使用@Transactional進行事務控制如果一個類的某個方法無法被他的代理類訪問,那麼也無法使用@Transactional進行事務控制如果呼叫方法時的物件不是代理類物件,那麼也無法使用@Transactional進行事務控制下面我們舉幾個常見的場景例子進行說明。
場景一:
interface UserService { void update();}class UserSericeImpl implements UserService { @Resource private UserDao userDao; @Resource private RoleDao roleDao; @Resource private XxxDao xxxDao; @Override public void update() { update2(); } @Transactional // 此處事務註解 private void update2() { // 資料更新操作1 userDao.update(); // 資料更新操作2 roleDao.update(); // 資料更新操作3 xxxDao.update(); }}
私有方法由於無法在代理類中訪問,所以一定是不生效的。
場景二:
interface UserService { void update(); void update2();}class UserSericeImpl implements UserService { @Resource private UserDao userDao; @Resource private RoleDao roleDao; @Resource private XxxDao xxxDao; @Override public void update() { update2(); } @Transactional // 此處加事務註解 @Override public void update2() { // 資料更新操作1 userDao.update(); // 資料更新操作2 roleDao.update(); // 資料更新操作3 xxxDao.update(); }}
此場景中:
在其他類中直接呼叫update2()方法,事務生效在其他類中直接呼叫update()方法,事務不生效!不生效的原因是上面的結論3中的情況,呼叫update2方法的物件不是代理物件,而是UserService的本身物件。場景三:
interface UserService { void update(); void update2();}class UserSericeImpl implements UserService { @Resource private UserDao userDao; @Resource private RoleDao roleDao; @Resource private XxxDao xxxDao; @Transactional // 此處加事務註解 @Override public void update() { update2(); } @Transactional // 此處也加事務註解 @Override public void update2() { // 資料更新操作1 userDao.update(); // 資料更新操作2 roleDao.update(); // 資料更新操作3 xxxDao.update(); }}
此場景中:
在其他類中直接呼叫update2()方法,事務生效在其他類中直接呼叫update()方法,事務生效! 此時生效的事務是update()方法上的事務,update2上的事務註解依然是無效的,但是由於事務的傳播性,update2中的更新操作會加入到update1的事務中。場景四:
interface UserService { void update();}class UserSericeImpl implements UserService { @Resource private UserDao userDao; @Resource private RoleDao roleDao; @Resource private XxxDao xxxDao; @Transactional // 此處新增事務註解 @Override public void update() { update2(); } public void update2() { // 資料更新操作1 userDao.update(); update3(); } private void update3() { // 資料更新操作2 roleDao.update(); // 資料更新操作3 xxxDao.update(); }}
此場景中:
在其他類中直接呼叫update2()方法,事務當然不生效! 沒有新增事務註解。在其他類中直接呼叫update()方法,事務生效! 由於事務的傳播性,update2 和 update3中的更新操作都會加入到update1的事務中。場景五:
interface UserService { void update(); void update2();}class UserSericeImpl implements UserService { @Resource private UserDao userDao; @Resource private RoleDao roleDao; @Resource private XxxDao xxxDao; @Resource private UserService userService; @Override public void update() { userService.update2(); } @Transactional @Override public void update2() { // 資料更新操作1 userDao.update(); // 資料更新操作2 roleDao.update(); // 資料更新操作3 xxxDao.update(); }}
此場景中:
在其他類中直接呼叫update2()方法,事務生效在其他類中直接呼叫update()方法,事務生效。此處生效的原因是訪問update2方法的物件是透過注入的,是spring給的,是一個代理物件,因此事務生效。更多場景不再列舉了,如果你的Spring事務不生效,請從我們總結的三點結論中尋找答案:
如果一個類不能被動態代理,那麼就一定無法使用@Transactional進行事務控制如果一個類的某個方法無法被他的代理類訪問,那麼也無法使用@Transactional進行事務控制如果呼叫方法時的物件不是代理類物件,那麼也無法使用@Transactional進行事務控制注:@Transactional(rollbackFor = Exception.class),大家還需要注意rollbackFor的條件,如果省略不寫,Spring預設觸發回滾的異常是執行時異常(RuntimeException)。所以,如果你的事務沒回滾時,請先確認發生的異常型別和rollbackFor屬性指定的是否一致。如果不匹配,即使你的事務是生效的,也不會觸發回滾。