Spring声明式事务在service内部之间调用失效问题

「扫码关注我,面试、各种技术(mysql、zookeeper、微服务、redis、jvm)持续更新中~」
在这里插入图片描述

最近在开发过程中遇到了一个问题,当在Controller中调用Service中A()方法,A方法内部又调用Service中B()方法,由于A方法中只有查询操作所以没有加事务控制,B方法中含有多次修改操作所以增加了@Transactional注解,结果在A方法调用完B方法后,程序报错了,但是B方法中修改操作的数据竟然成功了,我擦~什么鬼,于是开启了探索Spring事务之路,直接上示例。

示例1:A方法无事务,B方法加事务

@RestController
public class Controller{
    
    @Autowired
    private StudentcardService studentcardService;  
  
    @RequestMapping(value = "/test/{id}}", method = RequestMethod.GET)
    public Response queryStudentCard(@PathVariable("id") String id) {
       studentcardService.updateA(id);
    }
}
@Service
public class StudentcardServiceImpl implements StudentcardService {

    @Resource
    private StudentCardMapper studentCardMapper;

    @Override
    public void updateA(String id) {
        //先去调用内部方法B
        this.updateB(id);
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改问题字段
        sc.setQuestion("AAAAA");
        studentCardMapper.update(sc);
    }

    @Override
    @Transactional
    public void updateB(String id) {
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改答案字段
        sc.setAnswer("BBBBB");
        studentCardMapper.update(sc);
        //修改完数据后报错
        double i=1/0;
    }
}

访问后执行结果如下:
在这里插入图片描述
然而事务并没有起作用~接着进行测试
示例2:将A方法加事务,B方法不加事务

@Service
public class StudentcardServiceImpl implements StudentcardService {

    @Resource
    private StudentCardMapper studentCardMapper;

    @Override
    @Transactional
    public void updateA(String id) {
        //先去调用内部方法B
        this.updateB(id);
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改问题字段
        sc.setQuestion("AAAAA");
        studentCardMapper.update(sc);
    }

    @Override
    public void updateB(String id) {
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改答案字段
        sc.setAnswer("BBBBB");
        studentCardMapper.update(sc);
        //修改完数据后报错
        double i=1/0;
    }
}

访问后执行结果如下:
在这里插入图片描述
事务起作用了,都没有修改成功!接下来我们来个增强版,加上try后看下会有怎样的效果

示例3:A方法加事务,B方法没有事务,但是在A调用B方法时用try进行包裹,B方法中有错误

@Service
public class StudentcardServiceImpl implements StudentcardService {

    @Resource
    private StudentCardMapper studentCardMapper;

    @Override
    @Transactional
    public void updateA(String id) {
        //先去调用内部方法B
        try {
            this.updateB(id);
        }catch (Exception e){}
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改问题字段
        sc.setQuestion("AAAAA");
        studentCardMapper.update(sc);
    }

    @Override
    public void updateB(String id) {
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改答案字段
        sc.setAnswer("BBBBB");
        studentCardMapper.update(sc);
        //修改完数据后报错
        double i=1/0;
    }
}

访问后执行结果如下:
在这里插入图片描述
由于报错被try包起来了,所以数据都插入了!那如果将报错信息放到执行完方法B后呢,会怎样呢?

示例4:A方法加事务,B方法没有事务,但是在A调用B方法时用try进行包裹,A方法中有错误

@Service
public class StudentcardServiceImpl implements StudentcardService {

    @Resource
    private StudentCardMapper studentCardMapper;

    @Override
    @Transactional
    public void updateA(String id) {
        //先去调用内部方法B
        try {
            this.updateB(id);
        }catch (Exception e){}
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改问题字段
        sc.setQuestion("AAAAA");
        studentCardMapper.update(sc);
        //修改完数据后报错
        double i=1/0;
    }

    @Override
    public void updateB(String id) {
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改答案字段
        sc.setAnswer("BBBBB");
        studentCardMapper.update(sc);
    }
}

访问后执行结果如下:
在这里插入图片描述
哇塞,数据都没有插入呢!这是因为在事务提交前报错了,事务全部rollback了,下面言归正传,示例1为何不能成功呢?于是查询各种资料终于找到了缘由,并对示例1进行改造

示例5:A方法无事务,B方法加事务

@Service
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
public class StudentcardServiceImpl implements StudentcardService {

    @Resource
    private StudentCardMapper studentCardMapper;

    @Override
    public void updateA(String id) {
        //先去调用内部方法B
        StudentcardService studentcardService = (StudentcardService) AopContext.currentProxy();
        studentcardService.updateB(id);
        //this.updateB(id);
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改问题字段
        sc.setQuestion("AAAAA");
        studentCardMapper.update(sc);
    }

