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

Spring事务和事务传播机制

一、概念介绍

1.什么是事务

事务是一组操作的集合,是一组不可分割的操作。这组操作要么同时成功,要么同时失败。

2.事务的操作

事务的操作主要有三步:

1.开启事务,start transaction。

2.提交事务,commit。

3.回滚事务,rollback。

二、Spring中的事务实现

Spring中的事务操作分为两类:

1.编程式事务。(手动写代码操作事务)

2.声明式事务。(利用注解自动开启和提交事务)

事务实现:

1.创建数据库

首先创建数据库trans_test,然后创建用户表和操作表

DROP TABLE IF EXISTS user_info;
CREATE TABLE user_info (`id` INT NOT NULL AUTO_INCREMENT,`user_name` VARCHAR (128) NOT NULL,`password` VARCHAR (128) NOT NULL,`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now() ON UPDATE now(),PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARACTER
SET = utf8mb4 COMMENT = '⽤⼾表';
-- 操作⽇志表
DROP TABLE IF EXISTS log_info;
CREATE TABLE log_info (`id` INT PRIMARY KEY auto_increment,`user_name` VARCHAR ( 128 ) NOT NULL,`op` VARCHAR ( 256 ) NOT NULL,`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now() ON UPDATE now() 
) DEFAULT charset 'utf8mb4';

2.项目创建

创建项目,引入依赖。(略)

3.创建实体类

@Data
public class UserInfo {private Integer id;private String userName;private String password;private Date createTime;private Date updateTime;
}
@Data
public class LogInfo {private Integer id;private String userName;private String op;private Data createTime;private Data updateTime;
}

4.创建Mapper

@Mapper
public interface LogInfoMapper {@Insert("insert into log_info(user_name,op) values (#{name},{#op})")Integer insert(String userName, String op);
}
@Mapper
public interface UserInfoMapper {@Insert("insert into user_info(user_name,password) values(#{name},#{password})")Integer insert(String username, String password);
}

5.创建Service类

@Slf4j
@Service
public class LogInfoService {@Autowiredprivate LogInfoMapper logInfoMapper;public void insertLog(String userName,String op) {logInfoMapper.insert(userName,"用户注册");}
}
@Slf4j
@Service
public class UserService {@Autowiredprivate UserInfoMapper userInfoMapper;public void registryUser(String username, String password) {userInfoMapper.insert(username, password);}
}

6.创建Controller类

@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/registry")public String registry(String userName, String password) {userService.registryUser(userName, password);return "注册成功";}
}

6.创建Controller类

@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/registry")public String registry(String userName, String password) {userService.registryUser(userName, password);return "注册成功";}
}

实现声明式事务@Transactional

1.先添加依赖

<dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId>
</dependency>

2.在需要事务的方法上添加@Transactional注解,在进入方法时自动开启事务,方法执行完自动提交事务,如果发生了没有处理的异常会自动回滚事务。

例如:在registry上添加

@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/registry")public String registry(String userName, String password) {userService.registryUser(userName, password);return "注册成功";}
}

启动后正常插入数据:

修改程序,使之出现异常

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/registry")public String registry(String userName, String password) {userService.registryUser(userName, password);log.info("数据插入成功");int a = 10 / 0;return "注册成功";}
}

运行后发现虽然显示成功,但是数据库并没有新增数据。

@Transactional的作用:

可以用来修饰方法或类:

修饰方法时:只有修饰public方法时才生效;

修饰类时:对@Transactional修饰的类中所以public方法都起效。

注意如果异常没有被捕获,则进行事务异常回滚;

如果异常被捕获了,则方法被认为是正常执行,依然会提交事务。

修改代码,对异常进行捕获:

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/registry")public String registry(String userName, String password) {userService.registryUser(userName, password);log.info("添加数据成功");try {int a = 10 / 0;}catch (Exception e) {e.printStackTrace();}return "注册成功";}
}

这是成功添加数据:

但是有时我们希望在捕获到异常后仍然进行事务回滚,可以使用以下方式:

1.捕获异常后再使用throw重新抛出异常:

try {int a = 10 / 0;}catch (Exception e) {throw e;}

2.手动回滚事务

