@Transactional注解避坑

539 阅读8分钟

1.声明式事务

@Transactional是 org.springframework.transaction.annotation 包下的一个注解,通常写代码需要保持事务一致性的时候使用。咱们可能以为在方法上加上 @Transactional注解就完事儿了,但产生的结果可能不符合使用预期。事实上还有三个点需要注意:回滚,事务的传播以及加了@Transactional不生效的情况

1.回滚

//异常
Exception:
    IOException
    SQLException
    自定义异常
    RuntimeException:
        NullPointException
//错误
Error
@Transactional  默认出现RuntimeException和Error进行回滚

//启动测试类对下边的case进行测试
@Test
    public void throwRuntimeException(){
        testTransactional.throwException();
    }


 	/**
     *  case1:数据插入失败
     */
	@Transactional
    public void throwException(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        throw new RuntimeException();
    }
  
    /**
     *  case2:数据成功插入
     */
     @Transactional
    public void throwException() throws IOException {
        userMapper.insert(new UserDo(1L,"小明", 15));
        throw new IOException();
    }
    
    /**
     *  case3:数据插入失败
     */
    @Transactional(rollbackFor = Exception.class)
    public void throwException() throws IOException {
        userMapper.insert(new UserDo(1L,"小明", 15));
        throw new IOException();
    }

2.事务传播机制

Propagation.REQUIRED:当前有事务就加入,否则创建一个事务运行
Propagation.REQUIRES_NEW:当前有事务就挂起,并创建一个新事务运行。否则创建一个新事务运行
Propagation.SUPPORTS:当前有事务就加入,否则以非事务方式运行
Propagation.NOT_SUPPORTED:以非事务方式运行,当前有事务就挂起
Propagation.NESTED:有事务就在嵌套事务中运行,否则创建一个新事务
Propagation.MANDATORY:有事务就加入,否则抛出异常
Propagation.NEVER:以非事务方式运行,有事务则抛出异常

运行测试方法

 @Test
    public void throwRequired() {
        testOuterTransactionalServiceImpl.outer();
    }

case中的方法所在类的结构

@Service
public class TestOuterTransactionalServiceImpl {

    @Autowired
    private TestInnerTransactionalServiceImpl testSubTransactional;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private ApplicationContext applicationContext;

    @Transactional()
    public void outer(){
       //case中的方法内容
    }
}


@Service
public class TestInnerTransactionalServiceImpl {

    @Autowired
    private UserMapper userMapper;
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void inner() throws Exception {
        //case中的方法内容
    }

}

默认的事务传播行为:Propagation.REQUIRED:当前有事务就加入,否则创建一个事务运行

image.png

//case1:数据都不会被插入,都在同一个事务中,一个方法抛异常都会回滚
@Transactional(rollbackFor = Exception.class)
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        testSubTransactional.inner();
    }
    
@Transactional(rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
        throw new RuntimeException();
    }
    
//case2:数据都不会被插入,都在同一个事务中,一个方法抛异常都会回滚
@Transactional(rollbackFor = Exception.class)
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        testSubTransactional.inner();
        throw new RuntimeException();
    }
    
@Transactional(rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
    }
    
//case3:数据小明插入成功,数据小红插入失败。小红创建了一个新的事务,抛出异常回滚
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        testSubTransactional.inner();
    }
    
@Transactional(rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
        throw new RuntimeException();
    }

Propagation.REQUIRES_NEW:当前有事务就挂起,并创建一个新事务运行。否则创建一个新事务运行

//case1:数据小红插入成功,数据小明插入失败。小红创建了一个新的事务,外部异常回滚和她无关
@Transactional(rollbackFor = Exception.class)
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        testSubTransactional.inner();
        throw new RuntimeException();
    }
    
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
    }
   
//case2:数据小明插入成功,数据小红插入失败。outer()无事务。inner()创建了一个新的事务,出现异常回滚
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        testSubTransactional.inner();
        throw new RuntimeException();
    }
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
        throw new RuntimeException();
    }

Propagation.SUPPORTS:当前有事务就加入,否则以非事务方式运行

//case1:数据都不会被插入,都在同一个事务中,一个方法抛异常都会回滚
@Transactional(rollbackFor = Exception.class)
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        testSubTransactional.inner();
        throw new RuntimeException();
    }
   
