深入理解Spring的事务传播行为

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/weixin_36279318/article/details/102676119

前言

Spring在TransactionDefinition接口中规定了7种类型的事务传播行为。事务传播行为是Spring框架独有的事务增强特性,它不属于的事务实际提供方数据库行为。这是Spring为我们提供的强大的工具箱,使用事务传播行可以为我们的开发工作提供许多便利。但是人们对他的误解也颇多,你一定也听过“service方法事务最好不要嵌套”的传言。要想正确的使用工具首先需要了解工具。本文对七种事务传播行为做详细介绍,内容主要代码示例的方式呈现。

基本概念

1.什么是事务传播行为?

即然是传播,那么至少有两个东西,才可以发生传播。单体不存在传播这个行为。事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。 

例如:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。

用伪代码说明:

public void methodA(){
methodB();
//doSomething
}
 
@Transaction(Propagation=XXX)
public void methodB(){
//doSomething
}

代码中methodA()方法嵌套调用了methodB()方法,methodB()的事务传播行为由@Transaction(Propagation=XXX)设置决定。这里需要注意的是methodA()并没有开启事务,某一个事务传播行为修饰的方法并不是必须要在开启事务的外围方法中调用。 

2.Spring中七种事务传播行为

事务传播行为类型 说明
PROPAGATION_REQUIRED

如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。

解释:

 如果methodA()方法没有事务,methodB()有事务,则methodB()开启一个事务

 如果methodA()方法有事务,则methodB()加入methodA()的事务中去不需要再开启事务

PROPAGATION_SUPPORTS

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

解释:

如果methodA()方有事务,methodB()有事务

如果methodA()方法没有事务,methodB()有事务,那么methodB()有异常不回滚

PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

2.1 PROPAGATION_REQUIRED

场景1:同一个Service中,methodA()没有事务,那么methodB()开启事务

 public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
    }
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

 场景2:同一个Service中,methodA()有事务,那么methodB()不开启事务

@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        this.methodB();
    }
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

  场景3:ServiceA中的methodA()方法没有事务,methodA()中调用ServiceB中methodB()开启了事务法方法

  public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        personServiceB.methodB();
    }

  场景4:ServiceA中的methodA()方法有事务,methodA()中调用ServiceB中methodB()不开启事务法方法 

    //PersonServiceA类
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        personServiceB.methodB();
    }

    //PersonServiceB类
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

数据库结果结果分析

场景序号 数据库结果 结果分析
1

“张三”插入数据库

李四”未插入数据库。

methodA()方法没有事务,则methodB()开启一个事务
2

“张三”插入数据库

李四”未插入数据库

methodA()方法开启了事务,则methodB()加入methodA()的事务中去不需要再开启事务
3

“张三”插入数据库

李四”未插入数据库。

methodA()方法没有事务,则methodB()开启一个事务
4 “张三”和“李四”均未插入数据库 methodA()方法有事务,则methodB()无需再开启一个事务

2.2 PROPAGATION_SUPPORTS

