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扫描下方二维码关注公众号和小程序↓↓↓