1. 背景
在实现下单的方法中,首先通过远程调用查询了地址簿信息和服务信息等,然后将这些信息和前端传入的预约时间、服务项等信息封装到order实体类,然后调用Mybatis-Plus提供的ServiceImpl类的save方法,向数据库表中插入数据。
@Slf4j
@Service
public class OrdersCreateServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersCreateService {
@Transactional(rollbackFor = Exception.class)
public OrderResDTO placeOrder(OrderReqDTO OrderReqDTO) {
// 下单前数据准备
Orders orders = new Orders();
//。。。远程调用获取数据,封装到orders中 。。。
// 。。。 前端传入的数据,封装到orders中。。。
//保存订单
boolean save = this.save(orders);
if (!save) {
throw new DbRuntimeException("下单失败");
}
return new OrderResDTO(orders.getId());
}
}
2. 在事务方法中存在远程调用是否有问题?
下单方法中远程调用查询地址簿信息和服务信息,远程调用涉及网络传输,如果网络传输时间过长会增加数据库事务的时长,如果并发高会把数据库的链接消耗殆尽,导致系统不能正常工作。
3. 探索方案
3.1 探索方案
将保存订单的代码移动add方法中,add方法只保存订单,去掉placeOrder方法上的@Transactional注解,在add方法上添加@Transactional注解,优化如下:
@Slf4j
@Service
public class OrdersCreateServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersCreateService {
public OrderResDTO placeOrder(OrderReqDTO OrderReqDTO) {
// 下单前数据准备
Orders orders = new Orders();
//。。。远程调用获取数据,封装到orders中 。。。
// 。。。 前端传入的数据,封装到orders中。。。
//保存订单
this.add(orders);
return new OrderResDTO(orders.getId());
}
@Transactional(rollbackFor = Exception.class)
public void add(Orders orders) {
boolean save = this.save(orders);
if (!save) {
throw new DbRuntimeException("下单失败");
}
}
经过测试发现事务控制失败,当add方法抛出异常数据库事务并没有回滚,订单信息保存成功。
3.2 失败原因
首先要明白Spring进行事务控制是通过代理对象进行的,在调用add方法之前开启事务,方法执行结束提交事务。
失败原因:
非事务方法内部调用事务方法,不是通过代理对象去调用。
也就是说在placeOrder方法中通过this.add()调用add方法,this就是原始对象本身并不是代理对象。
4. 解决方案
在OrdersCreateServiceImpl注入OrdersCreateServiceImpl的代理对象,通过代理对象去调用add方法。
也就是将自己注入(实际会注入代理对象),然后通过这个注入调用add方法。
public class OrdersCreateServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersCreateService {
//将自己的代理对象注入
@Resource
private IOrdersCreateService owner;
...
@Override
public OrderResDTO placeOrder(OrderReqDTO OrderReqDTO) {
...
//保存订单
owner.add(orders);
return new OrderResDTO(orders.getId());
}
5. 总结
将插入数据库的代码封装到单独方法B中,去掉原方法A上的@Transactional注解,在单独的B方法上添加@Transactional注解,并且在原方法A中,修改原来插入数据库的代码为,通过将自己注入的代理对象来调用单独的B方法。