SpringBoot系列(三十一)事务回滚

一、事务的简单介绍

事物回滚详细参考文章

https://juejin.cn/post/6854573213616177159#heading-3

事务回滚简单demo:

https://juejin.cn/post/7058467403570610213

事务回滚失效的8大原因

https://blog.csdn.net/Summer_And_Opencv/article/details/104471522

事务的四大特性(原子性、一致性、隔离性、持久性)

举个最简单的银行汇款业务的场景,A向B汇款1000元。这个汇款动作主要有两个,①是A的银行账户上扣去1000元,②是B的银行账户上增加两千元。假如操作①成功了,而操作②失败了,这样A的账户上就白白少了1000元,而B的账户上却没有增加1000。所以我们需要用技术来保证操作①和操作②整体的原子性(即让操作①和②要么同时成功,要么同时失败)

Springboot实现事务支持的3种技术(AOP依赖注入的方式)

  • 动态代理(运行时载入)
  • 编译时载入
  • 类加载时载入

二、基于动态代理支持@Transactional

2.1修改pom.xml引入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.2.7.RELEASE</version>
</dependency>

2.2入口添加@EnableTransactionManagement注解

JAVA、基础技术、技术与框架SpringBoot系列(三十一)事务回滚插图

2.3、创建Service

    @Autowired
    private BannerMapper bannerMapper;

    @Autowired
    private BannerItemMapper bannerItemMapper;
    /*Banner成功插入;BannerItem0~2成功插入*/
    public void CreateBanners(){
        BannerDO bannerDO = new BannerDO();
        bannerDO.setVersion(0);
        bannerDO.setName("foxBanners");
        bannerMapper.insert(bannerDO);

        for(int i = 0; i<=3; i++){
            BannerItemDO bannerItemDO = BannerItemDO.builder()
                    .name("foxBanners"+i)
                    .bannerId(bannerDO.getId())
                    .build();
            bannerItemMapper.insert(bannerItemDO);
            if(i==2){
                throw new RuntimeException("故意抛出一个运行时异常");
            }
        }

    }

    @Transactional
    public void CreateBannerTransaction(){
        BannerDO bannerDO = new BannerDO();
        bannerDO.setVersion(0);
        bannerDO.setName("foxBanner");
        bannerMapper.insert(bannerDO);

        for(int i = 0; i<=3; i++){
            BannerItemDO bannerItemDO = BannerItemDO.builder()
                    .name("foxBanner"+i)
                    .bannerId(bannerDO.getId())
                    .build();
            bannerItemMapper.insert(bannerItemDO);
            if(i==2){
                throw new RuntimeException("故意抛出一个运行时异常");
            }
        }
    }

2.4、创建Controller

   @GetMapping("/createBanners")
    public CreatedVO CreateBanners() {
        bannerService.CreateBanners();
        return new CreatedVO();
    }

    @GetMapping("/CreateBannerTransaction")
    public CreatedVO CreateBannerTransaction() {
        bannerService.CreateBannerTransaction();
        return new CreatedVO();
    }

如何实现手动回滚

/**
 * 插入会员信息
 * 
 * @param cashierMember 会员信息
 * @return 结果
 */
@Override
@Transactional(rollbackFor = Exception.class)
public int insertCashierMember(CashierMember cashierMember)
{
    try {
        cashierMember.setCreateTime(DateUtils.getNowDate());
        cashierMember.setCreateBy(ShiroUtils.getLoginName());
        SMSUtil.sendCreateMemberMessage(cashierMember.getPhonenumber());
        return cashierMemberMapper.insertCashierMember(cashierMember);
    }catch (Exception e){
        System.out.println("方法出现异常:" + e);
        //实现手动回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
    return 0;
}

三、事务失效的8大原因

3.1 数据库引擎不支持事务

这里以 MySQL 为例,其 MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB。

3.2没有被 Spring 管理,没有注入到IOC容器中

http://www.fcors.com/%e6%8a%80%e6%9c%af%e4%b8%8e%e6%a1%86%e6%9e%b6/sprintboot%e7%b3%bb%e5%88%97%ef%bc%88%e4%ba%8c%ef%bc%89%ef%bc%9aspringboot%e7%9a%84ioc%e6%80%9d%e6%83%b3/
// @Service
public class OrderServiceImpl implements OrderService {
 
    @Transactional
    public void updateOrder(Order order) {
        // update order
    }

}

如果此时把 @Service 注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被 Spring 管理了,事务自然就失效了。

3.3方法不是 public 的

@Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。

3.4数据源没有配置事务管理器

3.5 不支持事务

@Service
public class OrderServiceImpl implements OrderService {
 
    @Transactional
    public void update(Order order) {
        updateOrder(order);
    }
 
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void updateOrder(Order order) {
        // update order
    }
 
}

Propagation.NOT_SUPPORTED: 表示不以事务运行

3.6 异常被吃了(没有抛出异常)

@Service
public class OrderServiceImpl implements OrderService {
 
    @Transactional
    public void updateOrder(Order order) {
        try {
            // update order
        } catch {
 
        }
    }
 
}

3.7 异常类型错误

@Service
public class OrderServiceImpl implements OrderService {
 
    @Transactional
    public void updateOrder(Order order) {
        try {
            // update order
        } catch {
            throw new Exception("更新错误");
        }
    }
 
}

这样事务也是不生效的,因为默认回滚的是:RuntimeException,如果你想触发其他异常的回滚,需要在注解上配置一下,如:

@Transactional(rollbackFor = Exception.class)

这个配置仅限于Throwable 异常类及其子类。

3.8 自身调用问题

例子一

@Service
public class OrderServiceImpl implements OrderService {
 
    public void update(Order order) {
        updateOrder(order);
    }
 
    @Transactional
    public void updateOrder(Order order) {
        // update order
    }
 
}

update方法上面没有加@Transactional 注解,调用有@Transactional 注解的 updateOrder 方法,结果:事务失效

例子二

@Service
public class OrderServiceImpl implements OrderService {
 
    @Transactional
    public void update(Order order) {
        updateOrder(order);
    }
 
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateOrder(Order order) {
        // update order
    }
}

update 方法上加了 @Transactional  ,updateOrder 加了 REQUIRES_NEW 新开启一个事务,结果:事务失效

解决的方法:可参考下面的文章,大概意思:分开成两个Service

@Service
public class ServiceA {

  @Autowired
  private ServiceB serviceB;
  @Transactional
  public void doSomething(){
    
    serviceB.insert();
    
    调用其他系统;
  }
}
@Service
public class ServiceB {

  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void insert(){
    向数据库中添加数据;
  }
}

https://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&mid=2247491775&idx=2&sn=142f1d6ab0415f17a413a852efbde54f&scene=21#wechat_redirect