springboot事务失效的日常总结(@Transactional)
什么是事务
事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。
事务是恢复和并发控制的基本单位
事务应该具有4个属性:(这里指的是单机,不是分布式)
原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性(单讲一篇)
说人话就是:
数据库一次执行数据的单元(要么这个事务(执行的sql)都成功,要么都失败)
为什么使用事务
说白了就是为了让这批次同时成功,或同时失败(也就是有个后悔的时候)
什么时候使用事务
场景一:如果实际的业务中,需要将一条数据同时存放到两张表中, 并且要求两张表中的数据同步,那么此时就需要使用事务管理机制,保证数据同步。如果出现错误情况,比如表一插入数据成功,表二插入数据失败,那么就回滚,终止数据持久化操作。
场景二:金融行业的软件开发严格重视事务处理,比如我们常见的转账操作,一方的账户金额减少,对应的是另一方的账户金额增加,这个过程需要使用到事务机制,不然转账不能成功
场景三:要求同一批次的提交需要一起成功,或一起失败
这个写的比较直白,做个备份:
Java中为什么使用事务?什么时候使用事务?如何使用事务? - Java精进之路 - 博客园 (cnblogs.com)
spring的事务:
编程式事务:
指在代码中手动的管理事务的提交,回滚等操作。
优点:可以自己控制事务的范围,适合异步的情况(可以指定代码行)
缺点:代码侵入性比较高
声明式事务:
是面向AOP切面的。将具体的业务和事务处理进行解耦,代码侵入性比较低。使用的比较普遍。
优点:耦合度低,使用简单(一般使用@Transactional)
缺点:只能使用在类,接口和方法上。使用的颗粒度比较高。不适合使用存在有异步调用的情况(接口不推荐使用这个)
@Transactional事务的失效
1. @Transactional 应用在非 public 修饰的方法上
因为@Transactional 的工作原理是基于AOP来实现的,所以,必须作用在public的方法上才行
protected
、private
修饰的方法上使用@Transactional
注解,虽然事务无效,但不会有任何报错
2.@Transactional 注解属性 propagation 设置错误
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常
以上这三种配置,是会导致注解失效的
3.@Transactional 注解属性 rollbackFor 设置错误
rollbackFor 指定回滚的情况。spring默认是回滚RuntimeException或这error才回滚。当然自定义的RuntimeException异常类也是可以的。
如果希望spring能够回滚别类型的异常,那就需要使用rollbackFor去指定(当然如果是指定异常的子类,也同样会回滚)
@Transactional(rollbackFor=Exception.class)
4. 同一个类中方法调用,导致@Transactional失效
其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理
开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方
public void A() throws Exception {
/**
* 调用B方法
*/
this.B();
... ...
}
@Transactional()
public void B() throws Exception {
mapper.insert();
}
处理办法:
处理方式:
/ /自己注入自己
@Autowired
A a ;然后用对象访问
5.异常被你的 catch“吃了
这个就比较简单了,就是你自己捕捉到了异常,并且自己处理,并不会抛出到上层的方法调用。那就不会生效了
try { nowDate.setTime(1623832800000L); } catch (Exception e) { System.out.println(e.getMessage()); }
6.数据库不支持事务
如mysql的数据库引擎,innodb支持事务,而myisam就不支持。
7.新开启一个线程
spring实现事务的原理是通过ThreadLocal把数据库连接绑定到当前线程中,新开启一个线程获取到的连接就不是同一个了。
@Transactional
public void deleteUser() throws MyException{
userMapper.deleteUserA();
try {
//休眠1秒,保证deleteUserA先执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
int i = 1/0;
userMapper.deleteUserB();
}).start();
}
这里备注一个写的比较好的博客: