Spring事务管理A方法内部调用B方法的回滚问题(springboot事务管理)
springboot事务管理写在前面之前遇到一些自以为是的面试官问了一大推关于A方法内部调用B方法的事务问题,换着法的问,AB方法分别设置不同的事务传播属性时,如果AB方法中某个方法出错了,AB方法数据会怎样回滚的问题?首先这种问题很不负责任,前提条件不是很清晰,问题没说清楚,容易成了绕口令游戏:1、A和B方法有没有对异常进行try catch?2、AB方法可设置的事务传播属性组合那么多,怎么能口
写在前面
之前遇到一些自以为是的面试官问了一大推关于A方法内部调用B方法的事务问题,换着法的问,AB方法分别设置不同的事务传播属性时,如果AB方法中某个方法出错了,AB方法数据会怎样回滚的问题?
首先这种问题很不负责任,前提条件不是很清晰,问题没说清楚,容易成了绕口令游戏:
1、A和B方法有没有对异常进行try catch ?
2、AB方法可设置的事务传播属性组合那么多,怎么能口头上给你一一列举?
3、报错的位置是在数据库操作方法之前还是之后?
4、AB方法是否是来自同一个类的两个方法,还是来自两个不同类的方法?
5、事务的超时时间怎么设置的?
这样的问题,前提条件没说清楚,如果把注意力放在什么AB方法,异常位置的判断上,就跳进了坑里去
其实决定事务是否回滚的本质很简单,不需要考虑什么AB方法:
事务超时时间内,方法所属事务内是否抛出可以触发回滚的异常,有则回滚。没有就不回滚
如果你认同我的观点,后面的内容就没必要继续看了
1、spring中事务控制API介绍
PlatformTransactionManager接口
它是一个接口,是spring的事务管理器核心接口,spring并不实现具体的事务,而是负责包装底层事务,提供规范。应用底层支持什么样的事务策略,spring就支持什么样的事务策略。该接口提供了我们操作事务的常用方法。如下:
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
void commit(TransactionStatus var1) throws TransactionException;
void rollback(TransactionStatus var1) throws TransactionException;
我们在开发中都是直接使用它的实现类,有如下常用实现类:
TransactionDefinition接口()
它是一个接口,是定义事务的信息对象,提供了如下常用的方法:
1、事务隔离级别
2、事务隔离级别
3、事务超时时间
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0; //事务传播属性
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1; //事务隔离级别
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
int TIMEOUT_DEFAULT = -1; //事务超时时间
default int getPropagationBehavior() {
return 0;
}
default int getIsolationLevel() {
return -1;
}
default int getTimeout() {
return -1;
}
default boolean isReadOnly() {
return false;
}
@Nullable
default String getName() {
return null;
}
static TransactionDefinition withDefaults() {
return StaticTransactionDefinition.INSTANCE;
}
}
事务传播属性
propagation | 说明 |
---|---|
PROPAGATION_REQUIRED | 支持当前事务,如果当前没有事务,则新建一个事务执行 |
PROPAGATION_SUPPORTS | 支持当前事务,如果没有当前事务,则以非事务的方式执行 |
PROPAGATION_MANDATORY | 支持当前事务,如果当前没有事务,则抛出异常 |
PROPAGATION_REQUIRES_NEW | 创建一个新的事务,如果当前已经有事务了,则将当前事务挂起 |
PROPAGATION_NOT_SUPPORTED | 不支持当前事务,而且总是以非事务方式执行 |
PROPAGATION_NEVER | 不支持当前事务,如果存在事务,则抛出异常 |
PROPAGATION_NESTED | 如果当前事务存在,则在嵌套事务中执行,否则行为类似于PROPAGATION_REQUIRED。 EJB中没有类似的功能。 |
默认是PROPAGATION_REQUIRED的事务传播。
事务隔离级别
说明:事务隔离级别,反应了事务在并发访问时的处理态度。
ISOLATION_DEFAULT:
默认级别,归属于下列某一种隔离级别。在项目中使用默认值即可。
ISOLATION_READ_UNCOMMITTED:
可以读取其他事务未提交的数据(脏读)
ISOLATION_READ_COMMITTED:
只读取其他事务已经提交的数据,解决脏读的问题。有可能两次读取不一致的问题,不可重复读(oracle数据库默认级别)
ISOLATION_REPEATABLE_READ:
是否读取其他事务提交修改后的数据,解决不可重复读的问题。保证两次读取一致,可重复读(mysql数据库默认级别)
**ISOLATION_SERIALIZABLE:**是否读取其他事务添加后的数据,解决幻影读的问题
细节:
1.事务级别从低到高:脏读->不可重复读->可重复读->解决幻读
2.事务级别越高,数据越安全,消耗的资源越多,数据库操作性能越低
3.在企业项目中,使用哪一种级别的事务,需要根据业务需求来确定
事务超时时间
以秒为单位进行设置。如果设置为-1(默认值),表示没有超时限制。在企业项目中使用默认值即可。
2、决定事务回滚的本质
1、不是一个方法就是一个事务,事务和方法不是对等关系。一个事务可以管理多个方法的数据库操作
2、如何判断多个方法的数据库操作被同一个事务管理,需要看事务的传播属性
3、事务超时时间内,方法所属事务内是否抛出可以触发回滚的异常,有则回滚。没有就不回滚
测试事务回滚的本质
注意:本文测试代码
实体类–成本表和收入表
初始值如图所示:
/**
* 成本实体类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "tb_cost")
@ToString(callSuper = true)
public class Cost {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "cost")
private BigDecimal cost;
}
/**
* 收入实体类
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
@Entity
@Table(name = "tb_revenue")
@ToString(callSuper = true)
public class Revenue {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "revenue")
private BigDecimal revenue;
}
Dao数据库访问类
@Repository
public interface RevenueDao extends JpaRepository<Revenue, Integer> {
}
@Repository
public interface CostDao extends JpaRepository<Cost, Integer> {
}
service类
RevenueService的addRevenue内部调用costService.addCost方法
@Service
public class RevenueService {
@Resource
private RevenueDao revenueDao;
@Resource
private PlatformTransactionManager transactionManager;
@Resource
private CostService costService;
/**
*
* @param id 查询的id
* @param addNum 增加的数字
* @param needRevenueServiceThrowExp 是否需要在RevenueService抛出异常
* @param needCostServiceThrowExp 是否需要在CostService抛出异常
* @param REVENUE_PROPAGATION RevenueService的事务传播属性
* @param COST_PROPAGATION CostService的事务传播属性
*/
public void addRevenue(Integer id, BigDecimal addNum, Boolean needRevenueServiceThrowExp, Boolean needCostServiceThrowExp, int REVENUE_PROPAGATION, int COST_PROPAGATION) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(REVENUE_PROPAGATION);
transactionTemplate.execute(status -> {
Revenue revenue = revenueDao.findById(id).get();
revenue.setRevenue(revenue.getRevenue().add(addNum));
revenueDao.save(revenue);
costService.addCost(id, addNum,needCostServiceThrowExp, COST_PROPAGATION);
if (needRevenueServiceThrowExp) {
throw new RuntimeException("RevenueService手动报错");
}
return true;
});
}
}
CostService类
@Service
public class CostService {
@Resource
private CostDao costDao;
@Resource
private PlatformTransactionManager transactionManager;
@Async
public void addCost(Integer id, BigDecimal addNum, Boolean needThrowExp, int COST_PROPAGATION) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(COST_PROPAGATION);
transactionTemplate.execute(status -> {
Cost cost = costDao.findById(id).get();
cost.setCost(cost.getCost().add(addNum));
costDao.save(cost);
if (needThrowExp) {
throw new RuntimeException("CostService手动报错");
}
return true;
});
}
}
论证1:不是一个方法就是一个事务,事务和方法不是对等关系。一个事务可以管理多个方法的数据库操作
测试方法:
@Test
void contextLoads() {
BigDecimal addNum = BigDecimal.ONE;
revenueService.addRevenue(1, addNum, true,false, TransactionDefinition.PROPAGATION_REQUIRED,TransactionDefinition.PROPAGATION_REQUIRED);
}
costService.addCost和revenueService.addRevenue被同一个事务管理。即时costService.addCost运行正常,但revenueService.addRevenue报错时,一样会回滚
论证2、如何判断多个方法的数据库操作被同一个事务管理,需要看事务的传播属性
测试方法
@Test
void contextLoads() {
BigDecimal addNum = BigDecimal.ONE;
revenueService.addRevenue(1, addNum, true,false, TransactionDefinition.PROPAGATION_REQUIRED,TransactionDefinition.PROPAGATION_REQUIRES_NEW);
}
costService.addCost和revenueService.addRevenuede的事务传播属性不一样。costService.addCost是PROPAGATION_REQUIRES_NEW,新起一个事务,不会报错就提交事务成功。revenueService.addRevenue报错时,会回滚。因为他们被不同的事务管理
论证3、方法所属事务内是否出现触发回滚的异常,有则回滚。没有就不回滚
测试方法
@Test
void contextLoads() {
BigDecimal addNum = BigDecimal.ONE;
revenueService.addRevenue(1, addNum, true,true, TransactionDefinition.PROPAGATION_REQUIRED,TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
}
同时结合论证2的例子:
costService.addCost是PROPAGATION_REQUIRES_NEW,新起一个事务,不会报错就提交事务成功。
@Test
void contextLoads() {
BigDecimal addNum = BigDecimal.ONE;
revenueService.addRevenue(1, addNum, true,false, TransactionDefinition.PROPAGATION_REQUIRED,TransactionDefinition.PROPAGATION_REQUIRES_NEW);
}
更多推荐
所有评论(0)