当前位置: 首页 > news >正文

【Java Web 快速入门】九、事务管理

目录

  • 事务管理
    • 事务回顾
    • Spring 事务管理基础
    • Spring 事务管理进阶
      • rollbackFor
      • propagation

事务管理

事务回顾

概念:事物是一组操作的集合,它是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败

操作:

  • 开启事务(一组操作开始前,开启事务):start transaction / begin ;
  • 提交事务(这组操作全部成功后,提交事务):commit ;
  • 回滚事务(中间任何一个操作出现异常,回滚事务):rollback ;

事务的必要性:当一组操作(如 “删除部门 + 删除该部门下的员工”)需要保证数据一致性时,必须通过事务控制。若没有事务,某一步操作异常可能导致部分操作生效、部分失效,造成数据不一致(例如:删除部门后出现异常,员工数据未被删除,导致数据关联错误)。

Spring 事务管理基础

在员工部门中,关于部门的代码为:

  • controller:
    import com.example.demo.pojo.Dept;
    import com.example.demo.responed.Result;
    import com.example.demo.service.DeptService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;import java.util.List;@Slf4j
    @RequestMapping("/depts")
    @RestController
    public class DeptController {@Autowiredprivate DeptService deptService;// 查询部门数据@GetMappingpublic Result list(){log.info("查询全部部门数据");//调用service查询部门数据List<Dept> deptList =  deptService.list();return Result.success(deptList);}// 删除部门@DeleteMapping("/{id}")public Result delete(@PathVariable Integer id){log.info("根据id删除部门:{}",id);//调用service删除部门deptService.deleteById(id);return Result.success();}//新增部门@PostMappingpublic Result add(@RequestBody Dept dept){log.info("新增部门: {}" , dept);//调用service新增部门deptService.add(dept);return Result.success();}
    }
    
  • service 接口:
    import com.example.demo.pojo.Dept;import java.util.List;public interface DeptService {void deleteById(Integer id);// 查询全部部门数据List<Dept> list();// 删除部门void delete(Integer id);// 新增部门void add(Dept dept);
    }
    
  • service 实现类
    import com.example.demo.mapper.DeptMapper;
    import com.example.demo.mapper.EmpMapper;
    import com.example.demo.pojo.Dept;
    import com.example.demo.service.DeptService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;import java.time.LocalDateTime;
    import java.util.List;@Service
    public class DeptServiceImpl implements DeptService {@Autowiredprivate DeptMapper deptMapper;@Overridepublic void deleteById(Integer id) {deptMapper.deleteById(id);}@Overridepublic List<Dept> list() {return deptMapper.list();}@Overridepublic void delete(Integer id) {deptMapper.deleteById(id);}@Overridepublic void add(Dept dept) {dept.setCreateTime(LocalDateTime.now());dept.setUpdateTime(LocalDateTime.now());deptMapper.insert(dept);}
    }
    
  • mapper 接口:
    import com.example.demo.pojo.Dept;
    import org.apache.ibatis.annotations.Delete;
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Select;import java.util.List;@Mapper
    public interface DeptMapper {// 查询全部部门@Select("select * from dept")List<Dept> list();// 根据ID删除部门@Delete("delete from dept where id = #{id}")void deleteById(Integer id);// 新增部门@Insert("insert into dept(name, create_time, update_time) values(#{name},#{createTime},#{updateTime})")void insert(Dept dept);
    }
    

目前,部门的删除操作只是删除了部门,然而在实际操作中,部门解散后相应的部门员工的部门编号也应该进行更改,为了方便,后面演示为部门删除后对应的部门员工也删除。

所以需要将部门的 service 实现类改为以下:

import com.example.demo.mapper.DeptMapper;
import com.example.demo.mapper.EmpMapper;
import com.example.demo.pojo.Dept;
import com.example.demo.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.time.LocalDateTime;
import java.util.List;@Service
public class DeptServiceImpl implements DeptService {@Autowiredprivate DeptMapper deptMapper;@Autowiredprivate EmpMapper empMapper;@Overridepublic void deleteById(Integer id) {// 1. 删除部门deptMapper.deleteById(id);// 2. 删除部门下的员工empMapper.deleteByDeptId(id);}// ...
}

在 EmpMapper 中添加接口方法:

// 根据部门id删除员工
@Delete("delete from emp where dept_id=#{deptId}")
void deleteByDeptId(Integer deptId);

目前代码存在一个问题,如果说在实现类调用的两个方法之间添加一个异常:

@Override
public void deleteById(Integer id) {// 1. 删除部门deptMapper.deleteById(id);int i = 1 / 0;// 添加异常// 2. 删除部门下的员工empMapper.deleteByDeptId(id);
}

即是程序抛出异常,部门依然删除了,部门被删除但相应的部门员工未被删除,导致数据不一致,这是因为异常导致后续删除员工的代码未执行,需将两步操作置于同一事务中才能保证事务的一致性。

Spring 中提供了 @Transactional 注解来解决这个问题:

