事务失效
1. final/static修饰方法
Spring事务使用AOP,通过CGLIB或JDK动态代理生成代理类,使用final/static修饰方法会导致无法动态代理,继而导致事务失效。
2. 使用非public修饰符修饰
事务方法定义了错误的访问权限(非public修饰)就会使事务失效。
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// 省略其他代码
......
}
上面为AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法代码片段,非public修饰符会直接return null,说明不支持事务。
3. 类忘了加Spring提供的注解导致没有被Spring所管理
例如忘记加@Controller、@Service、@Component、@Repository等注解
4. 使用新的线程执行代码逻辑
public class Test0 {
@Autowired
private TestService service;
@Transactional
public void add(params...) throws Exception {
// do something
new Thread(() -> {
service.save();
}).start();
}
}
@Service
public class TestService {
@Transactional
public void save() {
// do something...
}
}
事务方法add()调用了另一个事务方法save(),但save()是在另一个线程中调用的,由于获取到的数据库连接不同,是两个不同的事务,因此会失效。
5. 同一个类方法内部调用同类的另一个方法
同一个类方法内部调用同类的另一个方法会使事务失效。
如何解决?
- 可以用@Autowire注入自己使事务生效
@Service public class Service1 { @Autowired prvate Service1 service; public void save(User user) { service.add(user); } @Transactional public void add(User user) { ...... } }
- 新加一个Service类
- 使用AopContent类
注:需要在启动类上加注解
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
来暴露AOP的Proxy对象,否则会报错
Cannot find current proxy: Set ‘exposeProxy’ property on Advised to ‘true’ to make it available.
参考的文章链接
@Service
public class Service1 {
public void save(User user) {
......
((Service1)AopContext.currentProxy()).add(user);
}
@Transactional
public void add(User user) {
......
}
}
事务不回滚
spring七种事务传播行为
事物特性 | 说明 | 具体 |
---|---|---|
REQUIRED | 若当前上下文中存在事务,那么加入该事务,如果不存在事务,创建一个事务,这是默认的传播属性值 | spring默认的事务传播行为,A方法调用B方法,如果A方法有事务,则B方法加入到A方法中的事务中,否则B方法自己开启一个新事务 |
SUPPORTS | 若当前上下文存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行 | A方法调用B方法,如果A方法有事务,则B方法加入到A方法中的事务中,否则B方法自己使用非事务方式执行 |
MANDATORY | 若当前上下文中存在事务,否则抛出异常 | 只能在存在事务的方法中被调用,A方法调用B方法,如果A方法没事务,则B方法会抛出异常 |
REQUIRES_NEW | 每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行 | A方法调用B方法,如果A方法有事务,则B方法把A方法的事务挂起,B方法自己重新开启一个新事务 |
NOT_SUPPORTED | 若当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行 | A方法调用B方法,如果A方法有事务,则B方法挂起A方法中的事务中,否则B方法自己使用非事务方式执行 |
NEVER | 若当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码 | 不支持事务,A方法调用B方法,如果A方法有事务,则B方法会抛出异常 |
NESTED | 若当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务 | 同 Propagation.REQUIRED,不过此传播属性还可以:保存状态节点,从而避免所有嵌套事务都回滚 |
@Transactional(propagation = Propagation.SUPPORTS)
目前只有这三种传播特性才会创建新事务:REQUIRED,REQUIRES_NEW,NESTED。
参考文章链接
1. 自己手动try catch导致事务不回滚
@Service
public class Service1 {
@Transactional
public void save() {
try {
add();
} catch (Exception e) {
// 打印log
}
}
}
2. 手动抛非RuntimeException异常
throw new Exception(e)
Spring事务对于普通的Exception不会回滚,一般只有Error和RuntimeException会回滚。
3. 自定义回滚异常
@Transactional(rollbackFor = ServiceException.class)
public void add() throws Exception {
save();
}
在执行save()方法时抛出了SqlException、DuplicateKeyException等导致事务不回滚。
最好还是将参数设置为Exception或Throwable
4. 嵌套回滚了不想回滚的内容
这种内部嵌套事务预期回滚delete(),但是当delete()失败时,insert()也会回滚
public class Service1 {
@Autowired
private XxxDao xxxdao;
@Autowired
private XxxService xxxService;
@Transactional
public void add() throws Exception {
xxxDao.insert();
xxxService.delete();
}
}
@Service
public class XxxService {
@Transactional(propagation = Propagation.NESTED)
public void delete() {
......
}
}
可以手动在add中使用try…catch包裹xxxService.delete();
@Transactional
public void add(UserModel userModel) throws Exception {
xxxDao.insert();
try {
xxxService.delete();
} catch (Exception e) {
// 打印log日志
}
}
总结:上面是一些常见的事务问题,除此之外有些事务方法select比较多,(实际需要事务的可能只有一两行insert,update),调用层级很深且查询耗时高,会导致整个事务相当耗时导致大事务问题。