springboot项目中手动提交事务
作者:mmseoamin日期:2023-12-21

springboot项目中手动提交事务

  • 演示主要代码
  • 场景/需求/实际效果
  • 解决办法 :在mi方法中手动提交事务
  • Spring的7中事务传播行为

    演示主要代码

    @Service 层代码

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.PlatformTransactionManager;
    import org.springframework.transaction.TransactionDefinition;
    import org.springframework.transaction.TransactionStatus;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.transaction.support.DefaultTransactionDefinition;
    @Service
    @Transactional(rollbackFor = Exception.class)
    public class XlServiceImpl {
    	Logger logger = LoggerFactory.getLogger(this.getClass());
    	@Autowired
    	private XlMapper xlMapper;
    	
    	/**
    	 * 需求:mi()方法抛出异常,不影响本方法:本方法不回滚!
    	 * mi抛出异常后,insert1()正常插入
    	 * @param id
    	 * @return
    	 * @throws Exception
    	 */
    	public String doInsert() throws Exception {
    		xlMapper.insert1();
    		mi();
    		return "200";
    	}
    	
    	/**
    	 * 需求:本方法抛出异常时,回滚
    	 * 抛出异常后,insert2()回滚:不插入
    	 */
    	private void mi() {
    		xlMapper.insert2();
    		int x = 0;
    		int y = 3 / x; // 
    	}
    }
    

    Mapper接口层代码: insert1()和insert2()的插入SQL

    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Update;
    public interface XlMapper {
        @Update("UPDATE xl999 SET age=333 WHERE id=#{id}")
    	Integer updateById(Integer id);
        
        @Insert("INSERT INTO xl (name,age,create_time) VALUES('dp1',111,NOW())")
        Integer insert1();
        
        @Insert("INSERT INTO xl (name,age,create_time) VALUES('dp2',222,NOW())")
        Integer insert2();
    }
    

    controller层代码

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    @RestController
    public class ClassForTest {
    	Logger logger = LoggerFactory.getLogger(this.getClass());
    	@Autowired
    	private XlServiceImpl xlServiceImpl;
    	@GetMapping("/dosth")
    	public String doSth() throws Exception {
    		String affect = "";
    		affect = xlServiceImpl.doInsert();
    		return affect;
    	}
    }
    

    数据库表结构及初始数据

    springboot项目中手动提交事务,在这里插入图片描述,第1张

    场景/需求/实际效果

    1. 场景

      在spring的声明式事务@Transactional(rollbackFor = Exception.class)的类XlServiceImpl中:

    • 有其中一个方法doInsert()调用另外一个方法mi()。
    • doInsert()会调用Mapper接口的insert1()方法向数据库中插入一条数据,然后会调用方法mi()。
    • mi()会调用Mapper接口的insert2()方法向数据库中插入一条数据,然后会抛出异常。
      1. 需求

        insert1()可以正常插入,insert2()回滚,不会插入!

      2. 实际效果

        运行项目,调用方法,结果如下:

      • 页面:

        springboot项目中手动提交事务,在这里插入图片描述,第2张

      • 程序后台

        springboot项目中手动提交事务,在这里插入图片描述,第3张

      • 数据库: 与初始数据库数据一致,并没有数据插入——与需求中的 insert1()成功插入不符合。

        springboot项目中手动提交事务,在这里插入图片描述,第4张

        解决办法 :在mi方法中手动提交事务

        1. 在@Transactional(rollbackFor = Exception.class)类中注入spring的事务管理器PlatformTransactionManager:
        	/**
        	 * 引入 (平台)事务管理器,Spring 事务策略的核心。
        	 */
        	@Autowired
        	private PlatformTransactionManager transactionManager;
        
        1. 在mi方法中手动提交事务/回滚事务
        	/**
        	 * 需求:本方法抛出异常时,回滚 抛出异常后,insert2()回滚:不插入
        	 */
        	private void mi() {
        		DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        		def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);// 新发起一个事务
        		TransactionStatus status = transactionManager.getTransaction(def);// 获得事务状态
        		try {
        			xlMapper.insert2();
        			int x = 0;
        			int y = 3 / x;
        			// 手动提交事务
        			transactionManager.commit(status);
        		} catch (Exception e) {
        			// 手动回滚事务
        			transactionManager.rollback(status);
        		}
        	}
        
        1. 运行项目,调用方法,查看效果:

          springboot项目中手动提交事务,在这里插入图片描述,第5张

        2. 进一步测试:注释掉下面两行,效果虽然是一样的,但是,还是写上最好!!
        transactionManager.commit(status);
        
        transactionManager.rollback(status);
        

        mi()的完整代码:

        /**
        	 * 需求:本方法抛出异常时,回滚 抛出异常后,insert2()回滚:不插入
        	 */
        	private void mi() {
        		DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        		def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);// 新发起一个事务
        		TransactionStatus status = transactionManager.getTransaction(def);// 获得事务状态
        		try {
        			xlMapper.insert2();
        			int x = 0;
        			int y = 3 / x;
        			// 手动提交事务
        //			transactionManager.commit(status);
        		} catch (Exception e) {
        			// 手动回滚事务
        //			transactionManager.rollback(status);
        		}
        	}
        

        以上说明:真正起作用的是下面3行:

        		DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        		def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);// 新发起一个事务
        		TransactionStatus status = transactionManager.getTransaction(def);// 获得事务状态
        

        而,这3行中又起关键作用的是最后一行 :

        TransactionStatus status = transactionManager.getTransaction(def);// 获得事务状态
        

        springboot项目中手动提交事务,在这里插入图片描述,第6张

        通过第2句可知:最后一句是创建一个新事务!而这个新事务会自动完成提交和回滚,所以注释掉 提交和回滚的代码效果是一样的!!

        特别注意:

        1. @Transactional(rollbackFor = Exception.class) 方式创建的事务,如果将异常catch后,在catch块中不再抛出异常,是不会触发回滚的!
        2. 但是,在方法中显示手动创建一个事务:
        		DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        		def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);// 新发起一个事务
        		TransactionStatus status = transactionManager.getTransaction(def);// 获得事务状态
        

        这种手动方式创建的事务,在catch块中可以不用再抛出异常,也可以不用显示的写出:transactionManager.rollback(status); ,会自动进行回滚!

        1. 在方法中手动创建事务时,transactionManager.rollback(status); 最多只能执行一次,如果方法中有2个及以上地方调用的transactionManager.rollback(status); 回滚方法,那么程序就会抛出异常!如下:

          springboot项目中手动提交事务,在这里插入图片描述,第7张

          springboot项目中手动提交事务,在这里插入图片描述,第8张

        2. 在方法中如果执行了事务提交:transactionManager.commit(status); 后面再去执行事务回滚transactionManager.rollback(status);,也会报上图的错误 “事务已完成——不要再同一个事务中提交或回滚超过一次”

        也就是说,不管是提交事务,还是回滚事务,二者只能有一个执行并且只能执行一次!!!

        springboot项目中手动提交事务,在这里插入图片描述,第9张

        Spring的7中事务传播行为

        Propagation.REQUIRED代表当前方法支持当前的事务,且与调用者处于同一事务上下文中,回滚统一回滚(如果当前方法是被其他方法调用的时候,且调用者本身即有事务),如果没有事务,则自己新建事务,
        Propagation.SUPPORTS代表当前方法支持当前的事务,且与调用者处于同一事务上下文中,回滚统一回滚(如果当前方法是被其他方法调用的时候,且调用者本身即有事务),如果没有事务,则该方法在非事务的上下文中执行
        Propagation.MANDATORY代表当前方法支持当前的事务,且与调用者处于同一事务上下文中,回滚统一回滚(如果当前方法是被其他方法调用的时候,且调用者本身即有事务),如果没有事务,则抛出异常
        Propagation.REQUIRES_NEW创建一个新的事务上下文,如果当前方法的调用者已经有了事务,则挂起调用者的事务,这两个事务不处于同一上下文,如果各自发生异常,各自回滚
        Propagation.NOT_SUPPORTED该方法以非事务的状态执行,如果调用该方法的调用者有事务则先挂起调用者的事务
        Propagation.NEVER该方法以非事务的状态执行,如果调用者存在事务,则抛出异常
        Propagation.NESTED如果当前上下文中存在事务,则以嵌套事务执行该方法,也就说,这部分方法是外部方法的一部分,调用者回滚,则该方法回滚,但如果该方法自己发生异常,则自己回滚,不会影响外部事务,如果不存在事务,则与PROPAGATION_REQUIRED一样

        springboot项目中手动提交事务,在这里插入图片描述,第10张