Spring 事务的对于数据库的操作,要么执行,要不都不执行,在事务中都执行成功就会提交失败就会发生回滚。
原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。
事务隔离(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
未提交读(Read uncommitted),最低的隔离级别,允许“脏读”(dirty reads),事务可以看到其他事务“尚未提交”的修改。如果另一个事务回滚,那么当前事务读到的数据就是脏数据。
提交读(read committed),一个事务可能会遇到不可重复读(Non Repeatable Read)的问题。不可重复读是指,在一个事务内,多次读同一数据,在这个事务还没有结束时,如果另一个事务恰好修改了这个数据,那么,在第一个事务中,两次读取的数据就可能不一致。
可重复读(repeatable read),一个事务可能会遇到幻读(Phantom Read)的问题。幻读是指,在一个事务中,第一次查询某条记录,发现没有,但是,当试图更新这条不存在的记录时,竟然能成功,并且,再次读取同一条记录,它就神奇地出现了。
串行化(Serializable),最严格的隔离级别,所有事务按照次序依次执行,因此,脏读、不可重复读、幻读都不会出现。虽然 Serializable 隔离级别下的事务具有最高的安全性,但是,由于事务是串行执行,所以效率会大大下降,应用程序的性能会急剧降低。如果没有特别重要的情景,一般都不会使用 Serializable 隔离级别。
PROPAGATION_REQUIRED
Spring默认的传播机制,能满足绝大部分业务需求,如果外层有事务,如果外部方法开启事务并且是 Propagation.REQUIRED 的话,所有 Propagation.REQUIRED 修饰的内部方法和外部方法均属于同一事务一块提交,一块回滚。如果外层没有事务,新建一个事务执行
PROPAGATION_REQUES_NEW
该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可
PROPAGATION_SUPPORT
如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务
PROPAGATION_NOT_SUPPORT
该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码
PROPAGATION_NEVER
该传播机制不支持外层事务,即如果外层有事务就抛出异常
PROPAGATION_MANDATORY
与NEVER相反,如果外层没有事务,则抛出异常
PROPAGATION_NESTED
该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。
如果一个事务只对数据库执行读操作,那么该数据库就可能利用那个事务的只读特性,采取某些优化措施。通过把一个事务声明为只读。
指一个事务所允许执行的最长时间,如果在超时时间内还没有完成的话,就自动回滚。
在默认设置下,事务只在出现运行时异常(runtime exception)时回滚,而在出现受检查异常(checked exception)时不回滚,不过,可以声明在出现特定受检查异常时像运行时异常一样回滚。同样,也可以声明一个事务在出现特定的异常时不回滚,即使特定的异常是运行时异常。
在Springboot中声明式事务一般使用@Transactional注解,至于@EnableTransactionManagement注解不使用也可以。声明式事务是建立在AOP上的,我们的程序会通过注解的方式提供元数据,AOP与事务元数据结合产生一个代理,当执行方法的时候,拦截器TransactionInterceptor会对目标方法进行拦截,然后在执行目标方法的前后调用代理。其本质是在目标方法之前创建或者加入一个事务。@Transactional注解可以用在接口定义,接口方法和类的公共方法上。
代码如下
@Transactional
@GetMapping("/test")
public void test1(){
}
Spring提供了三种支持编程式事务的方式,使用TransactionTemplate或者TransactionalOperator或
TransactionManager,这里主要介绍TransactionTemplate,使用回调方法让应用程序代码从获取和释放事务性资源的样板操作中释放出来,也就意味着不用手动开启事务或者关闭事务,应用程序只关注业务。代码如下
@Autowired
TransactionTemplate transactionTemplate;
@GetMapping("/test")
public void test1(){
transactionTemplate.execute(new TransactionCallback
1. 是否使用了 try catch 进行了异常捕获,并且捕获之后,没有通过 throw new RuntimeException();进行异常抛出。因为对于 spring aop异常捕获原理,被拦截的方法需要显示的抛出异常,并不能进行任何处理,这样 aop 代理才能捕获到方法的异常,才能进行事务的回滚操作;默认清空下,aop只捕获 RuntimeException 的异常,但是可以通过配置来捕获特定的异常并回滚。对于这种情况不是RuntimeException异常,可以在抛出异常的时候,然后添加下面这行代码。
@Transactional(rollbackFor=Exception.class)
也可以在 catch语句中加:TransactionAspectsupport.currentTransactionstatus().setRollbackonly();
2.排查是不是产生了自调用的问题。在 spring 的 aop 代理下,只有目标方法被外部调用,目标方法才由 spring 生成的代理对象来管理;若统一类中的其他没有用@Transactional注解进行修饰的方法内部调用了用@Transactional注解进行修饰的方法,有@Transactional注解的方法的事务将会被忽略,不发生回滚。代码如下这种情况就不会就不会发生回滚,代码参考如下
@GetMapping("/test")
public void test1(){
test2();
}
@Transactional
public void test2(){
tbShopMapper.deleteById(1);
int m=19/0;
}
如果要生效可以把注解放在类上。
3 @Transactional注解只被应用到 public修饰的方法上;如果在 protected、private等修饰的方法上,@Transactional 注解不会报错,但是这个注解的将不会生效。
4.@Transactional注解不会对当前修饰的方法的子方法生效。比如:我们在方法A中声明了@Transactional注解,但是A方法的内部调用的方法B和方法c,其中方法B进行了数据库的操作,但是改部分的异常被方法B进行了处理并且没有进行抛出,这样的话事务是不会生效的。反之,如果方法B声明了@Transactional,但是方法A没有声明@Transactional,A方法内部调用B方法,事务也是不会生效的。如果想要事务生效,需要将子方法的事务控制交给调用的方法,在子方法中使用@Transactiona1注解并通过 ro1lbackFor指定定回滚的异常或者直接将异常抛出。
备注:在使用事务的时候,最好把子方法的异常进行抛出,交给调用的方法进行处理。