    @Override
    @Transactional
    public void updateB(String id) {
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改答案字段
        sc.setAnswer("BBBBB");
        studentCardMapper.update(sc);
        //修改完数据后报错
        double i=1/0;
    }
}

访问后执行结果如下:
在这里插入图片描述
哈哈,数据没有进行修改,事务起作用了!

下面说下具体对原因:
示例1 事务没有起作用,是由于Spring事务本质是基于AOP代理来实现的,当Controller调用Service的方法A是基于proxy的,所以会切入,但是方法A在调用方法B时,属于类内部调用,即使方法B上加上了@Transactional注解,但没有Spring代理了,所以不受事务控制,自然事务不会生效。
在这里插入图片描述
在这里插入图片描述
示例2 事务可以起作用是由于事务的传播行为导致的,默认事务的传播行为为:PROPAGATION_REQUIRED 。方法A标注了注解@Transactional ,执行的时候传播给方法B,因为方法A开启了事务,线程内的connection的属性autoCommit=false,并且执行到方法B时,事务传播依然是生效的,得到的还是方法A的connection,autoCommit还是为false,所以事务生效;反之,如果方法A没有注解@Transactional 时,是不受事务管理的,autoCommit=true,那么传播给方法B的也为true,执行完自动提交,即使B标注了@Transactional 事务也是不起作用的。
示例5事务又可以起作用的,是由于我们在方法A调用方法B时,先获取到了Service的当前代理,然后用当前代理去调用方法B,所以事务当然会生效了~

顺便补充下事务的传播行为,事务的传播行为是为了解决业务层方法之间相互调用,产生的事务应该如何进行传递的问题。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:如果当前存在一个事务,则该方法运行在一个嵌套的事务中。被嵌套的事务可以从当前事务中单独的提交和回滚。如果当前不存在事务,则开始一个新的事务。各厂商对这种传播行为的支持参差不齐,使用时需注意。

### STM32智能厨房设备中的应用 #### 使用STM32实现温度监控系统 为了确保烹饪过程的安全性和效率,可以利用STM32微控制器创建一个精确的温度监测解决方案。该方案能够实时采集炉灶或其他加热装置表面的温度数据,并通过LCD显示屏向用户提供反馈。当检测到异常高温时,系统会自动触发警报机制并切断电源供应以防止火灾发生[^1]。 ```c // 温度传感器读取函数示例 float readTemperature(void){ float temperature; // 模拟ADC转换获取当前环境温度值 ADC_ChannelConfTypeDef sConfig = {0}; HAL_ADC_Start(&hadc); if (HAL_ADC_PollForConversion(&hadc, 10) == HAL_OK){ uint32_t rawValue = HAL_ADC_GetValue(&hadc); temperature = ((rawValue * 3.3f)/4096)*100; // 将ADC数值转化为摄氏度 } HAL_ADC_Stop(&hadc); return temperature; } ``` #### 开发基于STM32的食物保鲜提醒器 另一个有趣的项目是食物保存期限管理工具。此应用程序允许用户输入食品购买日期以及预计过期时间;之后借助内部RTC模块定时唤醒MCU检查是否有即将到期的商品并向手机发送通知消息提示主人及时处理这些物品以免浪费资源。 ```c void setupFoodExpiryReminder(uint8_t dayOffset){ RTC_TimeTypeDef sTime = {0}; RTC_DateTypeDef DateToUpdate = {0}; /* 设置RTC的时间 */ sTime.Hours = 0; sTime.Minutes = 0; sTime.Seconds = 0; /* 配置指定天数后的日期作为闹钟触发条件 */ DateToUpdate.WeekDay = __HAL_RTC_GET_WEEKDAY(&hrtc)+dayOffset%7; DateToUpdate.Month = (__HAL_RTC_GET_MONTH(&hrtc)+((__HAL_RTC_GET_DATE(&hrtc)+dayOffset)/30))%12?(__HAL_RTC_GET_MONTH(&hrtc)+((__HAL_RTC_GET_DATE(&hrtc)+dayOffset)/30)):12; DateToUpdate.Date = ((__HAL_RTC_GET_DATE(&hrtc)+dayOffset)%30)?((__HAL_RTC_GET_DATE(&hrtc)+dayOffset)%30):30; } /* 定义中断服务程序用于发出警告音调 */ void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc){ Buzzer_ON(); } ``` #### 构建联网型智能烤箱控制系统 对于更高级别的需求来说,则可以通过Wi-Fi模组使传统家用电器具备互联网连接功能——比如一款支持远程控制操作参数设定(如预热温度、烘焙模式选择等)并通过移动APP界面直观展示运行状态信息给用户的智能烤箱产品就是很好的例子之一。这类产品的核心在于良好地集成各种外设接口并与云平台交互完成指令下发与接收工作流程优化。 ```json { "method": "setTargetTemp", "params": { "target_temperature_celsius": 180, "mode": "bake" }, "id": 1 } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值