  • 注解:@Transactional
  • 位置:service 层的方法上、类上、接口上
  • 作用:将当前方法交给 Spring 进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常回滚事务
  • 通常加在业务层执行多次数据访问操作的增删改方法上
import com.example.demo.mapper.DeptMapper;
import com.example.demo.mapper.EmpMapper;
import com.example.demo.pojo.Dept;
import com.example.demo.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.time.LocalDateTime;
import java.util.List;@Service
public class DeptServiceImpl implements DeptService {@Autowiredprivate DeptMapper deptMapper;@Autowiredprivate EmpMapper empMapper;@Transactional@Overridepublic void deleteById(Integer id) {// 1. 删除部门deptMapper.deleteById(id);int i = 1/0;// 2. 删除部门下的员工empMapper.deleteByDeptId(id);}// ...
}

这样就能保证事务的一致性,一起提交或者一起回滚。

可以配置 Spring 事务管理的日志开关,方便在控制台中查看日志:

application.properties:

logging.level.org.springframework.jdbc.support.JdbcTransactionManager=DEBUG

application.yml:

logging:level:org.springframework.jdbc.support.JdbcTransactionManager: DEBUG

Spring 事务管理进阶

@Transactional 注解的两个常见属性:

  • rollbackFor(异常回滚属性)
  • propagation(事务传播行为)

rollbackFor

将实现类的方法改为:

@Transactional
@Override
public void deleteById(Integer id) throw Exception{// 1. 删除部门deptMapper.deleteById(id);// 添加异常if(true) {throw new Exception("程序出错了!!!");	}// 2. 删除部门下的员工empMapper.deleteByDeptId(id);
}

如果代码中添加的是这种异常,即使添加了 @Transactional,也还是会出现程序抛出异常,部门依然删除了,部门被删除但相应的部门员工未被删除,导致数据不一致的情况。

这是因为 @Transactional 默认情况下,只有出现 RuntimeException(运行时异常)才会回滚,之前模拟的 1÷0 产生的算术运算异常属于运行时异常,所以能正常回滚,而直接抛出的 Exception 异常不是运行时异常,不会回滚。

rollbackFor 属性用于控制出现何种异常类型时回滚事务,配置为 rollbackFor = Exception.class 时,代表出现所有异常都会进行事务回滚。

@Transactional(rollbackFor = Exception.class)
@Override
public void deleteById(Integer id) throw Exception{// 1. 删除部门deptMapper.deleteById(id);// 添加异常if(true) {throw new Exception("程序出错了!!!");	}// 2. 删除部门下的员工empMapper.deleteByDeptId(id);
}

如此一来,不论是何种异常,都会进行事务回滚,保证了事务的一致性

propagation

事务传播行为指当一个事务方法被另一个事务方法调用时,该事务方法如何进行事务控制。例如 a 方法(带事务)调用 b 方法(带事务)时,b 是加入 a 的事务还是新建事务,由传播行为决定。

事务传播行为:

属性值含义
REQUIRED(默认值)需要事务,有则加入,无则创建新事务
REQUIRES_NEW需要新事务,无论有无,总是创建新事务
SUPPORTS支持事务,有则加入,无则在无事务状态中运行
NOT_SUPPORTED不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务
MANDATORY必须有事务,否则抛出异常
NEVER必须无事务,否则抛出异常

往数据库中新增一个部门操作日志表:

create table dept_log(id int unsigned primary key auto_increment comment '主键ID',create_time datetime not null comment '创建时间',description varchar(300) not null comment '操作描述'
)'部门操作日志表';

实体类为:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.time.LocalDateTime;@Data
@AllArgsConstructor
@NoArgsConstructor
public class DeptLog {private Integer id;private LocalDateTime createTime;private String description;
}

mapper 接口:

import org.apache.ibatis.annotations.Insert;public interface DeptLogMapper {@Insert("insert into dept_log(description, create_time) values(#{description}, #{createTime})")void insert(DeptLog deptLog);
}

service 接口以及实现类:

// service接口
import com.example.demo.pojo.DeptLog;
public interface DeptLogService {void insert(DeptLog deptLog);
}// service实现类
import com.example.demo.mapper.DeptLogMapper;
import com.example.demo.pojo.DeptLog;
import com.example.demo.service.DeptLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class DeptLogServiceImpl implements DeptLogService {@Autowiredprivate DeptLogMapper deptLogMapper;@Transactional@Overridepublic void insert(DeptLog deptLog) {deptLogMapper.insert(deptLog);}
}

现在要求,不论删除操作是否成功,都要记录操作日志,将 DeptServiceImpl 改为:

import com.example.demo.mapper.DeptMapper;
import com.example.demo.mapper.EmpMapper;
import com.example.demo.pojo.Dept;
import com.example.demo.pojo.DeptLog;
import com.example.demo.service.DeptLogService;
import com.example.demo.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.time.LocalDateTime;
import java.util.List;@Service
public class DeptServiceImpl implements DeptService {@Autowiredprivate DeptMapper deptMapper;@Autowiredprivate EmpMapper empMapper;@Autowiredprivate DeptLogService deptLogService;@Transactional@Overridepublic void deleteById(Integer id) {try {// 1. 删除部门deptMapper.deleteById(id);int i = 1/0;// 2. 删除部门下的员工empMapper.deleteByDeptId(id);}  finally {// 3. 删除部门日志DeptLog deptLog = new DeptLog();deptLog.setCreateTime(LocalDateTime.now());deptLog.setDescription("删除了部门id为" + id + "的部门");deptLogService.insert(deptLog);}}// ...
}

deleteById 方法(带事务)调用 insert 方法(带事务)时,insert 会加入 delete 的事务。若 delete 执行中出现异常,整个事务回滚,导致日志记录也回滚,无法满足需求。

import com.example.demo.mapper.DeptLogMapper;
import com.example.demo.pojo.DeptLog;
import com.example.demo.service.DeptLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;@Service
public class DeptLogServiceImpl implements DeptLogService {@Autowiredprivate DeptLogMapper deptLogMapper;@Transactional(propagation = Propagation.REQUIRES_NEW)@Overridepublic void insert(DeptLog deptLog) {deptLogMapper.insert(deptLog);}
}

将 insert 方法的传播行为设为 REQUIRES_NEW 后,调用 insert 时会挂起当前事务并新建事务。insert 执行完毕后提交,不受 delete 事务回滚影响,即使 delete 异常,日志也能成功记录。

http://www.xdnf.cn/news/1287613.html

相关文章:

