Administrator
发布于 2022-12-30 / 84 阅读
0
0

Boot Transaction Best Practise

Get Started

@Transactional Annotation—Declarative Transaction

@Service
public class UserServiceImpl implements UserService{

    @Transactional
    @Override
    public void insertUserDeclarative(WpUser wpUser) {
        userDao.insertUser(wpUser);
        // 增加一个异常,此时事务会生效,即wpUser插入到数据库这个操作,会被rollback
        int i = 1/0;
    }
}

测试:

@SpringBootTest
class BootTransactionDemoApplicationTests {

    @Autowired
    private UserService userService;

    @Test
    void contextLoads() {
        WpUser wpUser = new WpUser();
        wpUser.setName("测试人1号");
        wpUser.setAge(25);
        userService.insertUserDeclarative(wpUser);
    }
}

通过上面的demo,我们可以知道,给insertUserDeclarative方法添加了一个@Transactional注解,就可以开启事务了,当insertUserDeclarative方法抛出异常后,就会进行事务的rollback,从而让插入到数据库的数据,也实现了rollback。

PlatformTransactionManager—Programmatic txManager

@Service
public class UserServiceImpl implements UserService{

    @Autowired
    private PlatformTransactionManager txManager;
    
    @Override
    public void insertUserProgrammatic(WpUser wpUser) {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        // explicitly setting the transaction name is something that can be done only programmatically
        def.setName("insertUserTx");
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        TransactionStatus txStatus = txManager.getTransaction(def);

        try {
            userDao.insertUser(wpUser);
            // 增加一个异常,此时事务会生效,即wpUser插入到数据库这个操作,会被rollback
            int i = 1/0;
        }catch (Exception e) {
            txManager.rollback(txStatus);
            throw e;
        }
        txManager.commit(txStatus);
    }
}

测试

@SpringBootTest
class BootTransactionDemoApplicationTests {

    @Autowired
    private UserService userService;

    @Test
    void contextLoads() {
        WpUser wpUser = new WpUser();
        wpUser.setName("测试人1号");
        wpUser.setAge(25);
        userService.insertUserProgrammatic(wpUser);
    }
}

通过上面的demo,我们可以看到,通过PlatformTransactionManager ,来实现编程式事务,出现异常后,调用PlatformTransactionManager 的rollback方法,来实现事务回滚。



我们知道,jdbc原生的事务,是通过设置connection.setAutoCommit(false);connection.commit();connection.rollback();来实现的。
而spring事务其实也就是,对这些代码进行了封装。

比如,上面的PlatformTransactionManager的编程式事务,在调用getTransaction方法时,就是设置connection的autoCommit为false。调用链如下:

org.springframework.transaction.PlatformTransactionManager#getTransaction

----->org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction


----->org.springframework.transaction.support.AbstractPlatformTransactionManager#startTransaction

-----> org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin        con.setAutoCommit(false);

TransactionTemplate—Programmatic txManager

@Service
public class UserServiceImpl implements UserService{
    @Autowired
    private TransactionTemplate txTemplate;
    
    @Override
    public void insertUserByTxtemplate(WpUser wpUser) {
        Integer rows = txTemplate.execute(new TransactionCallback<Integer>() {
            @Override
            public Integer doInTransaction(TransactionStatus status) {

                try {
                    int affectedRows = userDao.insertUser(wpUser);
                    // 增加一个异常,此时事务会生效,即wpUser插入到数据库这个操作,会被rollback
//                    int i = 1 / 0;
                    return affectedRows;
                } catch (Exception e) {
                    status.setRollbackOnly();
                    return 0;
                }
            }
        });

        System.out.println("rows:"+rows);
    }
}

测试

@SpringBootTest
class BootTransactionDemoApplicationTests {

    @Autowired
    private UserService userService;

    @Test
    void contextLoads() {
        WpUser wpUser = new WpUser();
        wpUser.setName("测试人1号");
        wpUser.setAge(25);
        userService.insertUserByTxtemplate(wpUser);
    }
}

