溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Spring聲明式事務(wù)什么情況下會失效

發(fā)布時間:2021-09-09 15:03:17 來源:億速云 閱讀:121 作者:chen 欄目:開發(fā)技術(shù)

本篇內(nèi)容主要講解“Spring聲明式事務(wù)什么情況下會失效”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“Spring聲明式事務(wù)什么情況下會失效”吧!

編程式事務(wù)

在Spring中事務(wù)管理的方式有兩種,編程式事務(wù)和聲明式事務(wù)。先詳細(xì)介紹一下兩種事務(wù)的實(shí)現(xiàn)方式.

配置類

@Configuration @EnableTransactionManagement @ComponentScan("com.javashitang") public class AppConfig {      @Bean     public DruidDataSource dataSource() {         DruidDataSource ds = new DruidDataSource();         ds.setDriverClassName("com.mysql.jdbc.Driver");         ds.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=true");         ds.setUsername("test");         ds.setPassword("test");         ds.setInitialSize(5);         return ds;     }      @Bean     public DataSourceTransactionManager dataSourceTransactionManager() {         return new DataSourceTransactionManager(dataSource());     }      @Bean     public JdbcTemplate jdbcTemplate(DataSource dataSource) {         return new JdbcTemplate(dataSource);     }      @Bean     public TransactionTemplate transactionTemplate() {         return new TransactionTemplate(dataSourceTransactionManager());     } }
public interface UserService {     void addUser(String name, String location);     default void doAdd(String name) {}; }
@Service public class UserServiceV1Impl implements UserService {      @Autowired     private JdbcTemplate jdbcTemplate;     @Autowired     private TransactionTemplate transactionTemplate;      @Override     public void addUser(String name, String location) {         transactionTemplate.execute(new TransactionCallbackWithoutResult() {              @Override             protected void doInTransactionWithoutResult(TransactionStatus status) {                 try {                     String sql = "insert into user (`name`) values (?)";                     jdbcTemplate.update(sql, new Object[]{name});                     throw new RuntimeException("保存用戶信息失敗");                 } catch (Exception e) {                     e.printStackTrace();                     status.setRollbackOnly();                 }             }         });     } }

可以看到編程式事務(wù)的方式并不優(yōu)雅,因?yàn)闃I(yè)務(wù)代碼和事務(wù)代碼耦合到一塊,當(dāng)發(fā)生異常的時候還得需要手動回滾事務(wù)(比使用JDBC方便多類,JDBC得先關(guān)閉自動自動提交,然后根據(jù)情況手動提交或者回滾事務(wù))

如果讓你優(yōu)化事務(wù)方法的執(zhí)行?你會如何做?

「其實(shí)我們完全可以用AOP來優(yōu)化這種代碼,設(shè)置好切點(diǎn),當(dāng)方法執(zhí)行成功時提交事務(wù),當(dāng)方法發(fā)生異常時回滾事務(wù),這就是聲明式事務(wù)的實(shí)現(xiàn)原理」

使用AOP后,當(dāng)我們調(diào)用事務(wù)方法時,會調(diào)用到生成的代理對象,代理對象中加入了事務(wù)提交和回滾的邏輯。

聲明式事務(wù)

Spring aop動態(tài)代理的方式有如下幾種方法

JDK動態(tài)代理實(shí)現(xiàn)(基于接口)(JdkDynamicAopProxy)

CGLIB動態(tài)代理實(shí)現(xiàn)(動態(tài)生成子類的方式)(CglibAopProxy)

AspectJ適配實(shí)現(xiàn)

spring aop默認(rèn)只會使用JDK和CGLIB來生成代理對象

@Transactional可以用在哪里?

@Transactional可以用在類,方法,接口上

用在類上,該類的所有public方法都具有事務(wù)

用在方法上,方法具有事務(wù)。當(dāng)類和方法同時配置事務(wù)的時候,方法的屬性會覆蓋類的屬性

用在接口上,一般不建議這樣使用,因?yàn)橹挥谢诮涌诘拇頃?,如果Spring  AOP使用cglib來實(shí)現(xiàn)動態(tài)代理,會導(dǎo)致事務(wù)失效(因?yàn)樽⒔獠荒鼙焕^承)