使用TransactionAspectSupport.currentTransactionStatus()得到当前的事务,并使用setRollbackOnly设置setRollbackOnly

 log.info("添加数据成功");try {int a = 10 / 0;}catch (Exception e) {TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}

三、@Transactional详解

主要了解@Transactional注解中三个常见的属性:

1.rollbackFor:异常回滚属性,指定能够触发事务回滚的异常类型,可以指定多个异常类型

2.Isolation:事务的隔离级别,默认值为Isolation.DEFAULT

3.propagation:事务的传播机制,默认值为Propagation.REQUIRED

1.rollbackFor

@Transactional默认只在遇到异常和error时才会回滚,非运行时异常不回滚,即Exception的子类,除了RuntimeException及其子类。

在上面的案例中抛出的异常是Exception,如果改为IOException,就如下代码:

 @Transactional@RequestMapping("/t1")public String t1(String userName, String password) throws IOException {userService.registryUser(userName, password);log.info("用户信息插入成功");if(true){throw new IOException();}return "t1";}

测试代码:

可以发现程序虽然抛出了异常,但是事务依然进行了提交。

如果希望在IOException的情况下仍然可以进行事务回滚,可以通过@Transactional注解中的rollbackFor属性,通过rollbackFor这个属性指定出现何种异常类型时进行事务回滚。

@Transactional(rollbackFor = Exception.class)   

这样设置的话。Exception下面的子类就都会报错了。

总结:在Spring的事务管理中,默认只有遇到异常RuntimeException和Error时才会回滚;

            如果需要指定回滚的异常类型,可以通过rollbackFor属性来指定。

2.Isolation

意为事务的隔离级别,首先了解一下Mysql事务的隔离级别:

1.读未提交

也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。

2.读提交

读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据。

3.可重复读

事务不会读到其他事务对已有数据的修改,即使其他事务已提交,也就可以确保同一事务多次查询的结果一致,但是其他事务新插入的数据是可以感知到的,这也就会引发幻读问题。可重复读,是Mysql的默认事务隔离级别

4.串行化

序列化,事务最高隔离级别,它会强制事务排序,使之不会发生冲突,从而解决了脏读,不可重复读和幻读问题,但因为执行效率低,所以使用场景并不多。

Spring事务的隔离级别

1.Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。

2.Isolation.READ_UNCOMMITTED:读未提交,对应SQL标准读未提交。

3.Isolation.READ_COMMITTED:读已提交,对应sql的读已提交。

4.Isolation.REPEATABLE_READ:可重复读,对应sql的可重复读。

5.Isolation.SERIALIZABLE:串行化,对应sql中的串行化。

查看源码:

Spring中的事务隔离级别可以通过@Transactional中的isolation属性进行设置。

  @Transactional(isolation = Isolation.READ_COMMITTED)

3.propagation

propagation译为事务传播机制

所谓事务传播机制就是:多个事务方法存在调用关系时,事务是如何在这些方法间传播的。

解决的问题是一个事务在多个节点(方法)中传递的问题。

Spring的事务传播机制有以下7种:

1.Propagation.REQUIRED:默认的事务传播级别,如果存在当前存在事务,则加入该事务,如果当前没有事务,则创建一个新的事务。

2.Propagation.SUPPORTS:如果当前存在事务,则加入该事务,如果没有事务,则以非事务的方式进行。

3.Propagation.MANDATORY:强制性,如果存在事务,则加入该事务,如果没有事务,则抛出异常。

4.Propagation.REQUIRES_NEW:创建一个新的事务,如果当前存在一个事务,则将当前的事务挂起。

5.Propagation.NOT_SUPPORTED:以非事务的方式运行,如果存在事务,则将事务挂起。

6.Propagation.NEVER:以非事务方式运行,如果存在事务,则抛出异常。

7.Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行,如果当前没有事务,则等价于Propagation.REQUIRED。

对于这些事务传播机制,重点理解REQUIRED和REQUIRES_NEW。

.Propagation.REQUIRED的实例:

  将Controller和UserService和LogInfoService上都加上

@Transactional(propagation = Propagation.REQUIRED)
 @Autowiredprivate UserService userService;@Autowiredprivate LogInfoService logInfoService;@Transactional(propagation = Propagation.REQUIRED)@RequestMapping("/t1")public String t1(String userName, String password) {userService.registryUser(userName, password);logInfoService.insertLog(userName,"用户注册");return "t1";}