场景1:同一个Service中,methodA()没有事务,methodB()总是非事务地执行

 public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        this.methodB();
    }
    @Transactional(propagation = Propagation.SUPPORTS,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

 场景2:同一个Service中,methodA()有事务,methodB()事务执行

    //注意:methodA()方法事务传播行为是Propagation.REQUIRED
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        this.methodB();
    }
    //注意:单独调用menthodB()方法,事务不起作用,不会回滚
    @Transactional(propagation = Propagation.SUPPORTS,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

  场景3:ServiceA中的methodA()方法没有事务,methodA()中调用ServiceB中methodB()非事务执行

//PersonServiceA类的方法
public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        personServiceB.methodB();
    }

  场景4:ServiceA中的methodA()方法有事务,methodA()中调用ServiceB中methodB()事务执行

    //PersonServiceA类
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        personServiceB.methodB();
    }

    //PersonServiceB类
    @Transactional(propagation = Propagation.SUPPORTS,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

数据库结果结果分析 

场景序号 数据库结果 结果分析
1 “张三”和“李四”均插入数据库 methodA()方法没有事务,methodB()总是非事务执行,那么methodB()方法出现异常也不回滚
2 “张三”和“李四”均未插入数据库 methodA()方法有事务,那么methodB()methodA()方法回滚
3 “张三”和“李四”均插入数据库 methodA()方法没有事务,methodB()总是非事务执行,那么methodB()方法出现异常也不回滚
4 “张三”和“李四”均未插入数据库 methodA()方法有事务,那么methodB()methodA()方法回滚

2.3 PROPAGATION_MANDATORY

场景1:同一个Service中,methodA()没有事务,methodB()非事务执行

public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        this.methodB();
    }

    @Transactional(propagation = Propagation.MANDATORY,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

 场景2:同一个Service中,methodA()有事务,methodB()事务执行

@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        this.methodB();
    }

    @Transactional(propagation = Propagation.MANDATORY,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

  场景3:ServiceA中的methodA()方法没有事务,methodA()中调用ServiceB中methodB()方法非事务执行,且出现异常

    //PersonServiceA类
    public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        personServiceB.methodB();
    }
    //PersonServiceB类
    @Transactional(propagation = Propagation.MANDATORY,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

  场景4:ServiceA中的methodA()方法有事务,methodA()中调用ServiceB中methodB()有事务法方法

    //PersonServiceA类
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        personServiceB.methodB();
    }

    //PersonServiceB类
    @Transactional(propagation = Propagation.MANDATORY,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

数据库结果结果分析 

场景序号 数据库结果 结果分析
1 “张三”和“李四”均插入数据库 methodA()方法没有事务,methodB()有事务,但无效,那么methodB()方法出现异常也不回滚
2 “张三”和“李四”均未插入数据库 methodA()方法有事务,methodB()有事务,那么methodB()methodA()方法事务回滚
3

“张三”插入数据库

“李四”未插入数据库

methodA()方法没有事务,调用methodB()出现IllegalTransactionStateException
4 “张三”和“李四”均未插入数据库 methodA()方法有事务,methodB()有事务,那么methodB()methodA()方法回滚

2.4 PROPAGATION_REQUIRED_NEW

场景1:同一个Service中,methodA()没有事务,methodB()非事务执行

   public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        this.methodB();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

 场景2:同一个Service中,methodA()有事务,methodB()事务执行

    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        this.methodB();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

   场景3:ServiceA中的methodA()方法没有事务,methodA()中调用ServiceB中methodB()事务执行

    //PersonServiceA类
    public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        personServiceB.methodB();
    }

    //PersonServiceB类
    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

  场景4:ServiceA中的methodA()方法有事务,methodA()中调用ServiceB中methodB()有事务法方法 

 数据库结果结果分析 

场景序号 数据库结果 结果分析
1 “张三”和“李四”均插入数据库 methodA()方法没有事务,methodB()有事务但无效,那么methodB()方法出现异常也不回滚
2 “张三”和“李四”均未插入数据库 methodA()方法有事务,methodB()有事务,那么methodB()methodA()方法回滚
3

“张三”插入数据库

“李四”未插入数据库

methodA()方法没有事务,methodB()有事务,只对methodB()事务回滚
4 “张三”和“李四”均未插入数据库 methodA()方法有事务,methodB()有事务,那么methodB()methodA()方法回滚

2.5 PROPAGATION_NOT_SUPPORTED

