1、在常见的事务管理框架中,有以下几种事务传递的方式:
(1)REQUIRED:默认的传播行为。如果方法A在事务中被调用,并且方法A调用了方法B,则方法B将会加入到同一个事务中。如果方法A没有事务上下文,则会创建一个新的事务。
(2)REQUIRES_NEW:无论方法A是否在事务中被调用,方法B都会创建一个新的事务,并在方法B执行完毕后提交该事务。如果方法A已经存在事务,那么方法A的事务将被挂起,直到方法B的事务完成。
(3)SUPPORTS:如果方法A在事务中被调用,则方法B将加入到同一个事务中。如果方法A没有事务上下文,则方法B将以非事务方式执行。
(4)NOT_SUPPORTED:方法B将以非事务方式执行,即使方法A在事务中被调用。
(5)MANDATORY:如果方法A在事务中被调用,则方法B将加入到同一个事务中。如果方法A没有事务上下文,则会抛出异常。
(6)NEVER:如果方法A在事务中被调用,则会抛出异常。
(7)NESTED:如果方法A在事务中被调用,并且方法A调用了方法B,则方法B将在一个嵌套事务中执行。嵌套事务是外部事务的一部分,可以独立提交或回滚,但只有当外部事务成功提交时,嵌套事务才会被提交。
这些传播行为可以根据具体的需求来选择,以实现不同的事务传递行为。需要根据具体的业务场景和需求来决定使用哪种事务传递方式。
2、以下是代码案例
(1)对应SQL
CREATE TABLE `rb_order` (
`id` varchar(100) NOT NULL COMMENT '主键',
`order_name` varchar(100) DEFAULT NULL COMMENT '订单名称',
`order_price` varchar(100) DEFAULT NULL COMMENT '订单价格',
`tr_introduce` varchar(100) DEFAULT NULL COMMENT '事物描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `rb_user` (
`id` varchar(100) NOT NULL,
`name` varchar(100) DEFAULT NULL,
`password` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
(2)对应entity实体
@Data
@TableName("rb_order")
public class RbOrder {
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String orderName;
private String orderPrice;
private String trIntroduce;
}
@Data
@TableName("rb_user")
public class RbUser {
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String name;
private Integer password;
}
(3)对应mapper文件
@Mapper
public interface RbOrderMapper extends BaseMapper {
}
@Mapper
public interface RbUserMapper extends BaseMapper {
}
(4)对应service文件:
public interface RbOrderService extends IService {
}
public interface RbUserService extends IService {
}
(5)对应serviceImpl文件
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class RbOrderServiceImpl extends ServiceImpl implements RbOrderService {
}
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class RbUserServiceImpl extends ServiceImpl implements RbUserService {
}
(6)对应service biz业务类
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class TransactionalBizService {
private final RbUserService rbUserService;
private final RbOrderService rbOrderService;
/**
* 事务操作 - 默认的传播行为。如果方法A在事务中被调用,并且方法A调用了方法B,则方法B将会加入到同一个事务中。如果方法A没有事务上下文,则会创建一个新的事务
* 现象 - RbUser数据回滚,RbOrder数据回滚
*
* @return void
* @Author 俞春旺
* @Date 上午 11:48:14 2023年6月19日 0019
**/
@Transactional(rollbackFor = Exception.class)
public void transactional1() {
// 构建 - 事务保存对象
RbUser transactional1 = this.convertUser("transactional1");
// 操作 - 保存用户数据
boolean save = rbUserService.save(transactional1);
Assert.isTrue(save, "保存失败");
// 操作 - 新增订单数据,方法带入:@Transactional(rollbackFor = Exception.class)
SpringUtil.getBean(TransactionalBizService.class).saveOrderTransactionalRequired();
// 代码模拟操作失败
Assert.isTrue(false, "数据报错");
}
/**
* 事务操作 - 默认的传播行为。如果方法A在事务中被调用,并且方法A调用了方法B,则方法B将会加入到同一个事务中。如果方法A没有事务上下文,则会创建一个新的事务
* 现象 - RbUser数据回滚,RbOrder正常创建数据
*
* @return void
* @Author 俞春旺
* @Date 上午 11:48:14 2023年6月19日 0019
**/
@Transactional(rollbackFor = Exception.class)
public void transactional2() {
// 构建 - 事务保存对象
RbUser transactional2 = this.convertUser("transactional2");
// 操作 - 保存用户数据
boolean save = rbUserService.save(transactional2);
Assert.isTrue(save, "保存失败");
// 操作 - 新增订单数据,方法带入:@Transactional(rollbackFor = Exception.class)
// 操作 - 事物不生效:事务方法被同一个类中的其他方法调用:默认情况下,事务是通过AOP切面来实现的。如果在同一个类中的一个方法调用另一个带有@Transactional注解的方法,事务可能不会生效。这是因为AOP切面是通过代理来实现的,代理对象无法截获同一个类内部方法的调用。为了解决这个问题,可以通过将注解添加到另一个类中的方法来实现事务传播
// this.saveOrderTransactionalRequiredNew();
SpringUtil.getBean(TransactionalBizService.class).saveOrderTransactionalRequiredNew();
// 代码模拟操作失败
Assert.isTrue(false, "数据报错");
}
/**
* 事务操作 - 如果方法A在事务中被调用,则方法B将加入到同一个事务中。如果方法A没有事务上下文,则方法B将以非事务方式执行。
* 现象 - RbUser数据回滚,RbOrder数据回滚
*
* @return void
* @Author 俞春旺
* @Date 上午 11:48:14 2023年6月19日 0019
**/
@Transactional(rollbackFor = Exception.class)
public void transactional3() {
// 构建 - 事务保存对象
RbUser transactional3 = this.convertUser("transactional3");
// 操作 - 保存用户数据
boolean save = rbUserService.save(transactional3);
Assert.isTrue(save, "保存失败");
// 操作 - 新增订单数据,方法带入:@Transactional(rollbackFor = Exception.class)
SpringUtil.getBean(TransactionalBizService.class).saveOrderTransactionalSupports();
}
/**
* 事务操作 - 如果方法A在事务中被调用,则方法B将加入到同一个事务中。如果方法A没有事务上下文,则方法B将以非事务方式执行。
* 现象 - RbUser正常创建,RbOrder正常创建
*
* @return void
* @Author 俞春旺
* @Date 上午 11:48:14 2023年6月19日 0019
**/
public void transactional3_1() {
// 构建 - 事务保存对象
RbUser transactional3 = this.convertUser("transactional3");
// 操作 - 保存用户数据
boolean save = rbUserService.save(transactional3);
Assert.isTrue(save, "保存失败");
// 操作 - 新增订单数据,方法带入:@Transactional(rollbackFor = Exception.class)
SpringUtil.getBean(TransactionalBizService.class).saveOrderTransactionalSupports();
}
/**
* 事务操作 - 方法B将以非事务方式执行,即使方法A在事务中被调用。
* 现象 - RbUser数据回滚,RbOrder正常插入
*
* @return void
* @Author 俞春旺
* @Date 上午 11:48:14 2023年6月19日 0019
**/
@Transactional(rollbackFor = Exception.class)
public void transactional4() {
// 构建 - 事务保存对象
RbUser transactional4 = this.convertUser("transactional4");
// 操作 - 保存用户数据
boolean save = rbUserService.save(transactional4);
Assert.isTrue(save, "保存失败");
// 操作 - 新增订单数据,方法带入:@Transactional(rollbackFor = Exception.class)
SpringUtil.getBean(TransactionalBizService.class).saveOrderTransactionalNotSupports();
// 代码模拟操作失败
Assert.isTrue(false, "数据报错");
}
/**
* 事务操作 - 如果方法A在事务中被调用,则方法B将加入到同一个事务中。如果方法A没有事务上下文,则会抛出异常。
* 现象 - RbUser数据回滚,RbOrder数据回滚
*
* @return void
* @Author 俞春旺
* @Date 上午 11:48:14 2023年6月19日 0019
**/
@Transactional(rollbackFor = Exception.class)
public void transactional5() {
// 构建 - 事务保存对象
RbUser transactional5 = this.convertUser("transactional5");
// 操作 - 保存用户数据
boolean save = rbUserService.save(transactional5);
Assert.isTrue(save, "保存失败");
// 操作 - 新增订单数据,方法带入:@Transactional(rollbackFor = Exception.class)
SpringUtil.getBean(TransactionalBizService.class).saveOrderTransactionalMandatory();
// 代码模拟操作失败
Assert.isTrue(false, "数据报错");
}
/**
* 事务操作 - 如果方法A没有事务上下文,则会抛出异常。
* 现象 - RbUser正常创建,RbOrder数据回滚
*
* @return void
* @Author 俞春旺
* @Date 上午 11:48:14 2023年6月19日 0019
**/
public void transactional5_1() {
// 构建 - 事务保存对象
RbUser transactional5_1 = this.convertUser("transactional5_1");
// 操作 - 保存用户数据
boolean save = rbUserService.save(transactional5_1);
Assert.isTrue(save, "保存失败");
// 操作 - 新增订单数据,方法带入:@Transactional(rollbackFor = Exception.class)
SpringUtil.getBean(TransactionalBizService.class).saveOrderTransactionalMandatory();
// 代码模拟操作失败
Assert.isTrue(false, "数据报错");
}
/**
* 事务操作 - 如果方法A在事务中被调用,则会抛出异常。
* 现象 - RbUser正常创建,RbOrder数据回滚
*
* @return void
* @Author 俞春旺
* @Date 上午 11:48:14 2023年6月19日 0019
**/
@Transactional(rollbackFor = Exception.class)
public void transactional6() {
// 构建 - 事务保存对象
RbUser transactional6 = this.convertUser("transactional6");
// 操作 - 保存用户数据
boolean save = rbUserService.save(transactional6);
Assert.isTrue(save, "保存失败");
// 操作 - 新增订单数据,方法带入:@Transactional(rollbackFor = Exception.class)
SpringUtil.getBean(TransactionalBizService.class).saveOrderTransactionalNever();
// 代码模拟操作失败
Assert.isTrue(false, "数据报错");
}
/**
* 事务操作 - 如果方法A在事务中被调用,并且方法A调用了方法B,则方法B将在一个嵌套事务中执行。嵌套事务是外部事务的一部分,可以独立提交或回滚,但只有当外部事务成功提交时,嵌套事务才会被提交。
* 现象 - RbUser正常创建,RbOrder数据回滚
*
* @return void
* @Author 俞春旺
* @Date 上午 11:48:14 2023年6月19日 0019
**/
@Transactional(rollbackFor = Exception.class)
public void transactional7() {
// 构建 - 事务保存对象
RbUser transactional7 = this.convertUser("transactional7");
// 操作 - 保存用户数据
boolean save = rbUserService.save(transactional7);
Assert.isTrue(save, "保存失败");
// 操作 - 新增订单数据,方法带入:@Transactional(rollbackFor = Exception.class)
SpringUtil.getBean(TransactionalBizService.class).saveOrderTransactionalNested();
// 代码模拟操作失败
Assert.isTrue(false, "数据报错");
}
/**
* 事务操作 - 如果方法A在事务中被调用,并且方法A调用了方法B,则方法B将在一个嵌套事务中执行。嵌套事务是外部事务的一部分,可以独立提交或回滚,但只有当外部事务成功提交时,嵌套事务才会被提交。
* 现象 - RbUser正常创建,RbOrder数据回滚
*
* @return void
* @Author 俞春旺
* @Date 上午 11:48:14 2023年6月19日 0019
**/
public void transactional7_1() {
// 构建 - 事务保存对象
RbUser transactional7_1 = this.convertUser("transactional7_1");
// 操作 - 保存用户数据
boolean save = rbUserService.save(transactional7_1);
Assert.isTrue(save, "保存失败");
// 操作 - 新增订单数据,方法带入:@Transactional(rollbackFor = Exception.class)
SpringUtil.getBean(TransactionalBizService.class).saveOrderTransactionalNested();
// 代码模拟操作失败
Assert.isTrue(false, "数据报错");
}
/**
* 转换 - 用户信息
*
* @param name 用户名称
* @return com.rbgt.tr.entity.UserTest
* @Author 俞春旺
* @Date 下午 01:43:15 2023年6月19日 0019
**/
private RbUser convertUser(String name) {
RbUser user = new RbUser();
user.setName(name);
user.setPassword(3306);
return user;
}
/**
* 操作 - 新增订单数据
*
* @param
* @return void
* @Author 俞春旺
* @Date 下午 01:42:22 2023年6月19日 0019
**/
@Transactional(rollbackFor = Exception.class)
public void saveOrderTransactionalRequired() {
// 构建 - 订单数据
RbOrder rbOrder = this.convertOrder("saveOrderTransactionalRequired", "REQUIRED");
// 操作 - 数据逻辑
boolean save = rbOrderService.save(rbOrder);
Assert.isTrue(save, "新增失败");
}
/**
* 操作 - 新增订单数据
* 无论方法A是否在事务中被调用,方法B都会创建一个新的事务,并在方法B执行完毕后提交该事务。如果方法A已经存在事务,那么方法A的事务将被挂起,直到方法B的事务完成
*
* @param
* @return void
* @Author 俞春旺
* @Date 下午 01:42:22 2023年6月19日 0019
**/
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void saveOrderTransactionalRequiredNew() {
// 构建 - 订单数据
RbOrder rbOrder = this.convertOrder("saveOrderTransactionalRequiredNew", "REQUIRES_NEW");
// 操作 - 数据逻辑
boolean save = rbOrderService.save(rbOrder);
Assert.isTrue(save, "新增失败");
}
/**
* 操作 - 新增订单数据
* 如果方法A在事务中被调用,则方法B将加入到同一个事务中。如果方法A没有事务上下文,则方法B将以非事务方式执行。
*
* @param
* @return void
* @Author 俞春旺
* @Date 下午 01:42:22 2023年6月19日 0019
**/
@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
public void saveOrderTransactionalSupports() {
// 构建 - 订单数据
RbOrder rbOrder = this.convertOrder("saveOrderTransactionalSupports", "SUPPORTS");
// 操作 - 数据逻辑
boolean save = rbOrderService.save(rbOrder);
Assert.isTrue(save, "新增失败");
// 代码模拟操作失败
Assert.isTrue(false, "数据报错");
}
/**
* 操作 - 新增订单数据
* 方法B将以非事务方式执行,即使方法A在事务中被调用。
*
* @param
* @return void
* @Author 俞春旺
* @Date 下午 01:42:22 2023年6月19日 0019
**/
@Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class)
public void saveOrderTransactionalNotSupports() {
// 构建 - 订单数据
RbOrder rbOrder = this.convertOrder("saveOrderTransactionalNotSupports", "NOT_SUPPORTED");
// 操作 - 数据逻辑
boolean save = rbOrderService.save(rbOrder);
Assert.isTrue(save, "新增失败");
}
/**
* 操作 - 新增订单数据
* 如果方法A在事务中被调用,则方法B将加入到同一个事务中。如果方法A没有事务上下文,则会抛出异常。
*
* @param
* @return void
* @Author 俞春旺
* @Date 下午 01:42:22 2023年6月19日 0019
**/
@Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class)
public void saveOrderTransactionalMandatory() {
// 构建 - 订单数据
RbOrder rbOrder = this.convertOrder("saveOrderTransactionalMandatory", "MANDATORY");
// 操作 - 数据逻辑
boolean save = rbOrderService.save(rbOrder);
Assert.isTrue(save, "新增失败");
}
/**
* 操作 - 新增订单数据
* 如果方法A在事务中被调用,并且方法A调用了方法B,则方法B将在一个嵌套事务中执行。嵌套事务是外部事务的一部分,可以独立提交或回滚,但只有当外部事务成功提交时,嵌套事务才会被提交。
*
* @param
* @return void
* @Author 俞春旺
* @Date 下午 01:42:22 2023年6月19日 0019
**/
@Transactional(propagation = Propagation.NEVER, rollbackFor = Exception.class)
public void saveOrderTransactionalNever() {
// 构建 - 订单数据
RbOrder rbOrder = this.convertOrder("saveOrderTransactionalNever", "NEVER");
// 操作 - 数据逻辑
boolean save = rbOrderService.save(rbOrder);
Assert.isTrue(save, "新增失败");
}
/**
* 操作 - 新增订单数据
* 如果方法A在事务中被调用,则会抛出异常。
*
* @param
* @return void
* @Author 俞春旺
* @Date 下午 01:42:22 2023年6月19日 0019
**/
@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
public void saveOrderTransactionalNested() {
// 构建 - 订单数据
RbOrder rbOrder = this.convertOrder("saveOrderTransactionalNested", "NESTED");
// 操作 - 数据逻辑
boolean save = rbOrderService.save(rbOrder);
Assert.isTrue(save, "新增失败");
}
/**
* 转换 - 订单数据
*
* @param orderName 订单名称
* @return com.rbgt.tr.entity.RbgtTestOrder
* @Author 俞春旺
* @Date 下午 01:41:00 2023年6月19日 0019
**/
private RbOrder convertOrder(String orderName, String trIntroduce) {
RbOrder rbOrder = new RbOrder();
rbOrder.setOrderName(orderName);
rbOrder.setOrderPrice("3000");
rbOrder.setTrIntroduce(trIntroduce);
return rbOrder;
}
}
(7)对应controller文件
@RestController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class TransactionalController {
private final TransactionalBizService transactionalBizService;
@PutMapping("/transactional1")
public String transactional1() {
transactionalBizService.transactional1();
return "transactional1方法执行完成";
}
@PutMapping("/transactional2")
public String transactional2() {
transactionalBizService.transactional2();
return "transactional2方法执行完成";
}
@PutMapping("/transactional3")
public String transactional3() {
transactionalBizService.transactional3();
return "transactional3方法执行完成";
}
@PutMapping("/transactional3_1")
public String transactional3_1() {
transactionalBizService.transactional3_1();
return "transactional3_1方法执行完成";
}
@PutMapping("/transactional4")
public String transactional4() {
transactionalBizService.transactional4();
return "transactional4方法执行完成";
}
@PutMapping("/transactional5")
public String transactional5() {
transactionalBizService.transactional5();
return "transactional5方法执行完成";
}
@PutMapping("/transactional5_1")
public String transactional5_1() {
transactionalBizService.transactional5_1();
return "transactional5_1方法执行完成";
}
@PutMapping("/transactional6")
public String transactional6() {
transactionalBizService.transactional6();
return "transactional6方法执行完成";
}
@PutMapping("/transactional7")
public String transactional7() {
transactionalBizService.transactional7();
return "transactional7方法执行完成";
}
@PutMapping("/transactional7_1")
public String transactional7_1() {
transactionalBizService.transactional7_1();
return "transactional7方法执行完成";
}
}
(8)对应pom依赖
4.0.0
org.springframework.boot
SpringBoot
1.0.0-SNAPSHOT
org.springframework.boot
SpringBoot-Transactional
1.0.0-SNAPSHOT
8
8
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-web
com.alibaba
druid-spring-boot-starter
1.1.10
mysql
mysql-connector-java
com.alibaba
druid
1.1.0
com.baomidou
mybatis-plus-boot-starter
3.5.1
com.google.guava
guava
28.2-jre
org.springframework.boot
spring-boot-starter-data-redis
org.springframework
spring-test
org.springframework.boot
spring-boot-maven-plugin
(9)对应yml文件
server:
port: 10100 # 配置启动端口号
mybatis:
config-location: classpath:mybatis.cfg.xml # mybatis主配置文件所在路径
type-aliases-package: com.rbgt.tr.entity # 定义所有操作类的别名所在包
mapper-locations: # 所有的mapper映射文件
- classpath:mapper/*.xml
spring: #springboot的配置
datasource: #定义数据源
#127.0.0.1为本机测试的ip,3306是mysql的端口号。serverTimezone是定义时区,照抄就好,mysql高版本需要定义这些东西
#useSSL也是某些高版本mysql需要问有没有用SSL连接
url: jdbc:mysql://127.0.0.1:3306/rbgt?serverTimezone=GMT%2B8&useSSL=FALSE
username: root #数据库用户名,root为管理员
password: 123456 #该数据库用户的密码
# 使用druid数据源
type: com.alibaba.druid.pool.DruidDataSource
redis:
host: 127.0.0.1
port: 6379
timeout: 10000
database: 5
password: yiautos123456
# mybatis-plus相关配置
mybatis-plus:
# xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)
mapper-locations: classpath:mapper/*.xml
# 以下配置均有默认值,可以不设置
global-config:
# 配置逻辑删除全局值(1 表示已删除,0 表示未删除)
logic-delete-value: 1
logic-not-delete-value: 0
db-config:
#主键类型 AUTO:"数据库ID自增" INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
id-type: auto
#字段策略 IGNORED:"忽略判断" NOT_NULL:"非 NULL 判断") NOT_EMPTY:"非空判断"
field-strategy: NOT_EMPTY
#数据库类型
db-type: MYSQL
configuration:
configuration:
# 分页插件
# 需要配合pagehelper使用
page-helper:
helper-dialect: mysql
reasonable: true
# 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射
map-underscore-to-camel-case: true
# 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段
call-setters-on-nulls: true
# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
(10)对应项目截图
3、最后补充@Transactional失效原因
错误配置事务管理器:事务管理器负责管理事务的创建、提交和回滚。如果事务管理器配置错误或未配置,那么事务将不会生效。确保正确配置了适合的事务管理器,例如Spring中的DataSourceTransactionManager。
不是公共方法或在同一类内部调用:@Transactional注解通常应用于公共方法上,以便可以被外部调用触发事务。如果将@Transactional注解应用于非公共方法,或者在同一个类内部的方法之间相互调用,事务可能不会生效。确保@Transactional注解应用于公共方法,并通过外部调用触发事务。
异常被捕获而不是抛出:默认情况下,只有在方法中抛出未捕获的RuntimeException或Error时,事务才会回滚。如果在方法内部捕获了异常并处理了它,事务将继续提交。确保在需要事务回滚的地方适当抛出RuntimeException或Error。
PS:大家少走弯路,走大道,哈哈哈