轉自:
https://blog.csdn.net/cp026la/article/details/86496788
扯淡:
復雜的業務邏輯中一個請求可能需要多次操作數據庫,要保證一個Service 方法中多個 dao 的操作同時成功(失敗),事務的配置就很重要了。
大概分三種情況:
1、分布式事務:即多模塊中事務,分布式事務建議是可以避免就避免,可以使用消息中間件處理,但也不能完全解決。
2、多線程事務:參考:https://blog.csdn.net/kongkongyanan/article/details/81703415
3、單模塊中的事務,本章的重點,也是開發中遇到的最多的,這里給出兩種配置方式。
本章分別使用注解 @Transactional (springboot默認推薦)和 AOP 全局配置的方式:
pom 依賴(延續上一章代碼)增加aop的依賴:
<!-- aop --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
一、事務演示:
需要引入spring-boot-starter-jdbc依賴,但是我們集成了mybatis,已經包含了該依賴,不必重復引入。
1、controller層代碼:在UserController中添加測試的方法
@GetMapping @RequestMapping(value = "add/{number}") public ApiResult add(@PathVariable("number") Integer number){ userService.addUser(number); return ApiResult.ok(); }
2、service層代碼:在UserService中添加測試方法,設置一個可控的除以0異常
public void addUser(int number){ User user = new User(); user.setId(22); user.setName("xiaoming"); user.setAge(12); mapper.insertSelective(user); int num = 10/number; User user2 = new User(); user2.setId(23); user2.setName("xiaohong"); user2.setAge(90); mapper.insertSelective(user2); }
3、測試
3.1、請求接口:http://localhost:8080/user/add/10 數據庫正常插入兩條數據
3.2、刪除剛插入的兩條數據。請求接口:http://localhost:8080/user/add/0 制造除以0 異常。
現象:數據庫此時只插入第一條數據(xiaoming)。
分析:插入第二條數據(xiaohong)之前,出現除以0異常,造成程序中斷。
需求: 這種在同一個邏輯中的多個dao操作要么同時成功、要么同時失敗,不允許只成功一個dao操作的情況出現。
二、使用@Transactional 配置事務:
1、在service層addUser 方法上添加@Transactional注解
@Transactional(rollbackFor = Exception.class) public void addUser(int number){ ... }
2、測試:刪除數據庫中已經成功插入的數據。
2.1、請求接口:http://localhost:8080/user/add/0 制造除以0 異常。
2.2、結果:出現除以0 異常,數據庫並未插入任何數據,即實現了多個dao操作同時成功,同時失敗。
3、注意點:
1、spring 事務默認回滾的是運行時異常(RuntimeException)和錯誤(Error),非檢測異常,例如SQLException 不會回滾。加上rollbackFor = Exception.class 可解決。
2、在service中使用了try catch 捕獲了異常,事務不會回滾,因為try catch 異常之后就相當於沒有異常,建議異常在controller中統一處理。
三、使用 AOP 全局處理事務:
需要引入 aop 的依賴。
1、新建全局事務配置類(個人放置到config包中,方便管理):
/** * spring aop 配置全局事務 */ @Aspect @Configuration public class TransactionAdviceConfig { /* * 定義切入點 * execution()是最常用的切點函數 * execution (com.coolron.user.service.impl..*.*(..)) * 1、execution(): 表達式主體。 * 2、第一個*號:表示返回類型,*號表示所有的類型。 * 3、包名:表示需要攔截的包名,后面的兩個句點表示當前包和當前包的所有子包,com.sample.service.impl包、子孫包下所有類的方法。 * 4、第二個*號:表示類名,*號表示所有的類。 * 5、*(..):最后這個星號表示方法名,*號表示所有的方法,后面括弧里面表示方法的參數,兩個句點表示任何參數。 */ private static final String AOP_POINTCUT_EXPRESSION = "execution(* com.coolron.user.service.impl..*.*(..))"; @Autowired private PlatformTransactionManager transactionManager; @Bean public TransactionInterceptor txAdvice() { DefaultTransactionAttribute txAttr_REQUIRED = new DefaultTransactionAttribute(); txAttr_REQUIRED.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); DefaultTransactionAttribute txAttr_REQUIRED_READONLY = new DefaultTransactionAttribute(); txAttr_REQUIRED_READONLY.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); txAttr_REQUIRED_READONLY.setReadOnly(true); NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource(); // service 中方法前綴 source.addTransactionalMethod("add*", txAttr_REQUIRED); source.addTransactionalMethod("save*", txAttr_REQUIRED); source.addTransactionalMethod("delete*", txAttr_REQUIRED); source.addTransactionalMethod("update*", txAttr_REQUIRED); return new TransactionInterceptor(transactionManager, source); } @Bean public Advisor txAdviceAdvisor() { AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); pointcut.setExpression(AOP_POINTCUT_EXPRESSION); return new DefaultPointcutAdvisor(pointcut, txAdvice()); } }
2、測試:
將 Service 中addUser 方法上的 @Transactional 注解注釋,刪除數據。
2.1、請求接口:http://localhost:8080/user/add/10 正常操作,數據庫插入兩條數據。
2.2、刪除剛插入的兩條數據。請求接口:http://localhost:8080/user/add/0 制造除以0 異常。
結果:出現異常,數據庫並未插入數據。
3、注意點:
1、正確配置切入點的位置,本章配置的是:
execution(* com.coolron.user.service.impl….(…)),也可將user換成 * 指定多個位置
2、service 層中方法名應嚴格按照全局事務配置類中定義的規則來命名。
3、try catch 同樣也不要在service中使用。
至此,單模塊中的事務配置已完成,也是平時開發中最遇到的,對於分布式事務(即多模塊),盡量將涉及到事務的業務邏輯放到一個模塊中處理,使用消息中間件處理也不是最佳方案。