Spring事务传播方式详解版权声明

原创
小哥 3年前 (2022-11-11) 阅读数 87 #大杂烩

Spring在TransactionDefinition接口指定7事务传播行为的类型。事务传播行为是Spring该框架独特的事务增强功能,不属于事务的实际提供者数据库行为。这是Spring使用事务传播线为我们提供的强大工具箱可以为我们的开发工作提供许多便利。然而,对他的误解很多,你一定听说过他。”service方法事务最好不要“嵌套”。要正确使用工具,首先需要了解工具。本文详细描述了七种事务传播行为,并给出了主要的代码示例。

基础概念

  1. 什么是事务性沟通行为?

事务传播行为用于描述由一个事务传播行为修饰的方法如何由嵌套到另一个方法中的事务传播。

伪代码说明:

 public void methodA(){
    methodB();
    //doSomething
 }

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

代码中 methodA() 方法嵌套调用。 methodB() 方法, methodB() 的事务传播行为 @Transaction(Propagation=XXX) 制定决定。这里需要注意的是 methodA() 事务未打开,并且不必在打开事务的外围方法中调用事务传播行为修改的方法。

  1. Spring中的七种事务传播行为

事务传播行为的类型

说明

PROPAGATION_REQUIRED

如果当前没有事务,请创建一个新事务,如果已经有事务,请加入它。这是最常见的选择。

PROPAGATION_SUPPORTS

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

PROPAGATION_MANDATORY

使用当前事务,如果当前没有事务,则抛出异常。

PROPAGATION_REQUIRES_NEW

创建一个新事务,如果当前事务当前存在,则将其挂起。

PROPAGATION_NOT_SUPPORTED

以非事务方式执行操作,如果当前事务当前存在,则挂起该事务。

PROPAGATION_NEVER

以非事务方式执行,如果当前存在事务,则引发异常。

PROPAGATION_NESTED

如果事务当前存在,则在嵌套事务中执行该事务。如果当前没有事务,则执行相同的事务。PROPAGATION_REQUIRED类似操作。

定义非常简单,易于理解。让我们去代码测试部分验证我们的理解是否正确。

代码验证

文本中的代码以传统三层结构的两层表示,即e。Service和Dao层,由Spring负责依赖注入和注释事务管理,DAO层由Mybatis实现,您也可以使用任何您喜欢的方式,例如,Hibernate,JPA,JDBCTemplate数据库使用MySQL数据库,您还可以使用任何支持事务且不影响验证结果的数据库。

首先,我们在数据库中创建两个表:

user1

CREATE TABLE user1 (
  id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  name VARCHAR(45) NOT NULL DEFAULT ,
  PRIMARY KEY(id)
)
ENGINE = InnoDB;

user2

CREATE TABLE user2 (
  id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  name VARCHAR(45) NOT NULL DEFAULT ,
  PRIMARY KEY(id)
)
ENGINE = InnoDB;

然后写出相应的Bean和DAO层代码:

User1

public class User1 {
    private Integer id;
    private String name;
   //get和set方法省略...
}

User2

public class User2 {
    private Integer id;
    private String name;
   //get和set方法省略...
}

User1Mapper

public interface User1Mapper {
    int insert(User1 record);
    User1 selectByPrimaryKey(Integer id);
    //其他方法省略...
}

User2Mapper

public interface User2Mapper {
    int insert(User2 record);
    User2 selectByPrimaryKey(Integer id);
    //其他方法省略...
}

最后,生成特定验证的代码service层实现,我们在下面列出。

1.PROPAGATION_REQUIRED

我们为User1Service和User2Service相应方法加 Propagation.REQUIRED 属性。

User1Service方法:

@Service
public class User1ServiceImpl implements User1Service {
    //省略其他...
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequired(User1 user){
        user1Mapper.insert(user);
    }
}

User2Service方法:

@Service
public class User2ServiceImpl implements User2Service {
    //省略其他...
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequired(User2 user){
        user2Mapper.insert(user);
    }
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequiredException(User2 user){
        user2Mapper.insert(user);
        throw new RuntimeException();
    }

}

1.1 场景一

此方案外围方法不打开事务。

验证方法1:

    @Override
    public void notransaction_exception_required_required(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);

        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequired(user2);

        throw new RuntimeException();
    }