通过上面的demo,我们可以通过TransactionTemplate,来实现事务的控制,包括提交和回滚。

multiple transaction

多个事务的Declarative式写法

如果有多个事务,那么必须,要用一个额外的大事务,来包裹多个小事务,这样才能做到行动一致。


如果不用一个额外的大事务,那么多个小事务,是无法做到,一起提交 或者 一起回滚的。

并且,要指定@Transactional(propagation= Propagation.REQUIRED)

大事务如下:


@Service
public class UserDeptServiceImpl implements UserDeptService{

    @Autowired
    private UserService userService;

    @Autowired
    private DeptService deptService;

    @Transactional(propagation= Propagation.REQUIRED)
    @Override
    public void insertUserDeptDeclarative(WpUser wpUser, WpDept wpDept) {
        // 这里,我们想实现一个效果:插入用户 和 部门,假设有任何一个失败了,那么另一个也回滚
        userService.insertUserDeclarative(wpUser);
        deptService.insertDeptDeclarative(wpDept);
    }
}

2个小事务,分别如下

@Service
public class UserServiceImpl implements UserService{

    @Autowired
    private UserDao userDao;

    @Autowired
    private PlatformTransactionManager txManager;

    @Autowired
    private TransactionTemplate txTemplate;

    @Transactional(propagation= Propagation.REQUIRED)
    @Override
    public void insertUserDeclarative(WpUser wpUser) {
        userDao.insertUser(wpUser);
        // 增加一个异常,此时事务会生效,即wpUser插入到数据库这个操作,会被rollback
//        int i = 1/0;
    }
}

------------------------------

@Service
public class DeptServiceImpl implements DeptService{

    @Autowired
    private DeptDao deptDao;

    @Transactional(propagation= Propagation.REQUIRED)
    @Override
    public int insertDeptDeclarative(WpDept wpDept) {
        int affectedRows = deptDao.insertDept(wpDept);
        int i = 1/0;
        return affectedRows;
    }
}

测试

@SpringBootTest
class BootTransactionDemoApplicationTests {
    @Test
    void testUserDept() {
        WpUser wpUser = new WpUser();
        wpUser.setName("测试人1号");
        wpUser.setAge(25);

        WpDept wpDept = new WpDept();
        wpDept.setName("测试部");
        userDeptService.insertUserDeptDeclarative(wpUser,wpDept);
    }
}

多个事务的Programmatic写法

和上面一样,如果有多个事务,那么必须,要用一个额外的大事务,来包裹多个小事务,这样才能做到行动一致。

如果不用一个额外的大事务,那么多个小事务,是无法做到,一起提交 或者 一起回滚的。

2个小事务,分别如下:

@Service
public class UserServiceImpl implements UserService{

    @Autowired
    private PlatformTransactionManager txManager;

    @Override
    public void insertUserProgrammatic(WpUser wpUser) {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        // explicitly setting the transaction name is something that can be done only programmatically
        def.setName("insertUserTx");
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        TransactionStatus txStatus = txManager.getTransaction(def);

        try {
            userDao.insertUser(wpUser);
            // 增加一个异常,此时事务会生效,即wpUser插入到数据库这个操作,会被rollback
           //  int i = 1 / 0;
            txManager.commit(txStatus);
        } catch (Exception e) {
            txManager.rollback(txStatus);
           throw e; // 这里,必须要将异常抛出
        }
    }
    
}   

这里,需要注意:在catch中,除了回滚事务,还必须要将异常抛出。 这里先给出结论,下面会解释,为什么要将异常抛出。

@Service
public class DeptServiceImpl implements DeptService{

    @Autowired
    private PlatformTransactionManager txManager;
    
    @Override
    public void insertDeptProgrammatic(WpDept wpDept) {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        // explicitly setting the transaction name is something that can be done only programmatically
        def.setName("insertDeptTx");
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        TransactionStatus txStatus = txManager.getTransaction(def);

        try {
            deptDao.insertDept(wpDept);
            // 增加一个异常,此时事务会生效,即wpUser插入到数据库这个操作,会被rollback
            int i = 1 / 0;
            txManager.commit(txStatus);
        } catch (Exception e) {
            txManager.rollback(txStatus);
             throw e; // 这里,必须要将异常抛出
        }
    }
}

