您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“Spring聲明式事務(wù)什么情況下會失效”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“Spring聲明式事務(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ù)提交和回滾的邏輯。
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("保存用戶失敗"); } }
可以通過如下方式解決
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
@Autowired注入自己,假如為self,然后通過self調(diào)用方法
@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)
假如說有這樣一個場景,用戶注冊,依次保存用戶基本信息到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í)!
免責(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)容。