后端三郎:一名99年的宝藏开发男孩儿
看咱关系:文章对你有用的话,点赞打卡
目录
背景原因
假设我们接口修改数据,接口处理了一半,数据已经入库了,处理另一半的时候报错了,此时我们要保证已经处理完的数据回滚到未处理之前,保证数据的原子性。
Spring的@Transactional注解可以很方便的开启事务,但是默认只在遇到运行时异常和Error时才会回滚,非运行时异常不回滚
,即Exception的子类中,除了RuntimeException及其子类,其他的类默认不回滚。
框架版本
SpringBoot——2.4.1
Lombok——1.18.16
Mybatis-Plus——3.4.2
MySql——8.0.22
代码示例
表结构和数据
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for mjj_food_model -- ---------------------------- DROP TABLE IF EXISTS `mjj_food_model`; CREATE TABLE `mjj_food_model` ( `id` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '主键ID(uuid)', `food_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '美食名称', `food_brief` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '美食简介', `food_picture` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '美食图片', `city_id` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '城市ID', `city_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '城市名称', `create_id` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建人ID', `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', `update_id` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '修改人ID', `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间', `is_delete` tinyint(1) NULL DEFAULT NULL COMMENT '是否删除', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '美食特产表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of mjj_food_model -- ---------------------------- INSERT INTO `mjj_food_model` VALUES ('c525acf0-7763-11ec-996d-00163e0c3e0d', '牛肉丸子面', '临汾以牛肉丸子面最为有名,属于清真美食,牛肉丸子汤遍布临汾,一碗端上来,厚厚的红油看着就很有食欲,汤底是用牛骨高汤熬制,有嚼劲的丸子和劲道的面相互搭配,再加上青菜点缀,吃起来香、辣、麻,一碗下肚,很是过瘾。', '牛肉面图片', '167fa911-776e-11ec-996d-00163e0c3e0d', '临汾', '1', '2022-01-18 15:16:58', '1', '2022-01-18 15:17:05', 1); SET FOREIGN_KEY_CHECKS = 1;
MjjFoodModel实体类
package com.example.demo.entity.food; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.time.LocalDateTime; @AllArgsConstructor//有参构造方法 @NoArgsConstructor//无参构造方法 @Data public class MjjFoodModel { /** *美食特产主键ID */ private String id; /** *美食名称 */ private String foodName; /** *美食简介 */ private String foodBrief; /** *美食图片 */ private String foodPicture; /** *城市ID */ private String cityId; /** *城市名称 */ private String cityName; /** *创建人ID */ private String createId; /** *创建时间 */ private LocalDateTime createTime; /** *修改人ID */ private String updateId; /** *修改时间 */ private LocalDateTime updateTime; /** *是否删除 true(1)未删除 false(0)删除 */ private Boolean isDelete; }
Controller方法
@RestController @RequestMapping(value = "/food") //swagger注解,不用可以删除 @Api(value = "后端三郎and前端小马",tags = "李淳罡VS曹长卿————美食模块") public class FoodController { @Resource private FoodService foodService; @GetMapping(value = "/save") //swagger注解,不用可以删除 @ApiOperation(value = "美食特产编辑",httpMethod = "GET",notes = "美食特产编辑") public void save(){ foodService.save(); } }
FoodService 接口
void save();
FoodServiceImpl 实现类
@Resource private MjjFoodModelMapper mjjFoodModelMapper; @Override public void save() { //创建实体类 MjjFoodModel mjjFoodModel = new MjjFoodModel(); //复制主键ID为UUID mjjFoodModel.setId(UUID.randomUUID().toString()); //美食名称 mjjFoodModel.setFoodName("美食名称"); //新增 mjjFoodModelMapper.insert(mjjFoodModel); //写个BUG,使程序出现错误 int a = 4/0; }
MjjFoodModelMapper接口
@Mapper public interface MjjFoodModelMapper extends BaseMapper<MjjFoodModel> { }
不加事务结果
启动项目,访问localhost:8081/food/save测试(端口号自己配置的)
注:500的错误是我们故意写错的,为了看数据是否会回滚
接口运行一半报错,此时我们查看数据发现数据却添加进去了,此时会对我们的数据造成脏数据,因为报错了你再次请求的时候就会有多条数据。不符合我们的业务逻辑。
使用事务结果
在FoodServiceImpl 实现类的save方法上边加上@Transactional注解,再次测试
@Override @Transactional public void save() { //创建实体类 MjjFoodModel mjjFoodModel = new MjjFoodModel(); //复制主键ID为UUID mjjFoodModel.setId(UUID.randomUUID().toString()); //美食名称 mjjFoodModel.setFoodName("美食名称"); //新增 mjjFoodModelMapper.insert(mjjFoodModel); //写个BUG,使程序出现错误 int a = 4/0; }
启动项目,访问localhost:8081/food/save测试,接口依然报错,但是我们的数据没有添加进去,遇到异常数据进行了回滚。数据还是保持原样。
属性用法
readOnly:读写或只读事务,true代表只读,false代表读写,默认读写false,例如@Transactional(readOnly = true)。
rollbackFor:解决了只有运行时异常和Error才回滚的问题,用于指定回滚的异常类型。例如@Transactional(rollbackFor = Exception.class),用的Exception顶级异常,包含了所有的子类,所有的异常都可以回滚。
noRollbackFor:意思是遇到异常不进行回滚,例如@Transactional(rollbackFor = Exception.class),用的Exception顶级异常,包含了所有的子类,所有的异常都不回滚。和上一个正好相反。
propagation:事务的传播行为,默认的是REQUIRED
- REQUIRED 支持当前已经存在的事务,如果还没有事务,就创建一个新事务。
- MANDATORY 支持当前已经存在的事务,如果还没有事务,就抛出一个异常。
- NESTED 在当前事务中创建一个嵌套事务,如果还没有事务,那么就简单地创建一个新事务。
- REQUIRES_NEW 挂起当前事务,创建一个新事务,如果还没有事务,就简单地创建一个新事务。
- NEVER 强制要求不在事务中运行,如果当前存在一个事务,则抛出异常。
- NOT_SUPPORTED 强制不在事务中运行,如果当前存在一个事务,则挂起该事务。
- SUPPORTS 支持当前事务,如果没有事务那么就不在事务中运行。
timeout:事务的超时设置,如果超过该时间限制但事务还没有完成,则自动回滚事务。默认没有时间限制。
isolation:事务的隔离级别,使用默认的即可