Spring 事务失效的几种场景
Spring 事务 面试 About 4,933 words检查异常
代码
未指定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默认只会回滚非检查异常。RuntimeException和Error的子类。
解决
@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认为是正确执行了,不触发AOP的catch,所以无法执行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中默认publicMethodOnly为true,所以只会调用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。数据库层面保证锁。
————        END        ————
Give me a Star, Thanks:)
https://github.com/fendoudebb/LiteNote扫描下方二维码关注公众号和小程序↓↓↓