Spring事务@Transactional注解原理及其失效的几种场景

目录

Spring 事务管理类型

一、声明式事务

1.1 基于@Transactional注解

1.2@Transactional实现原理

二、事务的隔离级别

如何指定隔离级别

三、事务传播行为

支持当前事务的情况:

不支持当前事务的情况:

其他情况:

如何指定事务传播行为

四、Spring事务回滚规则

五、spring事务失效

六、常见事务失效实用的场景

1.1当事务在调用方法方法上,且表操作也是在该方法中

1.2当事务没有加调用该方法上,而是加在被调用方法上,事务失效

1.3但事务加在调用方法上,被调用方法不加事务

1.4 使用try...catch...捕获被调用方法异常,异常出现在调用方法上,被调用方法不开启事务

1.5,使用try...catch...捕获异常,调用方法开启了事务,异常出现在被调用方法上且被调用方法上开启事务

1.6 使用try...catch...捕获异常,异常出现在被调用方法上 ,被调用方法上不开启事务


Spring 事务管理类型

Spring 事务管理分为编程式和声明式两种。编程式事务编程式事务编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体的逻辑与事务处理解耦。

一、声明式事务

1.1 基于@Transactional注解

@Transactional实质是使用了JDBC的事务来进行事务控制的 ,基于Spring的动态代理的机制

1.2@Transactional实现原理

A、事务开始时,通过AOP机制,生成一个代理connection对象,并将其放入DataSource实例的某个与DataSourceTransactionManager相关的某处容器中。

B、在接下来的整个事务中,客户代码都应该使用该connection连接数据库,执行所有数据库命令[不使用该connection连接数据库执行的数据库命令,在本事务回滚的时候得不到回滚](物理连接connection逻辑上新建一个会话session;DataSource与TransactionManager配置相同的数据源)

C、事务结束时,回滚在第1步骤中得到的代理connection对象上执行的数据库命令,然后关闭该代理connection对象(事务结束后,回滚操作不会对已执行完毕的SQL操作命令起作用)

二、事务的隔离级别

1. DEFAULT(默认):使用底层数据源的默认隔离级别。

2. READ_UNCOMMITTED(读取未提交数据):事务可以读取未提交的更改,可能导致脏读(读取未提交的数据)问题。

3. READ_COMMITTED(读取已提交数据):事务只能读取已提交的更改,可以避免脏读问题,但仍可能出现不可重复读(读取的数据发生变化)和幻读(读取到新增的数据)问题。

4. REPEATABLE_READ(可重复读):事务可以重复读取之前读取的数据,可以避免脏读和不可重复读问题,但仍可能出现幻读问题。行锁

5. SERIALIZABLE(串行化):事务在完全隔离的情况下运行,可以避免脏读、不可重复读和幻读问题,但可能导致性能问题,因为事务必须完全串行化执行。表锁

如何指定隔离级别

//可以在@Transactional注解中指定隔离级别

@Override
@Transactional(isolation = Isolation.DEFAULT)
public ResponseResult saveInfo(UserReq userReq) {
log.info("注解中指定隔离级别");
}

也可以在配置文件中配置全局的默认隔离级别

spring.datasource.hikari.isolation=READ_COMMITTED

三、事务传播行为

spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。

支持当前事务的情况:

1、 REQUIRED(默认属性)
如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。

2、 SUPPORTS
支持当前事务,如果当前没有事务,就以非事务方式执行。

3、MANDATORY
支持当前事务,如果当前没有事务,就抛出异常。

不支持当前事务的情况:

4、REQUIRES_NEW
新建事务,如果当前存在事务,把当前事务挂起。

5、 NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

6、NEVER
以非事务方式执行,如果当前存在事务,则抛出异常。

其他情况:

7、NESTED

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

如何指定事务传播行为


//可以在@Transactional注解中指定事务传播行为

@Override
@Transactional(propagation = Propagation.REQUIRED)
public ResponseResult saveInfo(UserReq userReq) {
log.info("注解中指定隔离级别");
}

四、Spring事务回滚规则

指Spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。

Spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。

默认配置下,Spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。 用Spring事务管理器,由Spring来负责数据库的打开,提交,回滚.默认遇到运行期例外(throw new RuntimeException(“注释”);)会回滚,即遇到不受检查(unchecked)的例外时回滚;而遇到需要捕获的例外(throw new Exception(“注释”);)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .

如果让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class)

五、spring事务失效

A. 如果数据库不支持事务,则失效 B. Service类没有被Spring管理

比如类上没有标注@Service注解,实例没有加载到Spring IOC容器中,就会造成类中方法的事务在Spring中失效;

C. 内部调用

例如:如果同一个类中的两个方法分别为A和B,方法A上没有添加事务注解,方法B上添加了 @Transactional事务注解,方法A调用方法B,则方法B的事务会失效;