  • 【论文阅读】RestorerID: Towards Tuning-Free Face Restoration with ID Preservation
  • 【游戏优化笔记】开发中如何减少建筑和树木等环境元素的资源消耗?
  • 【跨服务器的数据自动化下载--安装公钥,免密下载】
  • 【CSS3】录音中。。。
  • 【oracle闪回查询】记录字段短时间被修改的记录
  • 【AI绘画】Stable Diffusion webUI 常用功能使用技巧
  • css之再谈浮动定位float(深入理解篇)
  • react+vite来优化下每次使用hook函数都要引入的情况
  • React (react-amap)高德地图使用(加标记、缩放、缩略图)
  • 荣耀手机无法连接win11电脑,错误消息:“无法在此设备上加载驱动程序 (hn_usbccgpfilter.sys)。”解决方案
  • OBOO鸥柏丨智能会议平板教学查询一体机交互式触摸终端招标投标核心标底参数要求
  • SQL Server增加对UTF-8的支持
  • Baumer高防护相机如何通过YoloV8深度学习模型实现纸箱的实时检测计数(C#代码UI界面版)
  • 谷歌ADK接入文件操作MCP
  • 力扣47:全排列Ⅱ
  • 基于Python的《红楼梦》文本分析与机器学习应用
  • 力扣 hot100 Day71
  • vivo Pulsar 万亿级消息处理实践(2)-从0到1建设 Pulsar 指标监控链路
  • [激光原理与应用-254]:理论 - 几何光学 - 自动对焦的原理
  • 数据结构:中缀到后缀的转换(Infix to Postfix Conversion)
  • Flutter GridView的基本使用
  • Java 工厂方法模式
  • 【项目设计】高并发内存池
  • 北京-4年功能测试2年空窗-报培训班学测开-第七十四天-线下面试-聊的很满意但可能有风险-等信吧
  • cuda排序算法--双调排序(Bitonic_Sort)
  • web前端第二次作业
  • 开发避坑指南(23):Tomcat高版本URL特殊字符限制问题解决方案(RFC 7230 RFC 3986)
  • TF-IDF:信息检索与文本挖掘的统计权重基石
  • 多奥电梯智能化解决方案的深度解读与结构化总结,内容涵盖系统架构、功能模块、应用场景与社会价值四大维度,力求全面展示该方案的技术先进性与应用前景。
  • Agent智能体基础