Spring事务

一、理论

**概念:**事务是一组操作的集合,会把所有的操作作为一个整体一起向系统提交或者撤销操作,要么同时成功、要么同时失败

  • 开启事务:(一组操作开始前,开启事务)start transaction/begin
  • 提交事务:(这组操作全部成功后,提交事务)commit
  • 回滚事务:(中间任何一步出现异常,回滚事务)rollback

Spring 支持编程式事务声明式事务

  • 编程式事务:使用 Transaction Template,需要显示执行事务。(实际开发中不常用)
  • 声明式事务:使用基于 @Transaction 的注解方式,实质是通过 AOP 实现,对方法前后进行拦截,在目标方法开始之前开启一个事务,执行完目标方法之后根据执行情况提交或者回滚事务。(常用)
# 声明式事务
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private UserService userService;
    @Override
    @Transactional
    public void deleteRole(Integer roleId) {
        roleMapper.deleteById(roleId);

        userService.deleteUserByRoleId(roleId);
    }
}

事务管理:

  • 注解: @Transaction
  • 位置: service 层的方法上、类上、接口上。
  • 作用: 将当前方法交给 Spring 进行事务管理,方法执行前,开启事务;成功执行后,提交事务;出现异常,回滚事务;

事务属性:

  • rollback:当出现异常时,回滚事务,默认情况下,只有出现运行时(RuntimeException)异常才回滚事务,出现编译时异常,不回滚。

@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private UserService userService;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteRole(Integer roleId) {
        roleMapper.deleteById(roleId);

        userService.deleteUserByRoleId(roleId);
    }
}

二、Spring 事务中有哪几种事务传播行为

事务传播行为:指当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。

属性值

含义

REQUIRE

默认值,需要事务,有则加入,无则创建新事务

REQUIRES_NEW

需要新事务,无论有无,总是创建新事务

SUPPORTS

支持事务,有则加入,无则在独立的连接中运行SQL

NOT_SUPPORTS

不支持事务,不加入,在独立的连接中运行SQL

MANDATORY

必须有事务,否则抛异常

NEVER

必须没事务,否则抛异常

NESTED

嵌套事务

前两个REQUIRE、REQUIRE_NEW在项目中经常用到,其他不常用。这些事务传播行为在@Transaction注解中设置 propagation属性进行指定。

2.1、REQUIRE

默认的事务传播行为,保证多个嵌套的事务方法在同一个事务内执行,并且同时提交,或者出现异常时,同时回滚。(常用)

@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private UserService userService;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteRole(Integer roleId) {
        roleMapper.deleteById(roleId);

        userService.deleteUserByRoleId(roleId);
    }
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Autowired
    private UserMapper userMapper;
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void deleteUserByRoleId(Integer roleId) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getRole_id,roleId);
        userMapper.delete(queryWrapper);
    }
}

调用链路:开启事务—>执行deleteRole方法 —>执行deleteUserByRoleId方法 —> 提交事务

当方法deleteUserByRoleId执行时抛出了Exception异常,事务是怎么执行的。

开启事务—>执行deleteRole方法 —>执行deleteUserByRoleId方法抛出异常 —> 回滚事务

当事务的传播性设置为REQUIRED,在整个事务的调用链上,任何一个环节抛出的异常都会导致全局回滚。

2.2、REQUIRE_NEW

每次都开启一 个新的事务

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Autowired
    private UserMapper userMapper;
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void deleteUserByRoleId(Integer roleId) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getRole_id,roleId);
        userMapper.delete(queryWrapper);
    }
}

方法deleteUserByRoleId的传播性设置为 REQUIRES_NEW,方法deleteRole仍然是REQUIRED,当deleteRole调用deleteUserByRoleId时,调用链如下:

开启事务1—>执行deleteRole方法—>挂起事务1 —>开启事务2 —>执行deleteUserByRoleId方法 —> 提交事务2 —>提交事务1

当方法deleteUserByRoleId 执行时抛出了异常,事务怎么处理

开启事务1—>执行deleteRole方法—>挂起事务1 —>开启事务2 —>执行deleteUserByRoleId方法抛出异常—> 回滚事务2 —>提交事务1

三、Spring事务失效的场景有哪些

