乐趣区

关于spring:Spring常见知识点Spring事务失效及其原因

1. 抛出查看异样

2. 业务办法内 try-catch

3. 切面程序

4. 非 public 办法,被 final 和 static 润饰的办法

5. 父子容器

6. 应用 this 调用本类办法

7. 多线程下原子性生效

8.Transaction 导致加锁失败

9. 多线程调用

10. 应用了不反对事务的存储引擎

11. 谬误的流传行为

1. 抛出查看异样

产生起因:
咱们在应用 @Transactional 注解的时候,如果 不加 rollbackFor 的类型

@Transactional

它默认抛出的是RuntimeException

咱们能够看一下异样的 类图

也就是说,默认状况下,IOException,SQLException 等异样,不会造成事务的回滚

解决方案:

@Transactional(rollbackFor = Exception.class)

指定回滚的类型为 Exception.class

2. 业务办法内 try-catch

产生起因
业务逻辑代码 内 try-catch 异样,间接消化异样,导致 spring 无奈捕捉到异样。

    @Transactional(rollbackFor = Exception.class)
    public void transfer(int fromAccount, int toAccount, int money) {
        // 模仿一个转账操作
        try {int fromBalance = userAccountMapper.findBalanceByUserAccount(fromAccount);
            if (fromBalance - money >= 0) {
                // 减钱
                userAccountMapper.update(fromAccount, -1 * money);
                // 增钱
                userAccountMapper.update(toAccount, money);
            }

        } catch (Exception e) {e.printStackTrace();
        }
    }

解决方案:
1. 咱们应该 抛出异样 让 spring 捕捉到这个谬误 ,这样就能够进行回滚。
2. 捕捉到异样后手动回滚

    @Transactional(rollbackFor = Exception.class)
    public void transfer(int fromAccount, int toAccount, int money) {
        // 模仿一个转账操作
        try {int fromBalance = userAccountMapper.findBalanceByUserAccount(fromAccount);
            if (fromBalance - money >= 0) {
                // 减钱
                userAccountMapper.update(fromAccount, -1 * money);
                // 增钱
                userAccountMapper.update(toAccount, money);
            }

        } catch (Exception e) {e.printStackTrace();
            //throw new RuntimeException();
            TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
        }
    }

3. 切面程序

产生起因:
事务注解的切面优先级最低 ,如果咱们 自定义的切面就曾经将异样消化掉 ,会 导致事务切面无奈失效。

@Aspect
@Component
@Slf4j
public class AopAspect {
    // 这个切面优先级比事务切面高,后行 catch 异样
    @Around(value = "execution (* com.example.demo.userAccount..*.*(..))")
    public Object around(ProceedingJoinPoint pjp){
        Object result = null;
        try {log.info("执行前");
            result = pjp.proceed();} catch (Throwable throwable) {log.error("{}",throwable);
        }
        log.info("执行后");

        return result;
    }

}

解决方案:
同状况 2
1. 咱们应该抛出异样,让 spring 捕捉到这个谬误,这样就能够进行回滚。
2. 捕捉到异样后手动回滚

4. 非 public 办法,被 final 和 static 润饰的办法

产生起因:
spring 为办法对象创立代理,增加切面的条件是办法必须是 public 的 且不能被 static 和 final 润饰,不然就会导致spring 无奈重写该办法,导致事务生效。

解决方案
应用 public 润饰该办法。

5. 父子容器
这种状况在 springBoot 的时代个别比拟少遇到。

产生起因:

package tx.app.service;

// ...

@Service
public class Service5 {

    @Autowired
    private AccountMapper accountMapper;

    @Transactional(rollbackFor = Exception.class)
    public void transfer(int from, int to, int amount) throws FileNotFoundException {int fromBalance = accountMapper.findBalanceBy(from);
        if (fromBalance - amount >= 0) {accountMapper.update(from, -1 * amount);
            accountMapper.update(to, amount);
        }
    }
}
package tx.app.controller;

// ...

@Controller
public class AccountController {

    @Autowired
    public Service5 service;