验证方法2:

    @Override
    public void notransaction_required_required_exception(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);

        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiredException(user2);
    }

验证方法分别进行,结果如下:

验证方法序列号

数据库结果

结果分析

1

插入“张三”和“李四”。

外围方法不打开交易,插入“张三”和“李四”方法在自己的交易中独立运行,外围方法例外不影响内部插入“张四”和“李四”方法独立交易。

2

插入“张三”,未插入“李四”。

外围方法没有事务,插入的“张三”和“李四”方法都在各自的事务中独立运行。,因此,插入“李四”方法抛出异常只会回滚并插入“李三”方法,插入“张三”方法不会受到影响。

结论:通过这两种方法,我们证明了在外围方法不打开事务的情况下 Propagation.REQUIRED 经过修饰的内部方法将新打开自己的交易,并且打开的交易彼此独立,互不干扰。

1.2 场景二

外围方法启动事务,这是一个使用率很高的场景。

验证方法1:

   @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_exception_required_required(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);

        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequired(user2);

        throw new RuntimeException();
    }

验证方法2:

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_required_required_exception(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);

        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiredException(user2);
    }

验证方法3:

    @Transactional
    @Override
    public void transaction_required_required_exception_try(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);

        User2 user2=new User2();
        user2.setName("李四");
        try {
            user2Service.addRequiredException(user2);
        } catch (Exception e) {
            System.out.println("方法回滚");
        }
    }

验证方法分别进行,结果如下:

验证方法序列号

数据库结果

结果分析

1

没有插入“张三”和“李四”。

外围方法打开事务,内部方法加入外围方法事务,外围方法回滚,内部方法也回滚。

2

没有插入“张三”和“李四”。

外围方法打开事务,内部方法加入外围方法事务,内部法抛出异常回滚,外围方法感知到异常导致整个事务回滚。

3

没有插入“张三”和“李四”。

外围方法打开事务,内部方法加入外围方法事务,并且内部方法抛出异常回滚,即使该方法是。catch外围方法无法察觉,整个事务仍在回滚。

结论:上述测试结果表明,在外围方法打开交易的情况下。 Propagation.REQUIRED 修饰的内部方法被添加到外围方法的事务中。 Propagation.REQUIRED 修饰的内部和外围方法属于同一事务,只要回滚一个方法,整个事务就会回滚。

2.PROPAGATION_REQUIRES_NEW

我们为User1Service和User2Service相应方法加 Propagation.REQUIRES_NEW 属性。
User1Service方法:

@Service
public class User1ServiceImpl implements User1Service {
    //省略其他...
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addRequiresNew(User1 user){
        user1Mapper.insert(user);
    }
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequired(User1 user){
        user1Mapper.insert(user);
    }
}

User2Service方法:

@Service
public class User2ServiceImpl implements User2Service {
    //省略其他...
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addRequiresNew(User2 user){
        user2Mapper.insert(user);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addRequiresNewException(User2 user){
        user2Mapper.insert(user);
        throw new RuntimeException();
    }
}

2.1 场景一

外围方法无法打开事务。

验证方法1:

    @Override
    public void notransaction_exception_requiresNew_requiresNew(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequiresNew(user1);

        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiresNew(user2);
        throw new RuntimeException();

    }

验证方法2:

    @Override
    public void notransaction_requiresNew_requiresNew_exception(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequiresNew(user1);

        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiresNewException(user2);
    }

验证方法分别进行,结果如下:

验证方法序列号

数据库结果

结果分析

1

插入“张三”,插入“李四”。

外围方法没有事务,插入的“张三”和“李四”方法都在各自的事务中独立运行。,外围方法抛出异常回滚而不影响内部方法。

2

插入“张三”,未插入“李四”

外围方法不打开事务,插入“张三”方法和插入“李四”方法打开自己的事务,插入”李四“方法抛出异常回滚,其他事务不受影响。

结论:通过这两种方法,我们证明了在外围方法不打开事务的情况下 Propagation.REQUIRES_NEW 经过修饰的内部方法将新打开自己的交易,并且打开的交易彼此独立,互不干扰。

2.2 场景二

外围方法打开事务。

验证方法1:

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_exception_required_requiresNew_requiresNew(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);

        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiresNew(user2);

        User2 user3=new User2();
        user3.setName("王五");
        user2Service.addRequiresNew(user3);
        throw new RuntimeException();
    }

