Spring事务注解的使用

1、在 bs> SpringBoot作用 :提供了非常方便的事务操作,通过注解就可以实现事务的回滚,非常方便快捷

实现事务的两种实现方法

编程式事务管理:

编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。(我基本上没用过)

声明式事务管理:

推荐使用

建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务
声明式事务管理不需要入侵代码,通过@Transactional就可以进行事务操作,更快捷而且简单。

基于AOP的生命事务管理使用

  • @Transactional可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性 。
  • Spring的AOP即声明式事务管理默认是针对unchecked exception回滚。也就是默认对RuntimeException()异常或是其子类进行事务回滚;checked异常,即Exception可try{}捕获的不会回滚,因此对于我们自定义异常,通过rollbackFor进行设定。
@Transactional(rollbackFor=Exception.class) //指定回滚,遇到异常Exception时回滚
public void methodName() {
    
    
   throw new Exception("注释");
}
@Transactional(noRollbackFor=Exception.class)//指定不回滚,遇到运行期例外(throw new RuntimeException("注释");)会回滚
public ItimDaoImpl getItemDaoImpl() {
    
    
   throw new RuntimeException("注释");
}

Spring的事务管理默认是针对unchecked exception回滚,也就是默认对Error异常和RuntimeException异常以及其子类进行事务回滚,且必须抛出异常。若使用try-catch对其异常捕获则不会进行回滚!(Error异常和RuntimeException异常抛出时不需要方法调用throws或try-catch语句)。而checked exception** 则必须用try语句块进行处理或者把异常交给上级方法处理总之就是必须写代码处理它,所以必须在service捕获异常,然后再次抛出,这样事务方才起效。

https://img-blog.csdnimg.cn/dd2a2fcfd72e48ac93b7aa67934aab19.png

  • 如果我们需要捕获异常后,同时进行回滚,通过TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();进行手动回滚操作。
  • 设置回滚点:使用Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();设置回滚点,使用TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);回滚到savePoint。

SpringBoot项目会自动配置一个DataSourceTransactionManager,所以我们只需在方法(或者类)加上@Transactional注解,就自动纳入Spring的事务管理了。如下在方法加上 @Transactional 注解:

@RestController
@Transactional
public class TestController {
    
    
@Autowired
    UserService userService ;
public  String insertOrder(@RequestBody User user){
    
    
    userService.save(user);
    throw new RuntimeException("发生异常")return "ok";
}
}

抛出异常之后,事务会自动回滚,数据不会插入到数据库。

事务注解的参数

在这里插入图片描述

值得注意的注解参数

  • Propagation.REQUIRED 如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。如a方法和b方法都添加了注解,使用默认传播模式,则a方法内部调用b方法,会把两个方法的事务合并为一个事务。这里又会存在问题,如果b方法内部抛了异常,而a方法catch了b方法的异常,那这个事务还能正常运行吗?答案是不行!会抛出异常:
    org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only,因为当ServiceB中抛出了一个异常以后,ServiceB会把当前的transaction标记为需要rollback。但是ServiceA中捕获了这个异常,并进行了处理,认为当前transaction应该正常commit(默认注解使用try-cathch表示不进行处理)。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException。Propagation.SUPPORTS
    如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。

  • @Transactional(propagation=Propagation.REQUIRED)
    如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)

  • @Transactional(propagation=Propagation.NOT_SUPPORTED)
    容器不为这个方法开启事务

  • @Transactional(propagation=Propagation.REQUIRES_NEW)
    不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务

  • @Transactional(propagation=Propagation.MANDATORY)
    必须在一个已有的事务中执行,否则抛出异常

  • @Transactional(propagation=Propagation.NEVER)
    必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)

  • @Transactional(propagation=Propagation.SUPPORTS)
    如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.

  • 事物超时设置:
    @Transactional(timeout=30) //默认是30秒

事务隔离级别:

@Transactional(isolation = Isolation.READ_UNCOMMITTED)
读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED)
读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ)
可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE)
串行化

Propagation.NOT_SUPPORTED 以非事务的方式运行,如果当前存在事务,暂停当前的事务。

Propagation.NEVER 以非事务的方式运行,如果当前存在事务,则抛出异常。

Propagation.NESTED 和 Propagation.REQUIRED 效果一样。

isolation属性 事务的隔离级别,默认值为 Isolation.DEFAULT。

可选的值有: Isolation.DEFAULT 使用底层数据库默认的隔离级别。 Isolation.READ_UNCOMMITTED ,Isolation.READ_COMMITTED ,Isolation.REPEATABLE_READ,Isolation.SERIALIZABLE

timeout属性:事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

readOnly属性:指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

rollbackFor属性:用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

noRollbackFor属性:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

6、@Transactional事务几点注意,这里面有几点需要大家留意:
A. 一个功能是否要事务,必须纳入设计、编码考虑。不能仅仅完成了基本功能就ok。
B. 如果加了事务,必须做好开发环境测试(测试环境也尽量触发异常、测试回滚),确保事务生效。
C. 以下列了事务使用过程的注意事项,请大家留意。
1.不要在接口上声明@Transactional,而要在具体类的方法上使用@Transactional注解,否则注解可能无效。
2.不要图省事,将@Transactional放置在类级的声明中,放在类声明,会使得所有方法都有事务。故@Transactional应该放在方法级别,不需要使用事务的方法,就不要放置事务,比如查询方法。否则对性能是有影响的。
3.使用了@Transactional的方法,对同一个类里面的方法调用,@Transactional无效。比如有一个类Test,它的一个方法A,A再调用Test本类的方法B(不管B是否public还是private),但A没有声明注解事务,而B有。则外部调用A之后,B的事务是不会起作用的。(经常在这里出错)
4.使用了@Transactional的方法,只能是public,@Transactional注解的方法都是被外部其他类调用才有效,故只能是public。道理和上面的有关联。故在protected、private或者package-visible的方法上使用 @Transactional 注解,它也不会报错,但事务无效。
5.spring的事务在抛异常的时候会回滚,如果是catch捕获了,事务无效。可以在catch里面加throw new RuntimeException();
6.最后有个关键的一点:和锁同时使用需要注意:由于Spring事务是通过AOP实现的,所以在方法执行之前会有开启事务,之后会有提交事务逻辑。而synchronized代码块执行是在事务之内执行的,可以推断在synchronized代码块执行完时,事务还未提交,其他线程进入synchronized代码块后,读取的数据不是最新的。
所以必须使synchronized锁的范围大于事务控制的范围,把synchronized加到Controller层或者大于事务边界的调用层!

猜你喜欢

转载自blog.csdn.net/qq_36737214/article/details/118108083