增删改 - 04 - 事务回滚与传播

本文详细介绍了Java中的事务概念,如何通过@Transactional注解确保事务的原子性,包括默认回滚策略、使用@Transactional(rollbackFor=Exception.class)的简单方法,以及遇到日志操作时如何通过事务传播特性(如Propagation.REQUIRES_NEW)控制回滚范围。作者还演示了手动事务管理和切面编程在事务处理中的应用。
摘要由CSDN通过智能技术生成

事务回滚与传播

1. 保证增删改事务安全

什么是事务: 单次执行的增删改等一系列操作,这些操作要么全部成功执行,要么全部失败回滚


2. 如何保证事务?

最简单的方法:使用@Transactional(rollbackFor = Exception.class)注解

注解可以用在方法和类上。
方法要求:只能用在被重写的方法上面
类要求:写在@Service的类上面

举个栗子:单表service方法里的插入

@Service
public class WorkOperatorServiceImpl extends ServiceImpl<WorkOperatorMapper, WorkOperator> implements IWorkOperatorService {

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean createWorkOperator(WorkOperatorDTO saveDTO) {
        //  DTO -> entity
        //  需要额外查询 片区、片区id、作业单位名称、年龄
        WorkOperator saveOne = transDTO2Entity(saveDTO);
        saveOne.setId(null);
        //  保存
        return workOperatorDAO.insertWorkOperator(saveOne);
    }
}

再举个栗子:多表数据操作

场景如下: 某一作业任务下有多个作业点位信息,一对多

有两张表 ,点位表和任务表
task : id, name, points(JSON,存储点位信息 id)
points : id, address, coordinate(JSON,存储点位经纬度坐标)

重点: 删除任务时,需要同步删除任务下的点位信息

@Service
public class GuaranteeTaskServiceImpl extends ServiceImpl<GuaranteeTaskMapper, GuaranteeTask> implements GuaranteeTaskService {

	/** 点位表 */
    @Resource
    GuaranteeTaskPointsServiceImpl pointsService;

	/** 任务表 */
    @Resource
    GuaranteeTaskAssignService assignService;

/**
     * 根据任务 id 删除 点位和自己
     *
     * @param id 任务 id
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteById(Long id) {
        GuaranteeTask oneTask = lambdaQuery().eq(GuaranteeTask::getId, id).one();
        //  删除点位,点位表操作
        pointsService.removeBatchByIds(oneTask.getPoints());
        //  删除分配任务里的 task-id = id 的数据
        return assignService.remove(new QueryWrapper<GuaranteeTaskAssign>().eq("task_id", id));
    }
}

这样一来,添加注解后的方法里,要么全部执行成功,要么全部回滚


3. 不想全部回滚,只想回滚一部分怎么办?

场景如下: 事务操作时,分为两步,第一步进行业务代码逻辑,第二部分进行日志的记录输出,业务代码作为主体功能,优先实现,日志不输出也无所谓。

出现的问题:按照上面的写法,添加单个注解后,业务代码执行成功,日志输出失败,但是主体功能也会回滚,导致此次业务办理失败。

预期目标:日志失败,但是业务成功实现

解决方案:
使用事务传播进行解决


3.1 事务实现的原理 - 切面实现

事务可以手动提交,也可以自动提交,所以,在事务操作前后,添加上事务手动提交的代码,改变当前事务的提交模式,代码执行无误时,commit事务,有误时,进行回滚(也就是不提交)

不自动提交事务相当于: 去饭馆吃饭没给钱,先吃饭
饭吃完后,付款,付款这一步就是 commit 操作
饭吃到一半,发现饭里有东西,开始找老板处理。找老板处理,不给钱这一步就是不提交,回滚操作,rollback

代码上手动实现事务操作如下:

class Transactional {
	//	建立数据库连接,进行操作
	s1 = new SqlSession();
	sqlSession.open();
	//	设置事务为手动提交
	s1.setAutoCommit(false);
	try{
		//	事务处理 Method()
		Method_1();
		Method_2();
		Method_3();
		//	处理完毕后,开始提交
		commit();
	} catch(Exception e){
		//	发现异常就回滚
		rollback();
	} finally {
		//	关闭和数据库的连接
		s1.close();
	}
}

如果每个事务操作都要这么写,那可就太蠢啦,所以直接使用了注解进行切面的环绕通知,完成了这一段重复性很强的代码。

切面 : 就是在某个方法运行时,对该方法的入参和结果添加一些新的处理逻辑,以达到增强原来方法的作用,后续讲到

使用注解后,上面的代码就变成了下面的样子,便于更加专注业务实现

class Transactional {
	@Transactional(rollbackFor = Exception.class)
	public Method(){
		Method_1();
		Method_2();
		Method_3();
	}
}

3.2 事务传播实现

讲完了原理,开始解决上面提到的日志事务问题

回滚操作,是在一个数据库连接中处理的,如果给主要的业务方法,再开一个新的数据库连接,对这个新连接进行事务操作,不影响原先的连接,不就OK了嘛

举个不太恰当的栗子: 你买了两份饭,A 和 B,A份有问题,B没有问题。经过和老板商量,A不用付钱,赔你一份新的(A 回滚),对于 B 还是得付钱(B 提交)

代码中如何实现呢?

  • 日志方法
class A {
	//	打印日志,进入总方法的sqlSession
	@Transactional(rollbackFor = Exception.class)
	public void logInfo();
}
  • 业务方法
class B {
	//	业务操作,新开一个连接,回滚该连接的数据
	@Transactional(rollbackFor = Exception.class, propagation = REQUIRES_NEW)
	public void Method();
}
  • 对于上面所述的日志问题处理方法,外部和内部的属性值看情况而定
    这样一来,业务方法Method(); 不会因 logInfo();失败导致回滚了。
class dealTransactional {
	@Transactional(rollbackFor = Exception.class)
	public void finallMethod() {
		logInfo();
		Method();
	}
}
  • propagation 属性值的说明
    对事务传播属性值的说明
    新开连接事务操作的图解如下:
    在这里插入图片描述
  • 21
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值