验证方法2:

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_required_requiresNew_requiresNew_exception(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);

        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiresNew(user2);

        User2 user3=new User2();
        user3.setName("王五");
        user2Service.addRequiresNewException(user3);
    }

验证方法3:

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_required_requiresNew_requiresNew_exception_try(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);

        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiresNew(user2);
        User2 user3=new User2();
        user3.setName("王五");
        try {
            user2Service.addRequiresNewException(user3);
        } catch (Exception e) {
            System.out.println("回滚");
        }
    }

验证方法分别进行,结果如下:

验证方法序列号

数据库结果

结果分析

1

没有插入“张三”,插入“李四”,插入了“王五”。

外围方法打开事务,插入“张三”方法和外围方法的事务,在独立的新事务中分别插入“李四”方法和“王五”方法,外围方法抛出异常只回滚与外围方法相同的事务的方法,因此插入“张四”方法回滚。

2

未插入“张三”,插入“李四”,未插入“王五”。

外围方法打开交易,插入“张三”方法和外围方法的交易,插入了“李四”方法,并在独立的新交易中插入了“王五”方法。插入“Wang Wu”方法抛出异常,首先插入。 “Wang Wu”方法的事务被回滚,异常继续被外围方法抛出,外围方法事务也被回滚,因此“张三”方法的插入也被回滚。

3

插入了“张三”,插入了“李四”,没有插入“王五”。

外围方法打开交易,插入“张三”方法和外围方法的交易,插入了“李四”方法,并在独立的新交易中插入了“王五”方法。插入“Wang Wu”方法抛出异常,首先插入。“王五”方法的事务被回滚,异常被catch它不会被外围方法感知,外围方法事务也不会回滚,因此“张三”方法的插入是成功的。

结论:在外围方法打开交易的情况下。 Propagation.REQUIRES_NEW 修饰后的内部方法仍将单独打开独立的交易,并且也独立于外部方法交易。内部方法、内部方法和外部方法交易相互独立,互不干扰。

3.PROPAGATION_NESTED

我们为User1Service和User2Service相应方法加 Propagation.NESTED 属性。
User1Service方法:

@Service
public class User1ServiceImpl implements User1Service {
    //省略其他...
    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void addNested(User1 user){
        user1Mapper.insert(user);
    }
}

User2Service方法:

@Service
public class User2ServiceImpl implements User2Service {
    //省略其他...
    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void addNested(User2 user){
        user2Mapper.insert(user);
    }

    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void addNestedException(User2 user){
        user2Mapper.insert(user);
        throw new RuntimeException();
    }
}

3.1 场景一

此方案外围方法不打开事务。

验证方法1:

    @Override
    public void notransaction_exception_nested_nested(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addNested(user1);

        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addNested(user2);
        throw new RuntimeException();
    }

验证方法2:

    @Override
    public void notransaction_nested_nested_exception(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addNested(user1);

        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addNestedException(user2);
    }

验证方法分别进行,结果如下:

验证方法序列号

数据库结果

结果分析

1

插入“张三”和“李四”。

外围方法不打开交易,插入“张三”和“李四”方法在自己的交易中独立运行,外围方法例外不影响内部插入“张四”和“李四”方法独立交易。

2

插入“张三”,未插入“李四”。

外围方法没有事务,插入的“张三”和“李四”方法都在各自的事务中独立运行。,因此,插入“李四”方法抛出异常只会回滚并插入“李三”方法,插入“张三”方法不会受到影响。

结论:通过这两种方法,我们证明了在外围方法不打开事务的情况下 Propagation.NESTEDPropagation.REQUIRED 功能相同,修饰的内部方法将新打开自己的事务,并且打开的事务彼此独立,互不干扰。

3.2 场景二

外围方法打开事务。

验证方法1:

    @Transactional
    @Override
    public void transaction_exception_nested_nested(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addNested(user1);

        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addNested(user2);
        throw new RuntimeException();
    }

验证方法2:

    @Transactional
    @Override
    public void transaction_nested_nested_exception(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addNested(user1);

        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addNestedException(user2);
    }

验证方法3:

    @Transactional
    @Override
    public void transaction_nested_nested_exception_try(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addNested(user1);

        User2 user2=new User2();
        user2.setName("李四");
        try {
            user2Service.addNestedException(user2);
        } catch (Exception e) {
            System.out.println("方法回滚");
        }
    }

