为了账号安全,请及时绑定邮箱和手机立即绑定

Spring @Transactional-隔离,传播

/ 猿问

Spring @Transactional-隔离,传播

慕村9548890 2019-10-12 14:24:48

有人可以通过实际示例解释注释中的隔离和传播参数@Transactional吗?


基本上,何时和为什么我应该选择更改其默认值。


查看完整描述

3 回答

?
摇曳的蔷薇

好的问题,尽管不是一个简单的答案。


传播


定义事务之间的关系。常用选项:


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 Committed,它只能让您读取传播级别为的其他正在运行的事务已提交的值Required。然后,如果您的应用程序有其他需求,则可以从那里开始。


一个实际的示例,该示例在进入provideService例程时始终在其中创建新事务,而在离开时总是在其中完成:


public class FooService {

    private Repository repo1;

    private Repository repo2;


    @Transactional(propagation=Propagation.REQUIRES_NEW)

    public void provideService() {

        repo1.retrieveFoo();

        repo2.retrieveFoo();

    }

}

如果我们改为使用Required,则在进入例程时如果事务已经打开,则事务将保持打开状态。还要注意,a的结果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:我们希望一切都回滚而后备存储保持不变。


查看完整回答
反对 回复 2019-10-12
?
精慕HU

PROPAGATION_REQUIRED = 0 ; 如果方法M1的DataSourceTransactionObject T1已经启动;如果另一个方法M2需要事务对象,则不创建新的事务对象。对于M2使用相同的对象T1


PROPAGATION_MANDATORY = 2 ; 方法必须在事务中运行。如果没有现有的交易正在进行,则将引发异常


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


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


PROPAGATION_NEVER = 5 ; 这些方法均未在事务上下文中运行。


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


多笔交易的问题


方案1.如果T1事务从另一个并发事务T2写入的表A1中读取数据,如果在T2进行回滚的途中,则T1获得的数据无效。例如a = 2是原始数据。如果T1读取a = 1由T2写入。如果T2回滚,则a = 1将在DB中回滚到a = 2。但是,现在,T1具有a = 1,但在DB表中已更改为a = 2。


方案2如果T1事务从表A1读取数据,如果另一个并发事务(T2)更新表A1上的数据,则T1读取的数据与表A1不同,因为T2已更新表A1上的数据,例如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();

    }


}

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


查看完整回答
反对 回复 2019-10-12
?
莫回无

关于其他参数的足够解释由其他答案给出;但是,您要求提供一个真实的示例,以下示例阐明了不同传播选项的目的:


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

/* 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。


查看完整回答
反对 回复 2019-10-12

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信