@Transaction

参考:@Transaction必知必会

1. Spring事务的基本原理

  事务管理是应用系统开发中必不可少的一部分,Spring为事务提供了丰富的功能支持。Spring事务分为:编码式和声明式两种方式。声明式事务基于AOP将具体业务逻辑和事务处理解耦,声明式事务管理不会污染业务代码,因此在实际应用中更普遍,声明式事务分为两种,一种是基于配置文件,另一种基于@Transaction。

  使用@Transaction有如下方便:可设置是否自动开启事务、自动提交事务、遇到异常自动回滚。

  原理如下:

  • 配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional标识。
  • spring 在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务,异常回滚事务)。
  • 真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。

2. @Transactional基本配置解析

  只需要在方法上加入@Transactional注解。当@Transactional加在方法上,表示对该方法应用事务。当加在类上,表示对该类里面所有的方法都应用相同配置的事务。下面是对注解的解析:

public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";
   Propagation propagation()
default Propagation.REQUIRED;//定义事务传播性 Isolation isolation() default Isolation.DEFAULT; //定义事务隔离性
  
int timeout() default -1;//事务超时设置.超过这个时间,发生回滚 boolean readOnly() default false;//只读事务
     从这一点设置的时间点开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!(查询中不会出现别人在时间点a之后提交的数据)。
      注意是一次执行多次查询来统计某些信息,这时为了保证数据整体的一致性,要用只读事务
    Class<? extends Throwable>[] rollbackFor() default {};//导致事务回滚的异常类数组.

    String[] rollbackForClassName() default {};//导致事务回滚的异常类名字数组

    Class<? extends Throwable>[] noRollbackFor() default {};/不会导致事务回滚的异常类数组

    String[] noRollbackForClassName() default {};/不会导致事务回滚的异常类名字数组
}

  事务隔离性:

  脏读:A事务进行增删改查未提交,B就可以读到,A回滚了,B读到的就是脏读

  不可重复读:A事务要读两次,B在两次读之间进行了操作,A两次读数不一样就是不可重复读

  幻读:A事务对一定范围的数据进行修改,B事务向范围内新增了一条数据,事务A无法读取并修改事务B新增的数据

  不可重复读和幻读的区别在于对数据的操作不同,不可重复读是指修改操作,在一个事务内,同一条件下,两次读到的数据不同,幻读是指新增或删除操作,在一个事务内,同一条件下,两次读到的条数不同。

  事务传播性:

  1.PROPAGATION_REQUIRED(spring 默认) 

  外层事务 Service A 的 Method A() 调用 内层Service B 的 Method B()。定义ServiceB.methodB() 的事务级别为 PROPAGATION_REQUIRED,那么执行 ServiceA.methodA() 的时候spring已经起了事务,这时调用 ServiceB.methodB(),ServiceB.methodB() 看到自己已经运行在 ServiceA.methodA() 的事务内部,就不再起新的事务。假如 ServiceB.methodB() 运行的时候发现自己没有在事务中,他就会为自己分配一个事务。不管如何,ServiceB.methodB()都会在事务中。

  2.PROPAGATION_REQUIRES_NEW

  定义 ServiceA.methodA() 的事务级别为 PROPAGATION_REQUIRED,ServiceB.methodB() 的事务级别为 PROPAGATION_REQUIRES_NEW。那么当执行到 ServiceB.methodB() 的时候,ServiceA.methodA() 所在的事务就会挂起,ServiceB.methodB() 会起一个新的事务,等待 ServiceB.methodB() 的事务完成以后,它才继续执行。它与1中的区别在于ServiceB.methodB() 新起了一个事务。如过ServiceA.methodA() 发生异常,ServiceB.methodB() 已经提交的事务是不会回滚的。

  3.PROPAGATION_SUPPORTS

  定义ServiceB.methodB() 的事务级别为 PROPAGATION_SUPPORTS,那么当执行到ServiceB.methodB()时,如果发现ServiceA.methodA()已经开启了一个事务,则加入当前的事务,如果发现ServiceA.methodA()没有开启事务,则自己也不开启事务。这种时候,内部方法的事务性完全依赖于最外层的事务。

  4.PROPAGATION_NOT_SUPPORTED

  该传播机制不支持事务,如果外层存在事务则挂起外层事务 ,然后执行当前逻辑,执行完毕后,恢复外层事务。

  5.PROPAGATION_NEVER

  该传播机制不支持事务,如果外层存在事务则直接抛出异常。

  6.PROPAGATION_MANDATORY

  该传播机制是说配置了该传播性的方法只能在已经存在事务的方法中被调用,如果在不存在事务的方法中被调用,会抛出异常。

  7.PROPAGATION_NESTED

  该传播机制特点是可以保存状态保存点,当事务回滚后会回滚到某一个保存点上,从而避免所有嵌套事务都回滚。

3. @Transactional 使用应该注意的地方

  1. 默认情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常)或者 Error,则 Spring 将回滚事务;除此之外,Spring 不会回滚事务。你如果想要在特定的异常回滚可以考虑rollbackFor()等属性

  2. @Transactional 只能应用到 public 方法才有效。

  这是因为在使用 Spring AOP 代理时,Spring 会调用 TransactionInterceptor在目标方法执行前后进行拦截之前,DynamicAdvisedInterceptorCglibAopProxy的内部类)的的 intercept方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource(Spring 通过这个类获取@Transactional 注解的事务属性配置属性信息)的 computeTransactionAttribute 方法。

@Nullable
    protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
       //这里判断是否是public方法
        if(this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
            return null;
        } 
//省略其他代码

  若不是 public,就不会获取@Transactional 的属性配置信息,最终会造成不会用 TransactionInterceptor 来拦截该目标方法进行事务管理。

  3.Spring 的 AOP 的自调用问题

  在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。若同一类中的其他没有@Transactional注解的方法内部调用有@Transactional注解的方法,有@Transactional注解的方法的事务被忽略,不会发生回滚。这个问题是由于Spring AOP 代理造成的(如下面代码所示)。之所以没有应用事务,是因为在内部调用,而代理后的类(把目标类作为成员变量静态代理)只是调用成员变量中的对应方法,自然也就没有aop中的advice,造成只能调用父类的方法。另外一个问题是只能应用在public方法上。为解决这两个问题,使用 AspectJ 取代 Spring AOP 代理。

@Transactional
 public void saveUser(){
        User user = new User();
        user.setAge(22);
        user.setName("mask");
        logger.info("save the user{}",user);
        userRepository.save(user);
       // throw new RuntimeException("exception");
    }
 public void saveUserBack(){
        saveUser();   //自调用发生
    }

  4.自注入解决办法

@Service
public class UserService {

    Logger logger = LoggerFactory.getLogger(UserService.class);

    @Autowired
    UserRepository userRepository;
    
    @Autowired 
    UserService userService; //自注入来解决

    @Transactional
    public void saveUser(){

        User user = new User();
        user.setAge(22);
        user.setName("mask");
        logger.info("save the user{}",user);
        userRepository.save(user);
       // throw new RuntimeException("exception");
    }
 public void saveUserBack(){
        saveUser();
    }
}

另外也可以把注解加到类上来解决。

4. 总结

@Transactional用起来是方便,但是我们需要明白它背后的原理,避免入坑。另外@Transactional不建议用在处理时间过长的事务。因为,它会一直持有数据库线程池的连接,造成不能及时返回。就是尽量是的事务的处理时间短。

posted @ 2019-12-20 15:05  皮肤黝黑的小白  阅读(182)  评论(0)    收藏  举报