@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
    }
    
//case2:数据都不会被插入,都在同一个事务中,一个方法抛异常都会回滚
@Transactional(rollbackFor = Exception.class)
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        testSubTransactional.inner();
    }
   
@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
        throw new RuntimeException();
    }
    
//case3:数据都插入成功,当没有事务时,inner()会以非事务方式运行
 public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        testSubTransactional.inner();
        //throw new RuntimeException();
    }
    
@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
        throw new RuntimeException();
    }

Propagation.NOT_SUPPORTED:以非事务方式运行,当前有事务就挂起

//case1:数据小红插入成功,数据小明插入失败。inner()会将原来的事务挂起,以非事务的方式运行
@Transactional(rollbackFor = Exception.class)
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        testSubTransactional.inner();
        //验证俩个方法不在同一个事务之中
        throw new RuntimeException();
    }
    
@Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
        //验证以非事务的方式运行
        throw new RuntimeException();
    }

Propagation.MANDATORY:有事务就加入,否则抛出异常

//case1:数据都不会被插入,都在同一个事务中,一个方法抛异常都会回滚
@Transactional(rollbackFor = Exception.class)
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        testSubTransactional.inner();
    }
    
@Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
        throw new RuntimeException();
    }
    
//case2:数据都不会被插入,都在同一个事务中,一个方法抛异常都会回滚
@Transactional(rollbackFor = Exception.class)
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        testSubTransactional.inner();
        throw new RuntimeException();
    }
    
@Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
    }
  
//case3:数据小明插入成功,数据小红插入失败,抛出异常
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        testSubTransactional.inner();
    }
    
@Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
    }

Propagation.NESTED:有事务就在嵌套事务中运行,否则创建一个新事务

//case1:数据都不会被插入,outer()方法抛异常,导致两个方法都回滚
@Transactional(rollbackFor = Exception.class)
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        testSubTransactional.inner();
        throw new RuntimeException();
    }
@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
    }
    
//case2:数据都不会被插入,inner()方法抛异常,导致两个方法都回滚
@Transactional(rollbackFor = Exception.class)
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        testSubTransactional.inner(); 
    }
@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
        throw new RuntimeException();
    }
    
//case3:数据小明插入成功,数据小红插入失败。outer()没有事务,inner()会创建一个新事务执行
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        testSubTransactional.inner();
    }
@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
        throw new RuntimeException();
    }

Propagation.NEVER:以非事务方式运行,有事务则抛出异常

//case1:数据都不会被插入,outer()存在事务,导致inner()抛出异常,两个方法都回滚
@Transactional(rollbackFor = Exception.class)
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        testSubTransactional.inner();
    }
    
@Transactional(propagation = Propagation.NEVER, rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
    }
    
//case2:数据都会被插入,outer()不存在事务,inner()以非事务方式运行
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        testSubTransactional.inner();
    }
    
@Transactional(propagation = Propagation.NEVER, rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
         throw new RuntimeException();
    }

3.不生效的情况

1.多线程

@Transactional(rollbackFor = Exception.class)
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        new Thread(new Runnable() {
            @Override
            public void run() {
                testSubTransactional.inner();
            }
        }).start();
        //throw new RuntimeException();
    }
    
@Transactional(rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
        throw new RuntimeException();
    }
    
俩个方法之间不会相互影响,一个抛出异常不会影响另一个回滚

2.类内调用未加注解的方法A调用加注解的方法B

//数据全部插入成功,因为@Transactional是基于aop做增强在方法前后加上事务相关的处理,调用内部方法没有从容器中获取增强方法,导致失效
public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        this.inner();
    }

@Transactional(rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
        throw new RuntimeException();
    }
    
    
 //修改方法1  将两个方法放到不同的类中
 
 //修改方法2
 
 第一步:注入applicationContext
 @Autowired
 private ApplicationContext applicationContext;
 
 第二步:通过applicationContext获取bean
 TestOuterTransactionalServiceImpl testOuterTransactionalServiceImpl = (TestOuterTransactionalServiceImpl)  applicationContext.getBean("testOuterTransactionalServiceImpl");
 testOuterTransactionalServiceImpl.inner();

3.修饰符

