@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之后,B的事务是不会起作用的。(经常在这里出错)
如下:

  public class Test{

  public void A(){ 
     B(); 
 }
  
   @Transactional
   public void B(){  
     
       }
}

4.使用了@Transactional的方法,只能是public,@Transactional注解的方法都是被外部其他类调用才有效,故只能是public。道理和上面的有关联。故在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错,但事务无效。

5.经过在ICORE-CLAIM中测试,效果如下:
A.抛出受检查异常XXXException,事务会回滚。
B.抛出运行时异常NullPointerException,事务会回滚。
C.Quartz中,execute直接调用加了@Transactional方法,可以回滚;间接调用,不会回滚。(即上文3点提到的)
D.异步任务中,execute直接调用加了@Transactional方法,可以回滚;间接调用,不会回滚。(即上文3点提到的)
E.在action中加上@Transactional,不会回滚。切记不要在action中加上事务。
F.在service中加上@Transactional,如果是action直接调该方法,会回滚,如果是间接调,不会回滚。(即上文3提到的)
G.在service中的private加上@Transactional,事务不会回滚。

注意点:

  1. Spring默认情况下会对(RuntimeException)及其子类来进行回滚,在遇见Exception及其子类的时候则不会进行回滚操作。
  2. @Transactional既可以作用于接口,接口方法上以及类上,类的方法上。但是Spring官方不建议接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外,@Transactional注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在protected、private或者默认可见性的方法上使用@Transactional 注解,这将被忽略,也不会抛出任何异常。
  3. Spring默认使用的是jdk自带的基于接口的代理,而没有使用基于类的CGLIB代理。
  4. 如果在接口、实现类或方法上都指定了@Transactional 注解,则优先级顺序为方法>实现类>接口;
  5. 建议只在实现类或实现类的方法上使用@Transactional,而不要在接口上使用,这是因为如果使用JDK代理机制(基于接口的代理)是没问题;而使用CGLIB代理(继承)机制时就会遇到问题,因为其使用基于类的代理而不是接口,这是因为接口上的@Transactional注解是“不能继承的”;
    @Override
    public  void test(User user){
            test1(user);
    }
    
    @Transactional
    public  void test1(User user){
           test2(user);
           test3(user);
           test4(user);
    }
    
    public  void test2(User user){
        int i = userMapper.addUser(user);
    }
    
    public void test3(User user){
        user.setUsername("333");
        int i = userMapper.updateUser(user);
        throw new RuntimeException("制造一个异常");//制造一个异常
        //抛异常按理应该会滚,addUser(user)执行了,test3的updateUser(user)没执行,
        //但运行后test2,test3两个方法都执行了,什么鬼?
       //要用代理对象调用,事务才会生效
    }
    
    public void test4(User user){
        user.setUsername("444");
        int i = userMapper.updateUser(user);
    }

以上代码运行结果:方法test3()抛出异常,事务不会进行回滚,方法test2和test3都执行了,test4不执行,即使在test2,test3,test4都加事务注解@Transactional,也不会回滚

因为spring是基于接口代理,即JDk动态代理去处理事务的,这就说明是基于方法拦截的(记住这点),那么spring进入第一个方法test()时,它没有开启事务,就对里面嵌套的其他方法也忽略了,这也是正常的处理逻辑。试想一下,如果spring不是这样处理就会出现逻辑混乱。比如此时test()方法里面执行了一些DB操作,然后再调用其他方法test1(),如果发生异常,此时test()里面的DB操作部分没有回滚,test1()里面却回滚了?那岂不是出现了逻辑混乱?

    @Transactional
    @Override
    public  void test(User user){
            test1(user);
    }

    public  void test1(User user){
           test2(user);
           test3(user);
           test4(user);
    }

    public  void test2(User user){
        int i = userMapper.addUser(user);
    }

    public void test3(User user){
        user.setUsername("333");
        int i = userMapper.updateUser(user);
        throw new RuntimeException("制造一个异常");//制造一个异常
    }

    public void test4(User user){
        user.setUsername("444");
        int i = userMapper.updateUser(user);
    }

以上代码运行结果:方法test3()抛出异常,事务进行回滚,方法test2,test3,test4都不执行,

总结:在方法里没有try catch抓异常时,被调用的方法入口处如果没有加事务注解,那么方法内调用其他的方法(不管其他方法前面有没有加事务注解),其他的方法的事务都不会生效;被调用的方法入口处如果加了事务注解,那么方法内调用其他的方法(不管其他方法前面有没有加事务注解),其他的方法的事务都会生效.

在方法里有try catch抓异常时,(如下代码)都加了事务注解,当抛出异常后,两个方法都不会回滚,并且数据库记录都发生改变(事务失效)事务失效解决:
获取当前对象的动态代理对象即可.
事务失效更多办法参考:JDK动态代理给Spring事务埋下的坑

    @Transactional
    @Override
    public  void test(User user){
        int i = userMapper.addUser(user);
        //把test2(user)放在try里面目的:抓住异常,不管test2(user)有没有异常,都可以正常执行addUser(user)方法
        try {
          UserService proxy=(UserService) AopContext.currentProxy();//获取代理对象
          proxy.test2(user);//用代理对象调用方法开启事务
        //test2(user);//单独调用时
        } catch (Exception e) {
            logger.error("修改出现了异常",e.getMessage());
        }
    }

    @Transactional
    @Override
    public void test2(User user){
        user.setUsername("888");
        int i = userMapper.updateUser(user);
        int x=1/0;//制造一个异常
        // throw new RuntimeException("制造一个异常");//制造一个异常
    }
  1. 让Exception异常也进行回滚操作,在调用该方法前加上: @Transactional(rollbackFor = Exception.class)
  2. 让RuntimeException不进行回滚操作,在调用该方法前加上: @Transactional(noRollbackFor=RunTimeException.class)
  1. 在整个方法运行前就不会开启事务: @Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true),这样就做成一个只读事务,可以提高效率

在这里插入图片描述

查看方法的事务是否已经被执行
TransactionSynchronizationManager.isActualTransactionActive() 返回true:
该方法中存在事务,false则不存在

事务的传播及其属性的意义:

//事务传播属性
@Transactional(propagation=Propagation.REQUIRED)//如果有事务,那么加入事务,没有的话新创建一个
@Transactional(propagation=Propagation.NOT_SUPPORTED)//这个方法不开启事务
@Transactional(propagation=Propagation.REQUIREDS_NEW)//不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Transactional(propagation=Propagation.MANDATORY)//必须在一个已有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation.NEVER)//不能在一个事务中执行,就是当前必须没有事务,否则抛出异常
@Transactional(propagation=Propagation.SUPPORTS)//其他bean调用这个方法,如果在其他bean中声明了事务,就是用事务。没有声明,就不用事务。
@Transactional(propagation=Propagation.NESTED)//如果一个活动的事务存在,则运行在一个嵌套的事务中,如果没有活动的事务,则按照REQUIRED属性执行,它使用一个单独的事务。这个书屋拥有多个回滚的保存点,内部事务的回滚不会对外部事务造成影响,它只对DataSource TransactionManager事务管理器起效。
@Transactional(propagation=Propagation.REQUIRED,readOnly=true)//只读,不能更新,删除
@Transactional(propagation=Propagation.REQUIRED,timeout=30)//超时30秒
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT)//数据库隔离级别

猜你喜欢

转载自blog.csdn.net/qq_42848910/article/details/105538686