    public void transfer(int from, int to, int amount) throws FileNotFoundException {service.transfer(from, to, amount);
    }
}
@Configuration
@ComponentScan("tx.app.service")
@EnableTransactionManagement
// ...
public class AppConfig {// ... 有事务相干配置}
@Configuration
@ComponentScan("tx.app")
// ...
public class WebConfig {// ... 无事务配置}

起因:咱们发现 子容器没有事务配置 然而把所有组件都扫描了进来,事务仍然生效。

解决方案:

解法 1:各自扫描本人的组件

解法 2:不要用父子容器,所有 bean 放在同一容器

6. 应用 this 调用本类办法
咱们应用 this 办法调用注解的办法,会发现事务生效:

    public void transferAccount(int fromAccount, int toAccount, int money) {this.transfer(fromAccount,toAccount,money);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void transfer(int fromAccount, int toAccount, int money) {
        // 模仿一个转账操作
        try {int fromBalance = userAccountMapper.findBalanceByUserAccount(fromAccount);
            if (fromBalance - money >= 0) {
                // 减钱
                userAccountMapper.update(fromAccount, -1 * money);
                // 增钱
                userAccountMapper.update(toAccount, money);
            }

        } catch (Exception e) {e.printStackTrace();
            //throw new RuntimeException();
            TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
        }
    }

产生起因
间接 应用 this 办法调用的不是代理对象的办法,无奈加强,因而会造成事务生效。

解决方案:
1.通过注入本人来取得代理对象,进行调用。
2. 通过AppContext 从容器中获取代理对象,进行调用。

7. 多线程下原子性生效

    @Transactional(rollbackFor = Exception.class)
    public void transfer(int fromAccount, int toAccount, int money) {
        // 模仿一个转账操作
        try {
            // 此时查出来的余额只是过后的余额,可能操作的时候被人批改过。int fromBalance = userAccountMapper.findBalanceByUserAccount(fromAccount);
            if (fromBalance - money >= 0) {
                // 减钱
                userAccountMapper.update(fromAccount, -1 * money);
                // 增钱
                userAccountMapper.update(toAccount, money);
            }

        } catch (Exception e) {e.printStackTrace();
            //throw new RuntimeException();
            TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
        }
    }

产生起因
咱们发现 多个线程调用这个办法操作同一个账户,会发现原子性生效 ,因为 查问进去的余额可能被其它线程改掉,导致转账失败。

解决方案:
依据需要加上分布式锁或者 synchronized。

8.Transaction 导致加锁失败

产生起因
在后面咱们加了锁,可能是单机锁,也可能是分布式锁,然而有一点要留神的是,这 个办法执行结束之后,事务才会去提交,然而锁曾经开释了。

咱们看一下这个类:

DataSourceTransactionManager

这个类是在 办法执行完结之后,才去提交事务 ,然而 提交事务之前,下一个办法曾经进入执行流程,加锁失败。

解决方案:
1. 扩充锁的范畴
2. 在办法内提交事务

9. 多线程调用

    @Transactional(rollbackFor = Exception.class)
    public void transfer(int fromAccount, int toAccount, int money) {
        // 模仿一个转账操作
        try {CompletableFuture.supplyAsync(()->{
                try {
                    // 此时查出来的余额只是过后的余额,可能操作的时候被人批改过。int fromBalance = userAccountMapper.findBalanceByUserAccount(fromAccount);
                    if (fromBalance - money >= 0) {
                        // 减钱
                        userAccountMapper.update(fromAccount, -1 * money);
                        // 增钱
                        userAccountMapper.update(toAccount, money);
                    }

                } catch (Exception e) {e.printStackTrace();
                }
                return null;
            }).get();}  catch (ExecutionException | InterruptedException e) {throw new RuntimeException();
        }




    }

产生起因:
spring 的事务是通过数据库连贯来实现 ,而 spring 将数据库连贯放在 threalLocal 外面。 同一个事务,只能应用同一个数据库连贯 ,多线程场景下, 拿到的数据库连贯不同,属于不同的事务。

解决方案:
在线程外面回滚。

10. 应用了不反对事务的存储引擎
产生起因:比方 mysql 的 InnoDB 反对事务,MyISAM 不反对。

解决方案:应用反对事务的存储引擎

11. 谬误的流传行为
产生起因:

@Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)

是指如果存在事务则将这个事务挂起,并应用新的数据库连贯。新的数据库连贯不应用事务。

解决方案:
不应用这种形式。

退出移动版