前言
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