隔离,传播

有人可以通过真实世界的例子解释@Transactional注释中的隔离传播参数。 基本上什么时候和为什么我应该选择改变他们的默认值。


好问题,虽然不是一个可以回答的小问题。

传播

定义交易如何相互关联。 通用选项

  • Required :代码将始终在事务中运行。 创建一个新的事务或重用一个(如果可用)。
  • Requires_new :代码将始终运行在新的事务中。 如果存在,暂停当前事务。
  • 隔离

    定义事务之间的数据契约。

  • Read Uncommitted :允许脏读
  • Read Committed :不允许脏读
  • Repeatable Read :如果在同一个交易所中一行被读取两次,结果总是相同的
  • Serializable :按顺序执行所有事务
  • 多线程应用程序中的不同级别具有不同的性能特征。 我想如果你明白dirty reads概念,你将能够选择一个好的选择。


    可能会发生脏读的示例

      thread 1   thread 2      
          |         |
        write(x)    |
          |         |
          |        read(x)
          |         |
        rollback    |
          v         v 
               value (x) is now dirty (incorrect)
    

    因此,一个理智的默认值(如果可以声明)可以是Read Comitted ,它只允许您读取已由其他正在运行的事务处理的值,并结合Required的传播级别。 然后,如果应用程序有其他需求,则可以从那里开始工作。


    一个实际的例子,在进入provideService例程时总是创建一个新的事务,并在离开时完成。

    public class FooService {
        private Repository repo1;
        private Repository repo2;
    
        @Transactional(propagation=Propagation.REQUIRES_NEW)
        public void provideService() {
            repo1.retrieveFoo();
            repo2.retrieveFoo();
        }
    }
    

    如果我们使用Required ,则在进入例程时事务已经打开时,事务将保持打开状态。 还要注意, rollback的结果可能会不同,因为多次执行可能参与同一事务。


    我们可以通过测试轻松验证行为并查看结果与传播级别的差异

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations="classpath:/fooService.xml")
    public class FooServiceTests {
    
        private @Autowired TransactionManager transactionManager;
        private @Autowired FooService fooService;
    
        @Test
        public void testProvideService() {
            TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
            fooService.provideService();
            transactionManager.rollback(status);
            // assert repository values are unchanged ... 
    }
    

    传播级别为

  • Requires new我们期望fooService.provideService()不会回滚,因为它创建了它自己的子事务。

  • Required我们期望一切都回滚和支持存储不变。


  • PROPAGATION_REQUIRED = 0 ; 如果DataSourceTransactionObject T1已为方法M1启动。如果需要另一个方法M2 Transaction对象,则不会创建新的Transaction对象。同名对象T1用于M2

    PROPAGATION_MANDATORY = 2 ; 方法必须在事务内运行。 如果没有正在进行的事务,则会抛出异常

    PROPAGATION_REQUIRES_NEW = 3 ; 如果对于方法M1已经启动了DataSourceTransactionObject T1并且它正在进行(正在执行方法M1)。如果另一个方法M2开始执行,则在方法M2的持续时间内暂停T1,对于M2.M2在其自身的事务上下文中运行新的DataSourceTransactionObject T2

    PROPAGATION_NOT_SUPPORTED = 4 ; 如果DataSourceTransactionObject T1已为方法M1启动。如果另一个方法M2同时运行,则M2不应在事务上下文中运行。 T1暂停,直到M2完成。

    PROPAGATION_NEVER = 5 ; 没有任何方法在事务上下文中运行。

    一个隔离级别:一个事务可能会受到其他并发事务的活动影响多少。它支持一致性,使数据跨多个表保持一致的状态。 它涉及锁定数据库中的行和/或表。

    多重交易的问题

    场景1:如果T1事务从表A1中读取的数据是由另一个并发事务T2写入的。如果T2在回滚的方式中,T1获得的数据是无效的。例如,如果a = 2是原始数据,则读取a = 1是由T 2写的。如果T2回滚,那么a = 1将在数据库中回滚到a = 2。但是,现在,T1具有a = 1,但在DB表中它变为a = 2。

    场景2。如果T1事务从表A1中读取数据。如果另一个并发事务(T2)更新表A1上的数据,则T1读取的数据与表A1不同。因为T2已更新表A1.Eg上的数据(如果T1读a = 1和T2更新a = 2。然后a!= b。

    场景3。如果T1事务从表A1读取具有一定行数的数据。 如果另一个并发事务(T2)在表A1上插入更多行,则T1读取的行数与表A1上​​的行不同

    场景1被称为脏读。

    情景2被称为非重复读取。

    场景3被称为幻影读取。

    因此,隔离级别是方案1,方案2,方案3可以预防的范围。 您可以通过实现锁定来获得完整的隔离级别。这样可以防止对同一数据发生并发读取和写入操作,但会影响性能。隔离级别取决于应用程序对应用程序的隔离级别。

    ISOLATION_READ_UNCOMMITTED :允许读取尚未提交的更改。它受到方案1,方案2,方案3

    ISOLATION_READ_COMMITTED :允许从已提交的并发事务中读取数据。 它可能会遭受场景2和场景3.因为其他事务可能正在更新数据。

    ISOLATION_REPEATABLE_READ :同一个字段的多次读取将产生相同的结果,直到它被自己改变。它可能遭受场景3的困扰。因为其他事务可能正在插入数据

    ISOLATION_SERIALIZABLE :方案1,方案2,方案3从不发生,它是完全隔离的,它涉及完全锁定,由于锁定而影响性能。

    你可以使用测试

    public class TransactionBehaviour {
       // set is either using xml Or annotation
        DataSourceTransactionManager manager=new DataSourceTransactionManager();
        SimpleTransactionStatus status=new SimpleTransactionStatus();
       ;
    
    
        public void beginTransaction()
        {
            DefaultTransactionDefinition Def = new DefaultTransactionDefinition();
            // overwrite default PROPAGATION_REQUIRED and ISOLATION_DEFAULT
            // set is either using xml Or annotation
            manager.setPropagationBehavior(XX);
            manager.setIsolationLevelName(XX);
    
            status = manager.getTransaction(Def);
    
        }
    
        public void commitTransaction()
        {
    
    
                if(status.isCompleted()){
                    manager.commit(status);
            } 
        }
    
        public void rollbackTransaction()
        {
    
                if(!status.isCompleted()){
                    manager.rollback(status);
            }
        }
        Main method{
            beginTransaction()
            M1();
            If error(){
                rollbackTransaction()
            }
             commitTransaction();
        }
    
    }
    

    您可以调试并查看具有不同隔离和传播值的结果。


    其他答案给出了有关每个参数的足够解释; 然而,你要求一个真实世界的例子,下面是澄清不同传播选项的目的:

    假设您负责实施向用户发送确认电子邮件的注册服务。 你想出两个服务对象,一个用于注册用户,一个用于发送电子邮件,后者在第一个内部被称为。 例如这样的事情:

    /* Sign Up service */
    @Service
    @Transactional(Propagation=REQUIRED)
    class SignUpService{
     ...
     void SignUp(User user){
        ...
        emailService.sendMail(User);
     }
    }
    
    /* E-Mail Service */
    @Service
    @Transactional(Propagation=REQUIRES_NEW)
    class EmailService{
     ...
     void sendMail(User user){
      try{
         ... // Trying to send the e-mail
      }catch( Exception)
     }
    }
    

    您可能已经注意到第二个服务的传播类型为REQUIRES_NEW ,而且它有可能引发异常(SMTP服务器关闭,无效的电子邮件或其他原因)。您可能不希望整个过程回滚,如从数据库或其他事物中删除用户信息; 因此您可以在单独的事务中调用第二项服务。

    回到我们的例子,这次你关心数据库的安全性,所以你这样定义你的DAO类:

    /* User DAO */
    @Transactional(Propagation=MANDATORY)
    class UserDAO{
     // some CRUD methods
    }
    

    这意味着无论何时创建一个DAO对象,并因此创建对db的潜在访问权限,我们都需要确保该调用是从我们的一个服务内部进行的,这意味着应该存在实时事务; 否则会发生异常。因此传播类型为MANDATORY

    链接地址: http://www.djcxy.com/p/30135.html

    上一篇: isolation, propagation

    下一篇: What is the difference between CMD and ENTRYPOINT in a Dockerfile?