D. 使用默认的事务处理方式

E. 事务只能应用于 public 方法,这是由 Spring AOP 的本质决定的。在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常;

F. 方法的事务传播类型不支持事务

如果内部方法的事务传播类型为不支持事务的传播类型,则内部方法的事务在Spring中会失效。

事务传播类型为NOT_SUPPORTED,不支持事务,则相应方法的事务会在Spring中失效。

G.不正确的捕获异常

使用try...catch...捕获异常

六、常见事务失效实用的场景

1.1当事务在调用方法方法上,且表操作也是在该方法中

    @Override
    @Transactional
    public ResponseResult relieveInfo(UserRelieveReq userRelieveReq) {
        
        userMapper.updateTestById(userId);
       
        schoolUserMapper.updateTestById(userId,schoolId);
        //异常
         if(true){
                throw new CustomException("事务回滚");
            }
        return ResponseResult.success("更改成功")
    }

分析总结:

开启了事务后,事务生效,当返回成功之后数据才会插入数据库中,如果在此期间抛了异常,数据库中数据无变动。

1.2当事务没有加调用该方法上,而是加在被调用方法上,事务失效

声明事务加在SchoolService层方法上schoolRelieve(此方法进行更改数据库操作)

userService层relieveInfo方法做逻辑判断,不进行表数据插入或是更改(不加声明事务)

 @Service
@Slf4j
public class SaoDeSengServiceImpl extends ServiceImpl<SaoDeSengMapper, SaoDeSeng> implements SaoDeSengService {

    @Autowired
    private SaoSchoolService schoolService;

    @Override
//    @Transactional
    public ResponseResult relieve(SaoDeSengRelieveReq userRelieveReq) {

        //获取当前用户ID
        Long currentUserId = getCurrentUserId();
        SaoDeSeng user = (SaoDeSeng) selectUserById(currentUserId).getData();
            //获取审批记录
            ResponseResult<SchoolCertificationInfoResp> schoolCertInfoResp = schoolService.schoolCertInfo();
            if (schoolCertInfoResp.getData().getSscId() == null) {
                throw new CustomException("学校认证数据为空,解绑失败");
            }
            schoolService.schoolRelieve(SaoSchool.getSchoolId(), currentUserId, schoolCertInfoResp.getData().getSscId());
            //异常位置
            if(true){
                throw new CustomException("事务回滚");
            }
            return ResponseResult.success("解绑成功");
        }
    }
 @Service
@Slf4j
public class SaoDeSengSchoolServiceImpl extends ServiceImpl<SaoDeSengSchoolMapper, SaoDeSengSchool> implements SaoDeSengSchoolService {

  
    @Autowired
    private SaoDeSengSchoolUserMapper schoolUserMapper;

    @Autowired
    private SaoDeSengSchoolCertificationMapper schoolCertMapper;

   @Autowired
    private OpenMapper openMapper;


    @Override
    @Transactional
    public void schoolRelieve(Long schoolId, Long userId, Long sscId) {
	
        LambdaQueryWrapper<SaoDeSengSchoolUser> idQuery = new LambdaQueryWrapper<>();
        idQuery.eq(SaoDeSengSchoolUser::getSchoolId, schoolId);
        idQuery.eq(SaoDeSengSchoolUser::getUserId, userId);
        schoolUserMapper.delete(idQuery);

        LambdaQueryWrapper<SaoDeSengSchoolCertification> sscIdQuery = new LambdaQueryWrapper<>();
        sscIdQuery.eq(SaoDeSengSchoolCertification::getSscId, sscId);
        schoolCertMapper.delete(sscIdQuery);

      
        LambdaQueryWrapper<Open> userIdQuery = new LambdaQueryWrapper<>();
        userIdQuery.eq(Open::getUserId, userId);
        userIdQuery.eq(Open::getDelFlag, DelFlagEnum.NORMAL.getvalue());
        openMapper.delete(userIdQuery);
    }
}

分析

当事务没有加调用该方法上,而是加在被调用方法上,事务失效,数据不回滚  

1.3但事务加在调用方法上,被调用方法不加事务

具体代码如上1.2,只是事务开启位置做了变更

      // 被调用方法开启声明式事务@Transactional
    @Override
    @Transactional
    public ResponseResult relieveInfo(UserRelieveReq userRelieveReq) {}
//被调用方法上关闭事务
 @Override
//    @Transactional
    public void schoolRelieve(Long schoolId, Long userId, Long sscId) {
//       解绑学校用户表
}

分析

当事务加调用该方法上,不加在被调用方法上,事务生效,数据回滚

1.4 使用try...catch...捕获被调用方法异常,异常出现在调用方法上,被调用方法不开启事务


