Spring 事务失效的几种场景

Spring 事务 大约 4933 字

检查异常

代码

未指定rollbackFor

@Transactional
public void transfer(int from, int to, int amount) throws FileNotFoundException {
    int fromBalance = accountDao.findBalanceBy(from);
    if (fromBalance - amount > 0) {
        accountDao.update(from, - 1 * amount);
        new FileInputStream("aaa");
        accountDao.update(to, amount);
    }
}

原因

Spring默认只会回滚非检查异常。RuntimeExceptionError的子类。

解决

@Transactional(rollbackFor = Exception.class)

try catch

代码

方法内捕获了异常

@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) {
    try {
        int fromBalance = accountDao.findBalanceBy(from);
        if (fromBalance - amount > 0) {
            accountDao.update(from, - 1 * amount);
            new FileInputStream("aaa");
            accountDao.update(to, amount);
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
}

原因

Spring事务是基于AOP的环绕通知,如果在目标方法上捕获了异常,AOP认为是正确执行了,不触发AOPcatch,所以无法执行rollback

解决

方法一:catch中抛出异常。

@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) {
    try {
        int fromBalance = accountDao.findBalanceBy(from);
        if (fromBalance - amount > 0) {
            accountDao.update(from, - 1 * amount);
            new FileInputStream("aaa");
            accountDao.update(to, amount);
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}

方法二:catch中调用事务拦截器的setRollbackOnly()

@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) {
    try {
        int fromBalance = accountDao.findBalanceBy(from);
        if (fromBalance - amount > 0) {
            accountDao.update(from, - 1 * amount);
            new FileInputStream("aaa");
            accountDao.update(to, amount);
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
        TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
    }
}

增加 AOP

代码

给事务方法增加了切面类

@Aspect
class MyAspect {
    @Around("execution(* transfer(..))")
    public Object around(ProceedingJointPoint pjp) throws Throwable {
        try {
            return pjp.proceed();
        } catch (Throwable t) {
            e.printStackTrace();
            return null;
        }
    }
}

@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) throws FileNotFoundException {
    int fromBalance = accountDao.findBalanceBy(from);
    if (fromBalance - amount > 0) {
        accountDao.update(from, - 1 * amount);
        new FileInputStream("aaa");
        accountDao.update(to, amount);
    }
}

原因

第一层是事务切面,第二层是自定义切面,目标方法抛出异常,在自定义切面中被捕获,第一层切面无法感知,认为是正常完成。

解决

方法一(推荐):可以在自定义切面catch中抛出异常或调用setRollbackOnly方法。(抛出异常才能不会吞掉异常。)

方法二:调整切面的执行顺序。自定义切面和事务切面的优先级默认都是最低的。调整自定义切面的优先级比事务优先级高1即可。

@Aspect
@Order(Ordered.LOWEST_PRECEDENCE - 1)
class MyAspect {
}

方法修饰符

代码

不加public

@Transactional
void transfer(int from, int to, int amount) {
    int fromBalance = accountDao.findBalanceBy(from);
    if (fromBalance - amount > 0) {
        accountDao.update(from, - 1 * amount);
        accountDao.update(to, amount);
    }
}

原因

AnnotationTransactionAttributeSource中默认publicMethodOnlytrue,所以只会调用public的方法。

解决

方法一:添加public修饰符。

方法二:配置AnnotationTransactionAttributeSource属性为false

// 设置 publicMethodOnly 为 false
@Bean
public TransactionAttributeSource transactionAttributeSource() {
    return new AnnotationTransactionAttributeSource(false);
}

代码

@Service
public class TxService {

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void foo() {
        System.out.println("foo");
        bar();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void bar() {
        System.out.println("bar");
    }

}

原因

foo方法中调用bar方法没有经过AOP,其实是this.bar(),是目标类的目标方法。

解决

@Autowired注入的代理对象。

@Service
public class TxService {

    @Autowired
    TxService txService;

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void foo() {
        System.out.println("foo");
        txService.bar();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void bar() {
        System.out.println("bar");
    }

}

并发情况下

代码

@Service
public class TxService {

    @Transactional(rollbackFor = Exception.class)
    public void transfer(int from, int to, int amount) {
        int fromBalance = accountDao.findBalanceBy(from);
        if (fromBalance - amount > 0) {
            accountDao.update(from, - 1 * amount);
            accountDao.update(to, amount);
        }
    }
}

原因

多线程下,事务交错执行。@Transactional并没有保证原子性行为。synchronized加在transfer上并不能解决问题。

解决

方法一:外层调用transfer中加锁。

Object lock = new Object();
synchronized (lock) {
    txService.transfer();
}

方法二(推荐):查询语句中改为select for update。数据库层面保证锁。

阅读 72 · 发布于 2021-12-26

————        END        ————

扫描下方二维码关注公众号和小程序↓↓↓

扫描二维码关注我
昵称:
随便看看 换一批