通过实际案例摸清楚Spring事务传播的行为
@
事务传播
- 对于Spring事务传播的七大行为,我们往往还停留在一些概念上,比如下面这张表:
| 定义 | 说明 |
|---|---|
| PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务,则加入到这个事务中。这是最常见的选择。 |
| PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
| PROPAGATION_MANDATORY | 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常。 |
| PROPAGATION_REQUIRED_NEW | 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。 |
| PROPAGATION_NOT_SUPPORTED | 表示该方法不应该运行在事务中。如果当前存在事务,就把当前事务挂起。 |
| PROPAGATION_NEVER | 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常。 |
| PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
- 本文旨在通过实际案例代码进行分析Spring事务传播行为的各种特性。
案例准备
- 构建一个SpringBoot项目,增加以下代码:
- 实体类
/**
* User.java : 用户类
*/
@Entity
public class User implements Serializable {
// 用户id
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 用户名
@NotBlank(message = "用户名称不能为空")
@Column(name="name")
private String name;
// 邮箱
@Column(name="email")
@Pattern(message ="邮箱格式不符", regexp = "^[A-Za-z0-9\\u4e00-\\u9fa5]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$")
private String email;
public User(){}
public User(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
", createTime=" + createTime +
", updateTime=" + updateTime +
'}';
}
}
- DAO接口与实现类
/**
* 用户数据访问层(DAO)接口
*/
public interface UserDAO {
// 查找所有用户
List<User> findAll();
// 根据id查找用户
User findById(Long id) throws SQLException;
// 新增用户
Long addUser(User user) throws SQLException;
// 更新用户
void updateUser(User user);
// 删除用户
void deleteById(Long id);
// 自定义添加通过用户名称查找用户信息
List<User> findByName(String name);
}
/**
* 使用JdbcTemplate模板类实现用户数据访问层
*
*/
@Repository
public class UserDAOImpl implements UserDAO {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public List<User> findAll() {
return jdbcTemplate.query("select id,name,email from user;",
new Object[]{}, new BeanPropertyRowMapper<>(User.class));
}
@Override
public User findById(Long id) {
return jdbcTemplate.queryForObject("select id,name,email from user where id=?;",
new Object[]{id}, new BeanPropertyRowMapper<>(User.class));
}
@Override
public Long addUser(User user) {
return Integer.toUnsignedLong(
jdbcTemplate.update("insert into user(id,name,email) values(?,?,?);"
, user.getId(), user.getName(), user.getEmail()));
}
@Override
public void updateUser(User user) {
jdbcTemplate.update("update user set name=?,email=? where id =?;"
, user.getName(), user.getEmail(), user.getId());
}
@Override
public void deleteById(Long id) {
jdbcTemplate.update("delete from user where id=?", new Object[]{id});
}
@Override
public List<User> findByName(String name) {
return jdbcTemplate.query("select id,name,email from user where name=?;",
new Object[]{name}, new BeanPropertyRowMapper<>(User.class));
}
}
- 测试类
/**
* 事务传播测试案例
*/
public class TransactionalTest {
@Autowired
private UserDAO userDAO;
// 无事务
public void noneTransaction() throws SQLException {
User user1 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user1);
// 增加一个与user1主键相同的用户
User user2 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user2);
}
//....
}
案例解析
1、无事务
- 插入两个id(主键)相同的用户数据。
// 无事务
public void noneTransaction() throws SQLException {
User user1 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user1);
// 增加一个与user1主键相同的用户
User user2 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user2);
}
- 插入第一条数据成功,第二条数据失败

- 由于没有事务控制,数据库表中会存在一条数据:

2、 Propagation.REQUIRED
- 这个是默认的事务传播行为:如果当前没有事务,就新建一个事务,如果已经存在一个事务,则加入到这个事务中。
- 仍然插入两个id(主键)相同的用户数据。
// 事务传播为PROPAGATION_REQUIRED
@Transactional(propagation = Propagation.REQUIRED)
public void requiredTransaction() throws SQLException {
User user1 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user1);
// 增加一个与user1主键相同的用户
User user2 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user2);
}
- 第二条数据插入时报重复主键错误