验证方法分别进行,结果如下:

验证方法序列号

数据库结果

结果分析

1

没有插入“张三”和“李四”。

外围方法打开事务,内部事务是外围事务的子事务,外围方法被回滚,内部方法也被回滚。

2

没有插入“张三”和“李四”。

外围方法打开事务,内部事务是外围事务的子事务,内部方法抛出异常回滚,而外围方法感知到异常导致整个事务回滚。

3

“张三”插入,“李四”未插入。

外围方法打开事务,内部事务是外围事务的子事务,插入“张三”的内部方法抛出异常,可以单独回滚到子事务。

结论:上述测试结果表明,在外围方法打开交易的情况下。 Propagation.NESTED 修饰的内部方法属于外部事务的子事务,外围主事务被回滚,子事务必须回滚,内部子事务可以单独回滚,而不影响外围主事务和其他子事务。

  1. REQUIRED,REQUIRES_NEW,NESTED异同

由“1.2 情景2“和”3.2 场景2“比较,我们可以看到:
NESTED和REQUIRED修饰的内部方法都是外围方法事务,如果外围方法抛出异常,则回滚两个方法的事务。但是REQUIRED是加入外围方法事务,因此与外围事务属于同一事务,一次。REQUIRED事务抛出异常,外围方法事务被回滚。和NESTED是外围方法的子事务,具有单独的保存点,因此。NESTED该方法抛出一个已回滚且不影响外围方法事务的异常。

由“2.2 情景2“和”3.2 场景2“比较,我们可以看到:
NESTED和REQUIRES_NEW可以执行内部方法事务以回滚,而不影响外围方法事务。但因为NESTED是一个嵌套事务,因此在回滚外围方法之后,作为外围方法事务的子事务也会回滚。和REQUIRES_NEW它是通过打开一个新事务来实现的,内部事务和外围事务是两个事务,外围事务回滚不会影响内部事务。

  1. 其他事务传播行为

鉴于文章的篇幅,这里不一一描述其他事务通信行为的测试。感兴趣的读者可以去源代码找到相应的测试代码并解释结果。门户: https://github.com/TmTse/tran...

模拟用例

我们如何在实践中应用如此多的交易沟通行为?让我举个例子:

假设我们有一个注册方法,其中调用了添加点的方法,如果我们想添加点以不影响注册过程(即,添加点以执行失败的回滚不允许注册方法回滚),我们将这样写:

   @Service
   public class UserServiceImpl implements UserService {

        @Transactional
        public void register(User user){

            try {
                membershipPointService.addPoint(Point point);
            } catch (Exception e) {
               //省略...
            }
            //省略...
        }
        //省略...
   }

我们还要求注册失败 addPoint() 方法(注册方法回滚添加点方法也需要回滚),然后 addPoint() 该方法需要这样实现:

   @Service
   public class MembershipPointServiceImpl implements MembershipPointService{

        @Transactional(propagation = Propagation.NESTED)
        public void addPoint(Point point){

            try {
                recordService.addRecord(Record record);
            } catch (Exception e) {
               //省略...
            }
            //省略...
        }
        //省略...
   }

我们注意到,在 addPoint() 它也被调用 addRecord() 方法,用于记录。他的实现如下:

   @Service
   public class RecordServiceImpl implements RecordService{

        @Transactional(propagation = Propagation.NOT_SUPPORTED)
        public void addRecord(Record record){

            //省略...
        }
        //省略...
   }

我们注意到 addRecord() 方法中 propagation = Propagation.NOT_SUPPORTED ,因为日志是否准确无所谓,您可以多写一个或少写一个,所以 addRecord() 方法本身和外围 addPoint() 该方法抛出一个异常而不生成该异常。 addRecord() 方法回滚,以及 addRecord() 该方法抛出异常而不影响外围。 addPoint() 方法的执行。

通过这个例子,我相信我们对交易沟通行为的使用有了更直观的理解,通过各种属性的组合,可以真正使我们的业务更加灵活多样。

结论

通过以上介绍,我相信大家都是对的。Spring随着对交易沟通行为的深入理解,我希望每个人的日常开发工作都会有所帮助。

原始地址: https://segmentfault.com/a/1190000013341344

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除

热门