前言
spring事務失效場景可能大家在很多文章都看過了,所以今天就水一篇,看大家能不能收獲一些不一樣的東西。直接進入主題
spring事務失效場景以及原因
1、場景一:service沒有托管給spring
public class TranInvalidCaseWithoutInjectSpring {
private UserService userService;
public TranInvalidCaseWithoutInjectSpring(UserService userService) {
this.userService = userService;
}
@Transactional
public boolean add(User user){
boolean isSuccess = userService.save(user);
int i = 1 % 0;
return isSuccess;
}
}
@Test
public void testServiceWithoutInjectSpring(){
boolean randomBoolean = new Random().nextBoolean();
TranInvalidCaseWithoutInjectSpring tranInvalidCaseWithoutInjectSpring;
if(randomBoolean){
tranInvalidCaseWithoutInjectSpring = applicationContext.getBean(TranInvalidCaseWithoutInjectSpring.class);
System.out.println("service已經被spring托管");
}else{
tranInvalidCaseWithoutInjectSpring = new TranInvalidCaseWithoutInjectSpring(userService);
System.out.println("service沒被spring托管");
}
boolean isSuccess = tranInvalidCaseWithoutInjectSpring.add(user);
Assert.assertTrue(isSuccess);
}
失效原因: spring事務生效的前提是,service必須是一個bean對象
解決方案: 將service注入spring
2、場景二:拋出受檢異常
@Service
public class TranInvalidCaseByThrowCheckException {
@Autowired
private UserService userService;
@Transactional
public boolean add(User user) throws FileNotFoundException {
boolean isSuccess = userService.save(user);
new FileInputStream("1.txt");
return isSuccess;
}
}
@Test
public void testThrowCheckException() throws Exception{
boolean randomBoolean = new Random().nextBoolean();
boolean isSuccess = false;
TranInvalidCaseByThrowCheckException tranInvalidCaseByThrowCheckException = applicationContext.getBean(TranInvalidCaseByThrowCheckException.class);
if(randomBoolean){
System.out.println("配置@Transactional(rollbackFor = Exception.class)");
isSuccess = tranInvalidCaseByThrowCheckException.save(user);
}else{
System.out.println("配置@Transactional");
tranInvalidCaseByThrowCheckException.add(user);
}
Assert.assertTrue(isSuccess);
}
失效原因: spring默認只會回滾非檢查異常和error異常
解決方案: 配置rollbackFor
3、場景三:業務自己捕獲了異常
@Transactional
public boolean add(User user) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
}
return isSuccess;
}
@Test
public void testCatchExecption() throws Exception{
boolean randomBoolean = new Random().nextBoolean();
boolean isSuccess = false;
TranInvalidCaseWithCatchException tranInvalidCaseByThrowCheckException = applicationContext.getBean(TranInvalidCaseWithCatchException.class);
if(randomBoolean){
randomBoolean = new Random().nextBoolean();
if(randomBoolean){
System.out.println("將異常原樣拋出");
tranInvalidCaseByThrowCheckException.save(user);
}else{
System.out.println("設置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();");
tranInvalidCaseByThrowCheckException.addWithRollBack(user);
}
}else{
System.out.println("業務自己捕獲了異常");
tranInvalidCaseByThrowCheckException.add(user);
}
Assert.assertTrue(isSuccess);
}
失效原因: spring事務只有捕捉到了業務拋出去的異常,才能進行后續的處理,如果業務自己捕獲了異常,則事務無法感知
解決方案:
1、將異常原樣拋出;
2、設置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
4、場景四:切面順序導致
@Service
public class TranInvalidCaseWithAopSort {
@Autowired
private UserService userService;
@Transactional
public boolean save(User user) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
}
@Aspect
@Component
@Slf4j
public class AopAspect {
@Around(value = " execution (* com.github.lybgeek.transcase.aopsort..*.*(..))")
public Object around(ProceedingJoinPoint pjp){
try {
System.out.println("這是一個切面");
return pjp.proceed();
} catch (Throwable throwable) {
log.error("{}",throwable);
}
return null;
}
}
失效原因: spring事務切面的優先級順序最低,但如果自定義的切面優先級和他一樣,且自定義的切面沒有正確處理異常,則會同業務自己捕獲異常的那種場景一樣
解決方案:
1、在切面中將異常原樣拋出;
2、在切面中設置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
5、場景五:非public方法
@Service
public class TranInvalidCaseWithAccessPerm {
@Autowired
private UserService userService;
@Transactional
protected boolean save(User user){
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
}
public class TranInvalidCaseWithAccessPermTest {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class);
TranInvalidCaseWithAccessPerm tranInvalidCaseWithAccessPerm = context.getBean(TranInvalidCaseWithAccessPerm.class);
boolean isSuccess = tranInvalidCaseWithAccessPerm.save(UserUtils.getUser());
System.out.println(isSuccess);
}
}
失效原因: spring事務默認生效的方法權限都必須為public
解決方案:
1、將方法改為public;
2、修改TansactionAttributeSource,將publicMethodsOnly改為false【這個從源碼跟蹤得出結論】
3、開啟 AspectJ 代理模式【從spring文檔得出結論】
文檔如下
Method visibility and @Transactional
When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.
具體步驟:
1、在pom引入aspectjrt坐標以及相應插件
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.9</version>
<configuration>
<showWeaveInfo>true</showWeaveInfo>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal> <!-- use this goal to weave all your main classes -->
<goal>test-compile</goal> <!-- use this goal to weave all your test classes -->
</goals>
</execution>
</executions>
</plugin>
2、在啟動類上加上如下配置
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
注: 如果是在idea上運行,則需做如下配置
4、直接用TransactionTemplate
示例:
@Autowired
private TransactionTemplate transactionTemplate;
private void process(){
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
processInTransaction();
}
});
}
6、場景六:父子容器
失效原因: 子容器掃描范圍過大,將未加事務配置的serivce掃描進來
解決方案:
1、父子容器個掃個的范圍;
2、不用父子容器,所有bean都交給同一容器管理
注: 因為示例是使用springboot,而springboot啟動默認沒有父子容器,只有一個容器,因此就該場景就演示示例了
7、場景七:方法用final修飾
@Transactional
public final boolean add(User user, UserService userService) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
失效原因: 因為spring事務是用動態代理實現,因此如果方法使用了final修飾,則代理類無法對目標方法進行重寫,植入事務功能
解決方案:
1、方法不要用final修飾
8、場景八:方法用static修飾
@Transactional
public static boolean save(User user, UserService userService) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
失效原因: 原因和final一樣
解決方案:
1、方法不要用static修飾
9、場景九:調用本類方法
public boolean save(User user) {
return this.saveUser(user);
}
@Transactional
public boolean saveUser(User user) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
失效原因: 本類方法不經過代理,無法進行增強
解決方案:
1、注入自己來調用;
2、使用@EnableAspectJAutoProxy(exposeProxy = true) + AopContext.currentProxy()
10、場景十:多線程調用
@Transactional(rollbackFor = Exception.class)
public boolean save(User user) throws ExecutionException, InterruptedException {
Future<Boolean> future = executorService.submit(() -> {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new Exception();
}
return isSuccess;
});
return future.get();
}
失效原因: 因為spring的事務是通過數據庫連接來實現,而數據庫連接spring是放在threadLocal里面。同一個事務,只能用同一個數據庫連接。而多線程場景下,拿到的數據庫連接是不一樣的,即是屬於不同事務
11、場景十一:錯誤的傳播行為
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public boolean save(User user) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
失效原因: 使用的傳播特性不支持事務
12、場景十二:使用了不支持事務的存儲引擎
失效原因: 使用了不支持事務的存儲引擎。比如mysql中的MyISAM
13、場景十三:數據源沒有配置事務管理器
注: 因為springboot,他默認已經開啟事務管理器。org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration。因此示例略過
14、場景十四:被代理的類過早實例化
@Service
public class TranInvalidCaseInstantiatedTooEarly implements BeanPostProcessor , Ordered {
@Autowired
private UserService userService;
@Transactional
public boolean save(User user) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
@Override
public int getOrder() {
return 1;
}
}
失效原因: 當代理類的實例化早於AbstractAutoProxyCreator后置處理器,就無法被AbstractAutoProxyCreator后置處理器增強
總結
本文列舉了14種spring事務失效的場景,其實這14種里面有很多都是歸根結底都是屬於同一類問題引起,比如因為動態代理原因、方法限定符原因、異常類型原因等
demo鏈接
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-transaction-invalid-case