@Slf4j
@Service
public class LogInfoService {@Autowiredprivate LogInfoMapper logInfoMapper;@Transactional(propagation = Propagation.REQUIRED)public void insertLog(String userName,String op) {logInfoMapper.insert(userName,"用户注册");}
}
@Slf4j
@Service
public class UserService {@Autowiredprivate UserInfoMapper userInfoMapper;@Transactional(propagation = Propagation.REQUIRED)public void registryUser(String userName, String password) {userInfoMapper.insert(userName, password);}
}

之后运行代码;

运行之后,发现并没有插入任何数据;

描述流程:

1.t1方法开始事务

2.UserService插入一条数据(执行成功,而且和t1使用同一个事务)。

3.LogInfoService也尝试插入一条数据,但是由于出现异常而且没有捕获,所以执行失败(和t1使用同一个事务)

4.由于步骤3出现异常,所以事务回滚,步骤2和3使用一个事务,所以步骤2的数据也回滚了。

REQUIRES_NEW示例:

首先将UserService和LogInfoService中的事务传播机制改为REQUIRES_NEW

@Slf4j
@Service
public class LogInfoService {@Autowiredprivate LogInfoMapper logInfoMapper;@Transactional(propagation = Propagation.REQUIRES_NEW)public void insertLog(String userName,String op) {int a = 10 / 0;logInfoMapper.insert(userName,"用户注册");}
}
@Slf4j
@Service
public class UserService {@Autowiredprivate UserInfoMapper userInfoMapper;@Transactional(propagation = Propagation.REQUIRES_NEW)public void registryUser(String userName, String password) {userInfoMapper.insert(userName, password);}
}

运行代码,发现用户数据插入成功,但是日志表插入失败,可以看出LogInfoService方法中的事务不影响User Service中的事务。

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

相关文章:

  • C语言| 递归和循环的优缺点
  • 塔能水泵节能方案:精准驱动工厂能耗优化
  • 展锐Android13禁止用户使用超级省电
  • 新一代Python专业编译器Nuitka简介
  • ROS2:自定义接口文件(无废话)
  • 多模态理论知识
  • 二叉树与堆排序(概念|遍历|实现)
  • python酒店在线预定管理系统-酒店客房管理系统-快捷酒店入住系统
  • 【Linux系统】vim编辑器的使用
  • FoMo 数据集是一个专注于机器人在季节性积雪变化环境中的导航数据集,记录了不同季节(无雪、浅雪、深雪)下的传感器数据和轨迹信息。
  • C语言编程--递归程序--求数组的最大元素值
  • 油气地震资料信号处理中的NMO(正常时差校正)
  • 【网络篇】传输层TCP协议的确认应答,超时重传机制
  • IT咨询——企业数据资产怎样评估
  • 满分PPT | 基于数据运营的新型智慧城市实践与思考智慧城市数据中台解决方案智能建筑与智慧城市建设方案
  • 基于nacos实现动态线程池设计与实践:告别固定配置,拥抱弹性调度
  • LabVIEW与 IMAQ Vision 机器视觉应用
  • C++类与对象基础续
  • 15.命令模式:思考与解读
  • 毫米波雷达原理(最通俗的解释)
  • MATLAB程序演示与编程思路,相对导航,四个小车的形式,使用集中式扩展卡尔曼滤波(fullyCN-EKF)
  • go 编译报错:build constraints exclude all Go files
  • Python使用爬虫ip抓取热点新闻
  • autojspro怎么免费用
  • 【原创分享】魔音变声器内含超多语音包实时变声
  • C#中从本地(两个路径文件夹)中实时拿图显示到窗口中并接收(两个tcp发送的信号)转为字符串显示在窗体中实现检测可视化
  • 【Wandb】搜索框仅支持正则匹配,不接受“tags:“前缀查询
  • 知乎前端面试题及参考答案
  • 【计算机网络】TCP为什么可靠?解决了哪些问题?
  • 数字文明时代开源技术驱动的商业范式重构:基于开源AI大模型、AI智能名片与S2B2C商城小程序源码的协同创新研究