大事务,如下:

@Service
public class UserDeptServiceImpl implements UserDeptService{

    @Autowired
    private UserService userService;

    @Autowired
    private DeptService deptService;

    @Autowired
    private PlatformTransactionManager txManager;
    
    @Override
    public void insertUserDeptProgrammatic(WpUser wpUser, WpDept wpDept) {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        // explicitly setting the transaction name is something that can be done only programmatically
        def.setName("insertUserDeptTx");
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        TransactionStatus txStatus = txManager.getTransaction(def);

        try {
            userService.insertUserProgrammatic(wpUser);
            deptService.insertDeptProgrammatic(wpDept);
            txManager.commit(txStatus);
        } catch (Exception e) {
            txManager.rollback(txStatus);
            throw e; // 这里,必须要将异常抛出
        }
    }
    
}    

测试

    @Test
    void testUserDept() {
        WpUser wpUser = new WpUser();
        wpUser.setName("测试人1号");
        wpUser.setAge(25);

        WpDept wpDept = new WpDept();
        wpDept.setName("测试部");
        userDeptService.insertUserDeptProgrammatic(wpUser,wpDept);
    }

通过上面的demo,可以看到,只要2个小事务,任何一个事务回滚了,其他的事务,也会一起被回滚。

上面的流程如下:

image-20221230123440995

需要注意的是:

  1. 虽然事务A,调用了commit方法,但是因为spring实际上,并且真的去执行commit操作,只是记录下来了。

  2. 而事务B,调用了rollback方法,spring实际上,也没有真的去执行rollback操作,也只是记录下来了。

  3. 等到,大事务,调用rollback方法,此时,spring才真正的去执行rollback操作,结果就是,事务A和B中的操作,都回滚了

为什么会这样呢?我们看下源码就知道了

// org.springframework.transaction.support.AbstractPlatformTransactionManager#processCommit
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
		try {
			boolean beforeCompletionInvoked = false;

			try {
				boolean unexpectedRollback = false;
				prepareForCommit(status);
				triggerBeforeCommit(status);
				triggerBeforeCompletion(status);
				beforeCompletionInvoked = true;

				if (status.hasSavepoint()) {
					if (status.isDebug()) {
						logger.debug("Releasing transaction savepoint");
					}
					unexpectedRollback = status.isGlobalRollbackOnly();
					status.releaseHeldSavepoint();
				}
				else if (status.isNewTransaction()) { // 这一步,是最关键的一步
					if (status.isDebug()) {
						logger.debug("Initiating transaction commit");
					}
					unexpectedRollback = status.isGlobalRollbackOnly();
					doCommit(status);
				}
				else if (isFailEarlyOnGlobalRollbackOnly()) {
					unexpectedRollback = status.isGlobalRollbackOnly();
				}

				// Throw UnexpectedRollbackException if we have a global rollback-only
				// marker but still didn't get a corresponding exception from commit.
				if (unexpectedRollback) {
					throw new UnexpectedRollbackException(
							"Transaction silently rolled back because it has been marked as rollback-only");
				}
			}
			catch (UnexpectedRollbackException ex) {
				// can only be caused by doCommit
				triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
				throw ex;
			}
			catch (TransactionException ex) {
				// can only be caused by doCommit
				if (isRollbackOnCommitFailure()) {
					doRollbackOnCommitException(status, ex);
				}
				else {
					triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
				}
				throw ex;
			}
			catch (RuntimeException | Error ex) {
				if (!beforeCompletionInvoked) {
					triggerBeforeCompletion(status);
				}
				doRollbackOnCommitException(status, ex);
				throw ex;
			}

			// Trigger afterCommit callbacks, with an exception thrown there
			// propagated to callers but the transaction still considered as committed.
			try {
				triggerAfterCommit(status);
			}
			finally {
				triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
			}

		}
		finally {
			cleanupAfterCompletion(status);
		}
	}

