第一种 抛出受检异常
先来看看异常继承树
这里把异常分为三类,Error,RuntimeException、CheckedException,其中CheckedException是受检异常,受检异常是由编译器强制执行的,必须捕获,也就是大概率会出异常,需要你在写代码的时候就处理一下,不然会爆红,例如IOException。Error,RuntimeException是非受检异常,代码运行的时候才可能出错,概率小一点。
下面代码显示了@Transactional对哪些异常会进行补抓回滚。很明显,受检异常是无法被处理的
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
比如你的代码如下,抛出了受检异常,此时程序是无法回滚的,@Transactional失效。
@Transactional
public void transactionTest() throws IOException{
User user = new User();
UserService.insert(user);
throw new IOException();
}
解决方案: 配置rollbackFor属性,例如@Transactional(rollbackFor = Exception.class),这样的话,@Transactional会自己补抓受检异常和非受检异常,自然也能回滚了。
第二种 自己补抓了异常
事务底层是通过补抓异常来决定回滚的,如果你的代码自己捕获处理了异常,那么事务就补抓不到,自然会失效。
@Transactional(rollbackFor = Exception.class)
public void transactionTest() {
try {
User user = new User();
UserService.insert(user);
int i = 1 / 0;
}catch (Exception e) {
e.printStackTrace();
}
}
解决办法: 不要自己处理,或者在catch里抛出非受检异常
第三种 类中方法互相调用
事务是基于aop实现的,aop又是经过动态代理实现方法增强,也就是说事务的功能是通过代理增强的。当我们调用某个有@Transaction方法的时候,实际上是代理对象在调用该方法,对该方法实现了事务增强功能,如果如下面代码一样,方法调用方法,那么就是由DefaultTransactionService类的对象调用的方法,根本没走代理对象。事务自然失效。
@Service
public class DefaultTransactionService implement Service {
public void saveUser() throws Exception {
//do something
doInsert();
}
@Transactional(rollbackFor = Exception.class)
public void doInsert() throws IOException {
User user = new User();
UserService.insert(user);
throw new IOException();
}
}
解决办法:
方案一:直接在启动类中添加@Transactional注解saveUser()
方案二:@EnableAspectJAutoProxy(exposeProxy = true)在启动类中添加,会由Cglib代理实现。
第四种 方法不是public
如果方法不是public,Spring事务也会失败,因为Spring的事务管理源码AbstractFallbackTransactionAttributeSource中有判断computeTransactionAttribute()。如果目标方法不是公共的,则TransactionAttribute返回null。本质上还是因为非public不生成代理对象,不增强就无法添加事务功能。
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
解决方案:将当前方法访问级别更改为public。
第五种 没被spring管理
这种就好说了。不加@Controller、@Service、@Component、@Repository等注解,就无法被spring管理,自然不会生成事务。
第六种 方法被final修饰
spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。
但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能。
@Service
public class UserService {
@Transactional
public final void add(UserModel userModel){
saveData(userModel);
updateData(userModel);
}
}
第七种 多线程调用
在实际项目开发中,多线程的使用场景还是挺多的。如果spring事务用在多线程场景中,会有问题吗?
@Slf4j
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
new Thread(() -> {
roleService.doOtherThing();
}).start();
}
}
@Service
public class RoleService {
@Transactional
public void doOtherThing() {
System.out.println("保存role表数据");
}
}
从上面的例子中,我们可以看到事务方法add中,调用了事务方法doOtherThing,但是事务方法doOtherThing是在另外一个线程中调用的。
这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想doOtherThing方法中抛了异常,add方法也回滚是不可能的。
如果看过spring事务源码的朋友,可能会知道spring的事务是通过数据库连接来实现的。当前线程中保存了一个map,key是数据源,value是数据库连接。
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
我们说的同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。