- 由于启用了事务,提示事务回滚,表中没有插入任何数据

3. Propagation.SUPPORTS
- 支持当前事务,如果当前没有事务,就以非事务方式执行。这里我们做两个测试,首先以原来的代码,即调用外层没有启用事务来运行:
// 事务传播为PROPAGATION_SUPPORTS
// 调用的外层没有事务
@Transactional(propagation = Propagation.SUPPORTS)
public void supportsTransaction() throws SQLException {
User user1 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user1);
// 增加一个与user1主键相同的用户
User user2 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user2);
}
- 第一条插入成功,插入第二条事务时报主键重复错误,由于调用方外层启用事务,表中存留第一条数据。


- 接下来修改代码,用一个已启事务的调用方来调用该测试过程:
// 事务传播为PROPAGATION_SUPPORTS
// 调用方已启用事务
@Transactional
public void callSupportsTransaction() throws SQLException {
supportsTransaction();
}
@Transactional(propagation = Propagation.SUPPORTS)
public void supportsTransaction() throws SQLException {
User user1 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user1);
// 增加一个与user1主键相同的用户
User user2 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user2);
}
- 第一条插入成功,插入第二条事务时报主键重复错误,但由于这次调用方已启用了事务,表中没有插入任何数据。


4. Propagation.MANDATORY
- 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常。
- 我们首先直接运行以下代码
// 事务传播为PROPAGATION_MANDATORY
@Transactional(propagation = Propagation.MANDATORY)
public void mandatoryTransaction() throws SQLException {
User user1 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user1);
}
- 由于调用外层没有启用事务,该段测试代码判断当前事务不存在,则会抛出不存在事务的错误

- 接下来使用调用方的外层启用事务,再调用这段测试代码:
// 事务传播为PROPAGATION_MANDATORY
// 调用方启用事务
@Transactional
public void callMandatoryTransaction() throws SQLException {
User user = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user);
mandatoryTransaction();
}
@Transactional(propagation = Propagation.MANDATORY)
public void mandatoryTransaction() throws SQLException {
User user1 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user1);
}
- 测试程序在插入第二条数据时报主键错误

- 由于调用方启用事务,事务回滚,没有插入任何数据。

5. Propagation.REQUIRED_NEW
表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。
针对这种特性,我们做一个有趣的实验:调用方启用默认事务,并调用事务传播为PROPAGATION_REQUIRES_NEW的程序,并故意造成事务回滚。
// 调用方启用默认事务,并调用事务传播为PROPAGATION_REQUIRES_NEW的程序,在外层故意造成事务回滚
@Transactional
public void callRequiresNewTransaction() throws SQLException {
User user1 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user1);
requiresNewTransaction();
// 增加一个主键重复的用户,故意造成事务回滚
User user2 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user2);
}
// 事务传播为PROPAGATION_REQUIRES_NEW
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void requiresNewTransaction() throws SQLException {
User user = new User(101L, "Jack", "Jack@163.com");
userDAO.addUser(user);
}
- 测试情况如下:在外层事务造成回滚后,表中没有插入任何数据。


- 接下来再改下程序,调用方启用默认事务,并调用事务传播为PROPAGATION_REQUIRES_NEW的程序,但在调用的程序内层故意造成事务回滚。
// 调用方启用默认事务,并调用事务传播为PROPAGATION_REQUIRES_NEW的程序
@Transactional
public void callRequiresNewTransaction() throws SQLException {
User user1 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user1);
// 调用事务传播为PROPAGATION_REQUIRES_NEW的过程
requiresNewTransaction();
User user2 = new User(101L, "Rose", "Rose@163.com");
userDAO.addUser(user2);
}
// 事务传播为PROPAGATION_REQUIRES_NEW
// 内层错误造成事务回滚
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNewTransaction(){
// 增加一个主键重复的用户,故意造成事务回滚
User user2 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user2);
}
- 同样会造成事务回滚,表中无任何数据插入