3.1、方法的访问权限不是public类型
package com.duan.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.duan.mapper.RoleMapper;
import com.duan.pojo.Role;
import com.duan.service.RoleService;
import com.duan.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private UserService userService;

    @Override
    @Transactional()
    private void deleteRole(Integer roleId) {
         roleMapper.deleteById(roleId);

         userService.deleteUserByRoleId(roleId);
}
}

因为Spring事务是由AOP机制实现的,AOP机制的本质就是动态代理,而代理的事务方法不是public的话,computeTransactionAttribute()就会返回null,事务属性也就不存在了。

3.2、service类没有被Spring管理
package com.duan.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.duan.mapper.RoleMapper;
import com.duan.pojo.Role;
import com.duan.service.RoleService;
import com.duan.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

#@Service(注释了@service注解)
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private UserService userService;

    @Override
    @Transactional
    public void deleteRole(Integer roleId) {
         roleMapper.deleteById(roleId);

         userService.deleteUserByRoleId(roleId);
}
}

@Service注解注释之后,Spring事务(@Transactional)没有生效,因为Spring事务是由AOP机制实现的。

也就是说从Spring IOC容器获取bean时,Spring会为目标类创建代理,来支持事务的。但是@Service被注释后,你的service类都不是Spring管理的,没办法创建代理类来支持事务。

3.3、事务方法被final、static关键字修饰
package com.duan.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.duan.mapper.RoleMapper;
import com.duan.pojo.Role;
import com.duan.service.RoleService;
import com.duan.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private UserService userService;

    @Override
    @Transactional
    public final void deleteRole(Integer roleId) {
        roleMapper.deleteById(roleId);

        userService.deleteUserByRoleId(roleId);
}
}

如果一个方法被声明为final或者static,则该方法不能被子类重写,也就是说无法在该方法上进行动态代理,这会导致Spring无法生成事务代理对象来管理事务。

3.4、同一个类中,方法内部调用
package com.duan.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.duan.mapper.RoleMapper;
import com.duan.pojo.Role;
import com.duan.service.RoleService;
import com.duan.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private UserService userService;

    @Override
    @Transactional
    public void deleteRole(Integer roleId) {
        roleMapper.deleteById(roleId);
        Role roleByRoleId = getRoleByRoleId(roleId);

        userService.deleteUserByRoleId(roleId);
    }

    @Override
    public Role getRoleByRoleId(Integer roleId) {
        Role role = roleMapper.selectById(roleId);
        return role;
    }

}

事务是通过Spring AOP代理来实现的,而在同一个类中,一个方法调用另一个方法时,调用方法直接调用目标方法的代码,而不是通过代理类进行调用

3.5、数据库的存储引擎不支持事务

Spring事务的底层,还是依赖于数据库本身的事务支持。在MySQL中,MyISAM存储引擎是不支持事务的,InnoDB引擎才支持事务。因此开发阶段设计表的时候,确认你的选择的存储引擎是支持事务的

3.6、异常没有被抛出
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
  
  @Transactional
  public void update(Role role) {
    try{
      // update role
    }catch{
      
    }
  }
}

这个方法把异常给捕获了,但没有抛出来,所以事务不会回滚,只有捕捉到异常事务才会生效。

3.7、异常类型不匹配
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
  @Transactional
  public void update(Role role) {
    try{
      // update role
    }catch{
      throw new Exception("更新失败");
    }
  }
}

Spring默认回滚的是RuntimeException异常,和上面程序抛出的 Exception异常不匹配,所以事务也是不生效的。如果要触发默认RuntimeException之外异常的回滚,则需要在@Transactiona事务注解上指定异常类,示例如下:

@Transactional(rollbackFor = Exception.class)

以上场景是常见的Spring事务失效的常见场景,如果在项目中出现@Transactional事务不生效,可以根据具体情况排查一下,如:自身调用、异常不匹配、异常被捕获等。

代码地址:https://gitee.com/duan138/practice-code/tree/dev/transaction

三、总结

在日常开发时,Spring事务也是非常重要的知识点,同时也是面试官着重会问的问题,特别是事务传播行为有哪些以及碰没碰到过事务失效的场景等等,所以这部分内容还是要花点时间好好准备准备。

下篇文章来学习学习项目中非常重要的知识点AOP


改变你能改变的,接受你不能改变的,关注公众号:程序员康康,一起成长,共同进步。

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值