@Transactional失效的場景

@Transactional注解應(yīng)用到非public方法(除非特殊配置,例如使用AspectJ 靜態(tài)織入實(shí)現(xiàn) AOP)

自調(diào)用,因?yàn)锧Transactional是基于動態(tài)代理實(shí)現(xiàn)的

異常在代碼中被你自己try catch了

異常類型不正確,默認(rèn)只支持RuntimeException和Error,不支持檢查異常

事務(wù)傳播配置不符合業(yè)務(wù)邏輯

@Transactional注解應(yīng)用到非public方法

「為什么只有public方法上的@Transactional注解才會生效?」

首相JDK動態(tài)代理肯定只能是public,因?yàn)榻涌诘臋?quán)限修飾符只能是public。cglib代理的方式是可以代理protected方法的(private不行哈,子類訪問不了父類的private方法)如果支持protected,可能會造成當(dāng)切換代理的實(shí)現(xiàn)方式時表現(xiàn)不同,增大出現(xiàn)bug的可能醒,所以統(tǒng)一一下。

「如果想讓非public方法也生效,你可以考慮使用AspectJ」

自調(diào)用,因?yàn)锧Transactional是基于動態(tài)代理實(shí)現(xiàn)的

當(dāng)自調(diào)用時,方法執(zhí)行不會經(jīng)過代理對象,所以會導(dǎo)致事務(wù)失效。例如通過如下方式調(diào)用addUser方法時,事務(wù)會失效

// 事務(wù)失效 @Service public class UserServiceV2Impl implements UserService {      @Autowired     private JdbcTemplate jdbcTemplate;      @Override     public void addUser(String name, String location) {         doAdd(name);     }      @Transactional     public void doAdd(String name) {         String sql = "insert into user (`name`) values (?)";         jdbcTemplate.update(sql, new Object[]{name});         throw new RuntimeException("保存用戶失敗");     } }

可以通過如下方式解決

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. @Autowired注入自己,假如為self,然后通過self調(diào)用方法

  3. @Autowired ApplicationContext,從ApplicationContext通過getBean獲取自己,然后再調(diào)用

// 事務(wù)生效 @Service public class UserServiceV2Impl implements UserService {      @Autowired     private JdbcTemplate jdbcTemplate;     @Autowired     private UserService userService;      @Override     public void addUser(String name, String location) {         userService.doAdd(name);     }      @Override     @Transactional     public void doAdd(String name) {         String sql = "insert into user (`name`) values (?)";         jdbcTemplate.update(sql, new Object[]{name});         throw new RuntimeException("保存用戶失敗");     } }

異常在代碼中被你自己try catch了

這個邏輯從源碼理解比較清晰,只有當(dāng)執(zhí)行事務(wù)拋出異常才能進(jìn)入completeTransactionAfterThrowing方法,這個方法里面有回滾的邏輯,如果事務(wù)方法都沒拋出異常就只會正常提交

// org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction  try {   // This is an around advice: Invoke the next interceptor in the chain.   // This will normally result in a target object being invoked.   // 執(zhí)行事務(wù)方法   retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) {   // target invocation exception   completeTransactionAfterThrowing(txInfo, ex);   throw ex; } finally {   cleanupTransactionInfo(txInfo); }

異常類型不正確,默認(rèn)只支持RuntimeException和Error,不支持檢查異常

異常體系圖如下。當(dāng)拋出檢查異常時,spring事務(wù)不會回滾。如果拋出任何異常都回滾,可以配置rollbackFor為Exception

@Transactional(rollbackFor = Exception.class)

Spring聲明式事務(wù)什么情況下會失效

事務(wù)傳播配置不符合業(yè)務(wù)邏輯