6. Propagation.NOT_SUPPORTED
- 该方法不应该运行在事务中。如果当前存在事务,就把当前事务挂起。
- 为了测试该特性,我们首先定义另外一个测试服务类,该服务类中定义了事务传播为Propagation.NOT_SUPPORTED的方法
/**
* 测试 Propagation.NOT_SUPPORTED
*/
@Service
public class UserServiceTest {
@Autowired
private UserDAOImpl userDAO;
// 事务传播为Propagation.NOT_SUPPORTED
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void notSupportedTransaction(){
User user2 = new User(101L, "Rose", "Rose@163.com");
userDAO.addUser(user2);
}
}
- 在主测试类启用默认事务,并调用新增服务类中的事务传播为Propagation.NOT_SUPPORTED的方法,并且故意增加重复用户数据,造成主服务的事务回滚:
// 主测试类启用默认事务,并调用Propagation.NOT_SUPPORTED的方法
@Transactional
public void callNotSupportedTransaction() {
User user1 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user1);
// 调用事务传播为Propagation.NOT_SUPPORTED的过程
userServiceTest.notSupportedTransaction();
// 增加重复用户数据
User user2 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user2);
}
- 由于主服务类中启用了事务,在插入第二条重复用户数据时,会报主键冲突,造成事务回滚,两条数据都没有插入;但新增的服务类的方法没有运行在事务中,新增的用户数据会插入表中。


7. Propagation.NEVER
- 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常。
- 按测试Propagation.NOT_SUPPORTED进行改造,主服务类启用默认事务特性,并调用测试服务类Propagation.NEVER的过程
// 调用方启用默认事务,并调用Propagation.NEVER的过程
// 调用方启用默认事务,并调用Propagation.NEVER的过程
@Transactional
public void callNeverTransaction {
User user1 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user1);
// 调用事务传播为Propagation.NEVER的过程
userServiceTest.neverTransaction();
}
// 事务传播为Propagation.NEVER的过程
@Transactional(propagation = Propagation.NEVER)
public void neverTransaction() {
User user2 = new User(101L, "Rose", "Rose@163.com");
userDAO.addUser(user2);
}
- 由于主服务类启用了事务,而测试服务类的Propagation.NEVER不允许运行在事务中,会抛出异常。

8. Propagation.NESTED
- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
- 测试案例如下:主服务类不起任何事务,调用测试服务类Propagation.NESTED 的方法,且该方法中故意制造主键冲突的重复数据
// 调用方不起事务,并调用Propagation.NESTED的过程
public void callNestedTransaction(User user) {
User user1 = new User(100L, "Jack", "Jack@163.com");
userDAO.addUser(user1);
// 调用事务传播为Propagation.NEVER的过程
userServiceTest.nestedTransaction();
}
// 事务传播为Propagation.NESTED
@Transactional(propagation = Propagation.NESTED)
public void nestedTransaction() {
User user2 = new User(101L, "Rose", "Rose@163.com");
userDAO.addUser(user2);
// 插入重复数据,造成主键冲突
User user3 = new User(101L, "Rose", "Rose@163.com");
userDAO.addUser(user3);
}
- 由于主服务类没有启用事务,则第一条数据会插入表中,但测试服务类启用了Propagation.NESTED特性的事务,也即相当于默认事务行为,主键冲突抛出异常后,造成事务回滚,后面增加的两条数据都没有插入表。