上面的代码中,只有status.isNewTransaction()为true,才会去真正执行commit。


而事务A的status.isNewTransaction()其实是false,所以,没有真正执行commit。

事务B的status.isNewTransaction()也是false,

而大事务的status.isNewTransaction()才是true,所以大事务调用commit方法,才会真正去执行commit操作。




接下来,我们再来看另一个问题,上面说过,在捕获异常后,除了要调用rollback方法,还需要将异常抛出。现在,我们在这里解释下原因:

image-20221230110639768

通过上面的流程图,我们发现,如果不将异常抛出,很可能会出现:事务A调用了rollback方法后,事务B又调用commit方法,而这2个事务,其实是同一个事务,导致出现了org.springframework.transaction.IllegalTransactionStateException: Transaction is already completed - do not call commit or rollback more than once per transaction

多个事务的TransactionTemplate写法

和上面一样,如果有多个事务,那么必须,要用一个额外的大事务,来包裹多个小事务,这样才能做到行动一致。

如果不用一个额外的大事务,那么多个小事务,是无法做到,一起提交 或者 一起回滚的。

2个小事务,分别如下:

@Service
public class UserServiceImpl implements UserService{
    @Autowired
    private TransactionTemplate txTemplate;

    @Override
    public void insertUserByTxtemplate(WpUser wpUser) {
        Integer rows = txTemplate.execute(new TransactionCallback<Integer>() {
            @Override
            public Integer doInTransaction(TransactionStatus status) {

                try {
                    int affectedRows = userDao.insertUser(wpUser);
                    // 增加一个异常,此时事务会生效,即wpUser插入到数据库这个操作,会被rollback
                    int i = 1 / 0;
                    return affectedRows;
                } catch (Exception e) {
                    status.setRollbackOnly();
                    throw e;
                }
            }
        });

        System.out.println("insertUserByTxtemplate rows:"+rows);
    }
}
@Service
public class DeptServiceImpl implements DeptService{
    @Autowired
    private TransactionTemplate txTemplate;
    
     @Override
    public void insertDeptByTxtemplate(WpDept wpDept) {
        Integer affectedRows = txTemplate.execute(new TransactionCallback<Integer>() {
            @Override
            public Integer doInTransaction(TransactionStatus status) {
                try {
                    int rows = deptDao.insertDept(wpDept);
//                    int i = 1/0;
                    return rows;
                } catch (Exception e) {
                    status.setRollbackOnly();
                    throw e;
                }
            }
        });
        System.out.println("insertDeptByTxtemplate rows:"+affectedRows);
    }

}



大事务如下:

@Service
public class UserDeptServiceImpl implements UserDeptService{

  @Autowired
    private UserService userService;

    @Autowired
    private DeptService deptService;
    
    @Autowired
    private TransactionTemplate txTemplate;
    
    @Override
    public void insertUserDeptByTemplate(WpUser wpUser, WpDept wpDept) {
        Integer affectedRows = txTemplate.execute(new TransactionCallback<Integer>() {
            @Override
            public Integer doInTransaction(TransactionStatus status) {
                try {
                    userService.insertUserByTxtemplate(wpUser);
                    deptService.insertDeptByTxtemplate(wpDept);
                    return 1;
                } catch (Exception e) {
                    status.setRollbackOnly();
                    System.err.println(e.getMessage());
                    return 0;
                }
            }
        });
    }
}


测试

@SpringBootTest
class BootTransactionDemoApplicationTests {
    @Autowired
    private UserDeptService userDeptService;
    
     @Test
    void testUserDept() {
        WpUser wpUser = new WpUser();
        wpUser.setName("测试人1号");
        wpUser.setAge(25);

        WpDept wpDept = new WpDept();
        wpDept.setName("测试部");
        userDeptService.insertUserDeptByTemplate(wpUser,wpDept);
    }

}

评论