//使用private修饰被@Transactional注解的方法
public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        TestOuterTransactionalServiceImpl testOuterTransactionalServiceImpl = (TestOuterTransactionalServiceImpl) applicationContext.getBean("testOuterTransactionalServiceImpl");
        testOuterTransactionalServiceImpl.inner();
    }

@Transactional(rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
    }

4.try-catch捕获异常

//case1:数据小明插入成功,数据小红插入失败。inner()的异常被outer()捕获了
@Transactional(rollbackFor = Exception.class)
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        try {
            testSubTransactional.inner();
        } catch (Exception e) {
            System.out.println(e);
        }
    }
    
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
        throw new RuntimeException();
    }
    
//case2:数据都插入成功。异常都被捕获了
@Transactional(rollbackFor = Exception.class)
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        try {
            testSubTransactional.inner();
        } catch (Exception e) {
            System.out.println(e);
        }
    }  
    
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
        try {
            throw new RuntimeException();
        } catch (RuntimeException e) {
            System.out.println(e);
        }
    }

5.抛出异常不符

//数据小明插入成功,数据小红插入失败。inner()抛出的异常无法被outer()识别
@Transactional()
    public void outer() throws Exception {
        userMapper.insert(new UserDo(1L,"小明", 15));
        testSubTransactional.inner();
    }
    
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
    public void inner() throws Exception {
        userMapper.insert(new UserDo(1L,"小红", 15));
        throw new Exception();
    }
    
//所以要@Transactional(rollbackFor = Exception.class)

6.夹杂dml语句中夹杂ddl

在同一个事务中,ddl执行的时候会将之前的事务进行commit,这导致你回滚的时候,只能回滚ddl语句之后的操作

4.使用@Transactional 注解出现异常 “Transaction rolled back because it has been marked as rollback-only”

解决方法:

1.外层不捕捉

2.用创建新事务的传播机制Propagation.REQUIRES_NEW 或者 Propagation.NESTED

//case1:抛出以上异常,仍然会回滚代码。数据都不会插入
//处于同一个事务中的两个方法,外层try_catch 内层抛出异常
@Transactional(rollbackFor = Exception.class)
    public void outer(){
        userMapper.insert(new UserDo(1L,"小明", 15));
        try {
            testSubTransactional.inner();
        } catch (Exception e) {
            System.out.println(e);
        }
    }  
    
@Transactional(rollbackFor = Exception.class)
    public void inner(){
        userMapper.insert(new UserDo(1L,"小红", 15));
        throw new RuntimeException();
    }

2.编程式事务


PlatformTransactionManager 事务的操作方法

TransactionDefinition 事务的传播行为,隔离级别等

TransactionStatus 判断事务完成或是回滚

TransactionTemplate 实现编程式事务


DefaultTransactionDefinition 是TransactionDefinition的实现类
TransactionTemplate 主要使用上述三个类完成事务操作

public class TransactionTemplate extends DefaultTransactionDefinition implements TransactionOperations, InitializingBean {
@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
    Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
    if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
        return ((CallbackPreferringPlatformTransactionManager)this.transactionManager).execute(this, action);
    } else {
        TransactionStatus status = this.transactionManager.getTransaction(this);

        Object result;
        try {
            result = action.doInTransaction(status);
        } catch (Error | RuntimeException var5) {
            this.rollbackOnException(status, var5);
            throw var5;
        } catch (Throwable var6) {
            this.rollbackOnException(status, var6);
            throw new UndeclaredThrowableException(var6, "TransactionCallback threw undeclared checked exception");
        }

        this.transactionManager.commit(status);
        return result;
    }
  }
}
 public void outer() {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            
            @Override
            //doInTransactionWithoutResult没有返回值
            protected void doInTransactionWithoutResult(TransactionStatus status) {

                userMapper.insert(new UserDo(1L,"小明", 15));
                testSubTransactional.inner();
            }
        });
    }


public void inner() {
    transactionTemplate.setPropagationBehavior(3); //设置传播行为,从TransactionDefinition找值
    transactionTemplate.execute(new TransactionCallback<Object>() {
        @Override
        //doInTransaction有返回值
        public Object doInTransaction(TransactionStatus status) {
            userMapper.insert(new UserDo(1L,"小红", 15));

            return 1;
        }
    });
}