代码变动位置如截图:

 分析:使用try...catch...捕获异常自己处理,导致异常不被抛出,从而事务失效,数据不回滚

1.5,使用try...catch...捕获异常,调用方法开启了事务,异常出现在被调用方法上且被调用方法上开启事务

分析:调用方法和被调用方法都开启了事务,当异常出现被调用方法里面,事务生效,出现回滚

1.6 使用try...catch...捕获异常,异常出现在被调用方法上 ,被调用方法上不开启事务

分析: 异常出现在被调用方法上 ,且被调用方法上不开启事务,事务失效,数据不回滚

<think>好的,我需要回答用户关于Spring框架中@Transactional注解的用法和功能的问题。首先,我得整理一下现有的参考资料,确保涵盖用户提到的各个要点。用户提到了使用说明、功能介绍、官方文档和最佳实践,所以需要从这几个方面展开。 首先,@Transactional注解的基本功能是声明式事务管理。根据引用[1],Spring事务管理分为编程式和声明式,这里重点在声明式。需要说明注解的作用范围,比如类或方法级别,并提到默认的回滚行为是仅运行时异常。同时,用户可能想知道如何扩展回滚到其他异常,比如检查型异常,这时候可以用rollbackFor属性。 接下来是事务传播机制,引用[2]提到了传播机制,比如REQUIRED、REQUIRES_NEW等,需要简要解释这些传播行为的含义,以及如何通过propagation属性设置。同时,事务失效场景也是一个重点,比如自调用问题、非public方法等,引用[3]指出非public方法会导致事务不生效,这点必须强调。 然后要整理官方文档的要点,引用[2]的链接是Spring官方文档,可以提到推荐用户查阅该链接获取详细信息。此外,最佳实践部分需要结合资料中的内容,比如正确设置rollbackFor、避免自调用、使用public方法等。 还需要注意格式要求,用户提到行内数学表达式用$...$,不过这个问题可能不涉及数学公式,但需要确保其他格式正确,如代码块用```包裹,引用标识自然添加在段落末尾。同时生成相关问题,参考示例中的结构,最后列出相关问题。 现在检查是否有遗漏的信息。比如,引用[1]中的异常继承体系图虽然没有展示,但可以提到运行时异常和检查型异常的区别。事务传播机制的不同类型可能需要更详细的例子,但用户可能只需要基本说明。另外,事务失效的其他场景,如异常被捕获未抛出,或者数据库引擎不支持事务,这些可能需要补充。 最后,确保回答结构清晰,分点说明,符合用户要求的层次结构,并引用正确的来源。同时,生成的三个相关问题需要相关且有价值,帮助用户深入理解。</think>### Spring @Transactional 注解使用说明与功能介绍 #### 1. 核心功能 - **声明式事务管理**:通过注解简化事务配置,替代编程式事务管理[^1]。 - **事务范围控制**:支持类或方法级别的事务声明,方法级注解会覆盖类级配置。 - **默认回滚规则**:仅对运行时异常(`RuntimeException`)及其子类触发回滚,检查型异常(如`IOException`)默认不触发回滚。 - **自定义回滚**:通过`@Transactional(rollbackFor = Exception.class)`扩展回滚范围。 #### 2. 事务传播机制 通过`propagation`属性配置事务边界行为,常见选项: - `REQUIRED`(默认):存在事务则加入,否则新建 - `REQUIRES_NEW`:始终新建事务,挂起当前事务 - `NESTED`:嵌套事务执行 - `SUPPORTS`:存在事务则加入,否则无事务运行 [完整传播行为参考官方文档](https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/tx-propagation.html)[^2] #### 3. 使用规范(官方建议) ```java @Transactional public void updateOrder(Order order) { // 业务逻辑 } ``` - **可见性要求**:必须作用于`public`方法,私有/保护方法注解无效[^3] - **代理机制**:基于AOP代理实现,非Spring管理对象无效 - **自调用失效**:类内部方法互相调用会导致事务失效 #### 4. 最佳实践 1. **明确回滚异常** ```java @Transactional(rollbackFor = {SQLException.class, IOException.class}) ``` 2. **超时设置** ```java @Transactional(timeout = 30) // 单位:秒 ``` 3. **只读事务优化** ```java @Transactional(readOnly = true) ``` 4. **避免嵌套事务复杂度** 谨慎使用`REQUIRES_NEW`,可能引发死锁 #### 5. 典型失效场景 | 场景 | 解决方案 | |---------------------|----------------------------| | 非public方法 | 改用public修饰 | | 自调用 | 通过AopContext代理调用 | | 异常被捕获未抛出 | 在catch块抛出`throw new RuntimeException(e)` | | 数据库引擎不支持事务| 确认使用InnoDB等支持事务的引擎 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

€☞扫地僧☜€

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值