注意点
- 需要嵌套测试事务传播特性时应建立两个服务类,尽量不要在同一服务类中调用。
通过实际案例摸清楚Spring事务传播的行为的更多相关文章
- spring事务传播机制实例讲解
http://kingj.iteye.com/blog/1680350 spring事务传播机制实例讲解 博客分类: spring java历险 天温习spring的事务处理机制,总结 ...
- Spring事务传播机制
Spring在TransactionDefinition接口中规定了7种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播,即协调已经有事务标识的方法之间的发生调用时的事务 ...
- Spring事务传播特性的浅析——事务方法嵌套调用的迷茫
Spring事务传播机制回顾 Spring事务一个被讹传很广说法是:一个事务方法不应该调用另一个事务方法,否则将产生两个事务.结果造成开发人员在设计事务方法时束手束脚,生怕一不小心就踩到地雷. 其实这 ...
- Spring事务传播机制和数据库隔离级别
Spring事务传播机制和数据库隔离级别 转载 2010年06月26日 10:52:00 标签: spring / 数据库 / exception / token / transactions / s ...
- spring 事务传播机制
spring 事务 传播机制 描述的 事务方法直接相互调用,父子事物开启,挂起,回滚 等的处理方式. 绿色的 那几个 我认为比较重要. 1 , @Transactional(propagation=P ...
- 事务、事务特性、事务隔离级别、spring事务传播特性
事务.事务特性.事务隔离级别.spring事务传播特性 1.什么是事务: 事务是程序中一系列严密的操作,所有操作执行必须成功完成,否则在每个操作所做的更改将会被撤销,这也是事务的原子性(要么成功, ...
- 什么是事务、事务特性、事务隔离级别、spring事务传播特性
1.什么是事务: 事务是程序中一系列严密的操作,所有操作执行必须成功完成,否则在每个操作所做的更改将会被撤销,这也是事务的原子性(要么成功,要么失败). 2.事务特性: 事务特性分为四个:原子性(At ...
- spring事务传播实现源码分析
转载. https://blog.csdn.net/qpfjalzm123/article/details/83717367 本文只是对spring事务传播实现的流程进行简单的分析,如有不对之处请指出 ...
- Spring事务传播属性介绍(二).mandatory、not_supported、never、supports
Required.Required_New传播属性分析传送门:https://www.cnblogs.com/lvbinbin2yujie/p/10259897.html Nested传播属性分析传送 ...
随机推荐
- 题解 UVA11865 【Stream My Contest】
最小树形图(朱刘算法)\(+\) 二分答案. 由题意得,我们要在一些有向边中选出一些边,使\(0\)号节点能够到达其他节点,使距离之和\(\leqslant cost\),并且使每条边中的带宽的最小值 ...
- javascript中的堆栈、深拷贝和浅拷贝、闭包
堆栈 在javascript中,堆内存是用来存放引用类型的空间环境 而栈内存,是存储基本类型和指定代码的环境 在对象中的属性名具有唯一性,数字属性名=字符串属性名,但是在测试的时候你会发现,好像所有属 ...
- android 官方教程地址和一个中文教程
https://developer.android.google.cn/guide/components/fundamentals http://www.runoob.com/w3cnote/andr ...
- js原声代码 轮播图
js轮播图 html部分:建立div,内嵌img标签,可以设置大小, <!doctype html> <html> <head> <meta charset= ...
- 用xshell连接linux服务器失败 Could not connect to '112.74.73.194' (port 22): Connection failed.
用XSHELL连接linux服务器出现以下错误 Connecting to 42.51.xxx.xxx:22... Connection established. To escape to local ...
- 自述:转职IT ,痛苦一阵子;不转职IT,痛苦一辈子(第一章)
作为一个从后期制作转职过来的Java工程师,我认为我是幸运的,虽然我的本科专业(影视后期)也是火爆行业,不愁工作,但我不后悔进入这个IT坑,毕竟转行,只痛苦一阵子,但是不转行,可能我会痛苦一辈子. 我 ...
- PHP in_array() 函数
实例 在数组中搜索值 "Runoob" ,并输出一些文本: <?php $sites = array("Google", "Runoob&quo ...
- luogu P1452 [USACO03FALL]Beauty Contest G /【模板】旋转卡壳
LINK:旋转卡壳 如题 是一道模板题. 容易想到n^2暴力 当然也能随机化选点 (还真有人过了 考虑旋转卡壳 其实就是对于某个点来说找到其最远的点. 在找的过程中需要借助一下个点的帮助 利用当前点到 ...
- python网络爬虫实战PDF高清完整版免费下载|百度云盘|Python基础教程免费电子书
点击获取提取码:vg1y python网络爬虫实战帮助读者学习Python并开发出符合自己要求的网络爬虫.网络爬虫,又被称为网页蜘蛛,网络机器人,是一种按照一定的规则,自动地抓取互联网信息的程序或者脚 ...
- 当asp.net core偶遇docker一(模型验证和Rabbitmq 二)
上一篇我们说到构建了一个Rabbitmq容器 现在我们说说如何在一个悄悄传输消息到队列 我们现在设计一个Rabbitmq发送消息部分的模块 先设计一个远程发送的接口 public interface ...