场景1:同一个Service中,methodA()方法没有事务,methodB()方法总是非事务地执行

    public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        this.methodB();
    }
    @Transactional(propagation = Propagation.NOT_SUPPORTED,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

 场景2:同一个Service中,methodA()方法有事务,methodB()方法总是非事务地执行

@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        this.methodB();
    }
    @Transactional(propagation = Propagation.NOT_SUPPORTED,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

  场景3:ServiceA中的methodA()方法没有事务,methodA()中调用ServiceB中methodB()方法总是非事务地执行

    //PersonServiceA类
    public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        personServiceB.methodB();
    }

    //PersonServiceB类
    @Transactional(propagation = Propagation.NOT_SUPPORTED,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

  场景4:ServiceA中的methodA()方法有事务,methodA()中调用ServiceB中methodB()方法总是非事务地执行

    //PersonServiceA类
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        personServiceB.methodB();
    }
    //PersonServiceB类
    @Transactional(propagation = Propagation.NOT_SUPPORTED,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

 数据库结果结果分析

场景序号 数据库结果 结果分析
1 “张三”和“李四”均插入数据库 methodA()方法没有事务,methodB()总是非事务地执行,那么methodB()方法出现异常也不回滚
2 “张三”和“李四”均未插入数据库 methodA()方法有事务,methodB()总是非事务地执行,那么methodB()methodA()方法事务回滚
3 “张三”和“李四”均插入数据库 methodA()方法没有事务,methodB()总是非事务地执行,那么methodB()方法出现异常也不回滚
4

“张三”未插入数据库

“李四”插入数据库

methodA()方法有事务挂起的不提交,methodB()总是非事务地执行,执行完methodB()后,methodA()方法有事务才提交

2.6 PROPAGATION_NERVER

场景1:同一个Service中,methodA()方法没有事务,methodB()总是非事务地执行

 public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        this.methodB();
    }
    @Transactional(propagation = Propagation.NEVER,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

场景2:同一个Service中,methodA()方法有事务,methodB()方法总是非事务地执行

    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        this.methodB();
    }
    @Transactional(propagation = Propagation.NEVER,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

场景3:ServiceA中的methodA()方法没有事务,methodA()中调用ServiceB中methodB()方法总是非事务地执行

    //PersonServiceA类
    public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        personServiceB.methodB();
    }

    //PersonServiceB类
    @Transactional(propagation = Propagation.NEVER,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

  场景4:ServiceA中的methodA()方法有事务,methodA()中调用ServiceB中methodB()方法总是非事务地执行 

    //PersonServiceA类
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public void methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        personServiceB.methodB();
    }   
    //PersonServiceB类
    @Transactional(propagation = Propagation.NEVER,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

 数据库结果结果分析

场景序号 数据库结果 结果分析
1 “张三”和“李四”均插入数据库 methodA()方法没有事务,methodB()总是非事务地执行,那么methodB()方法出现异常也不回滚
2 “张三”和“李四”均未插入数据库 methodA()方法有事务,methodB()总是非事务地执行,那么methodB()methodA()方法事务回滚
3 “张三”和“李四”均插入数据库 methodA()方法没有事务,methodB()总是非事务地执行,那么methodB()方法出现异常也不回滚
4 “张三”和“李四”均未插入数据库 methodA()方法有事务,methodB()总是非事务地执行,那么methodB()methodA()方法事务回滚

2.7 PROPAGATION_NESTED

场景1:同一个Service中,methodA()方法没有事务,methodB()非事务执行

    public void  methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        this.methodB();
    }
    @Transactional(propagation = Propagation.NESTED,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

场景2:同一个Service中,methodA()方法有事务,methodB()方法总是非事务地执行 

 @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
 public void  methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        this.methodB();
    }
    @Transactional(propagation = Propagation.NESTED,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

场景3:ServiceA中的methodA()方法没有事务,methodA()中调用ServiceB中methodB()方法事务地执行 

    //PersonServiceA类
    public void  methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        personServiceB.methodB();
    }

    //PersonServiceB类
    //@Transactional(propagation = Propagation.NESTED,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

  场景4:ServiceA中的methodA()方法有事务,methodA()中调用ServiceB中methodB()方法总是非事务地执行  

    //PersonServiceA类
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public void  methodA(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("张三");
        personPO.setAge(22);
        personDao.insert(personPO);
        personServiceB.methodB();
    }

    //PersonServiceB类
    //@Transactional(propagation = Propagation.NESTED,rollbackFor = Exception.class)
    public void methodB(){
        TbPersonPO personPO=new TbPersonPO();
        personPO.setName("李四");
        personPO.setAge(24);
        personDao.insert(personPO);
        throw new RuntimeException();
    }

 数据库结果结果分析

场景序号 数据库结果 结果分析
1 “张三”和“李四”均插入数据库 methodA()方法没有事务,methodB()总是非事务地执行,那么methodB()方法出现异常也不回滚
2 “张三”和“李四”均未插入数据库 methodA()方法有事务,methodB()总是非事务地执行,那么methodB()methodA()方法事务回滚
3

“张三”插入数据库

“李四”未插入数据库

methodA()方法没有事务,methodB()总事务执行,methodB()中的事务回滚
4 “张三”和“李四”均未插入数据库 methodA()方法有事务,那么methodB()methodA()方法事务回滚

参考:https://blog.csdn.net/weixin_39625809/article/details/80707695

参考:https://www.jb51.net/article/135539.htm

猜你喜欢

转载自blog.csdn.net/weixin_36279318/article/details/102676119