假如說有這樣一個場景,用戶注冊,依次保存用戶基本信息到user表中,用戶住址信息到地址表中,當(dāng)保存用戶住址信息失敗時,我們也要保證用戶信息注冊成功。

public interface LocationService {     void addLocation(String location); }
@Service public class LocationServiceImpl implements LocationService {      @Autowired     private JdbcTemplate jdbcTemplate;      @Override     @Transactional     public void addLocation(String location) {         String sql = "insert into location (`name`) values (?)";         jdbcTemplate.update(sql, new Object[]{location});         throw new RuntimeException("保存地址異常");     } }
@Service public class UserServiceV3Impl implements UserService {      @Autowired     private JdbcTemplate jdbcTemplate;     @Autowired     private LocationService locationService;      @Override     @Transactional     public void addUser(String name, String location) {         String sql = "insert into user (`name`) values (?)";         jdbcTemplate.update(sql, new Object[]{name});         locationService.addLocation(location);     } }

調(diào)用發(fā)現(xiàn)user表和location表都沒有插入數(shù)據(jù),并不符合我們期望,你可能會說拋出異常了,事務(wù)當(dāng)然回滾了。好,我們把調(diào)用locationService的部分加上try  catch

@Service public class UserServiceV3Impl implements UserService {      @Autowired     private JdbcTemplate jdbcTemplate;     @Autowired     private LocationService locationService;      @Override     @Transactional     public void addUser(String name, String location) {         String sql = "insert into user (`name`) values (?)";         jdbcTemplate.update(sql, new Object[]{name});         try {             locationService.addLocation(location);         } catch (Exception e) {             e.printStackTrace();         }     } }

調(diào)用發(fā)現(xiàn)user表和location表還是都沒有插入數(shù)據(jù)。這是因?yàn)樵贚ocationServiceImpl中事務(wù)已經(jīng)被標(biāo)記成回滾了,所以最終事務(wù)還會回滾。

要想最終解決就不得不提到Spring的事務(wù)傳播行為了,不清楚的小伙伴看《面試官:Spring事務(wù)的傳播行為有幾種?》

Transactional的事務(wù)傳播行為默認(rèn)為Propagation.REQUIRED?!溉绻?dāng)前存在事務(wù),則加入該事務(wù)。如果當(dāng)前沒有事務(wù),則創(chuàng)建一個新的事務(wù)」

此時我們把LocationServiceImpl中Transactional的事務(wù)傳播行為改成Propagation.REQUIRES_NEW即可

「創(chuàng)建一個新事務(wù),如果當(dāng)前存在事務(wù),則把當(dāng)前事務(wù)掛起」

所以最終的解決代碼如下

@Service public class UserServiceV3Impl implements UserService {      @Autowired     private JdbcTemplate jdbcTemplate;     @Autowired     private LocationService locationService;      @Override     @Transactional     public void addUser(String name, String location) {         String sql = "insert into user (`name`) values (?)";         jdbcTemplate.update(sql, new Object[]{name});         try {             locationService.addLocation(location);         } catch (Exception e) {             e.printStackTrace();         }     } } @Service public class LocationServiceImpl implements LocationService {      @Autowired     private JdbcTemplate jdbcTemplate;      @Override     @Transactional(propagation = Propagation.REQUIRES_NEW)     public void addLocation(String location) {         String sql = "insert into location (`name`) values (?)";         jdbcTemplate.update(sql, new Object[]{location});         throw new RuntimeException("保存地址異常");     } }
@Service public class LocationServiceImpl implements LocationService {      @Autowired     private JdbcTemplate jdbcTemplate;      @Override     @Transactional(propagation = Propagation.REQUIRES_NEW)     public void addLocation(String location) {         String sql = "insert into location (`name`) values (?)";         jdbcTemplate.update(sql, new Object[]{location});         throw new RuntimeException("保存地址異常");     } }

到此,相信大家對“Spring聲明式事務(wù)什么情況下會失效”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI