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@Slf4jpublic 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;// ...@Servicepublic 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;// ...@Controllerpublic 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)
是指如果存在事务则将这个事务挂起,并应用新的数据库连贯。新的数据库连贯不应用事务。
解决方案:
不应用这种形式。