Spring @Transactional - 隔离,传播

问题

有人可以通过真实世界的例子来解释876477709隔离传播**参数在@Transactional注释中的用途。基本上何时以及为什么我应该选择更改其默认值。


#1 热门回答(293 赞)

好问题,虽然不是一个微不足道的答案。
传播
定义事务彼此之间的关系。常见选项

  • 必需:代码将始终在事务中运行。创建新事务或重用一个(如果可用)。
  • Requires_new:代码将始终在新事务中运行。暂停当前​​事务(如果存在)。
    隔离
    定义事务之间的数据协定。
  • Read Uncommitted:允许脏读
  • Read Committed:不允许脏读
  • 可重复读取:如果在同一个事务中读取两次行,则结果将始终相同
  • Serializable:按顺序执行所有事务

不同级别在多线程应用程序中具有不同的性能特征。我想如果你了解dirty reads概念,你将能够选择一个好的选择。

可能发生脏读的示例

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

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

一个实际示例,在输入566716586例程时将始终创建新事务,并在离开时完成。

public class FooService {
    private Repository repo1;
    private Repository repo2;

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

如果在进入例程时事务已经打开,我们使用了Required而不是事务will remain open。另请注意,arollback的结果可能会有所不同,因为多次执行可能会参与同一事务。

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

@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 ... 
}

传播水平为

  • 需要新的我们期望fooService.provideService()没有回滚,因为它创建了它自己的子事务。
  • 要求我们希望所有内容都回滚并支持商店不变。

#2 热门回答(201 赞)

PROPAGATION_REQUIRED = 0;如果已经为方法M1启动了DataSourceTransactionObject T1。如果需要另一个方法M2事务对象,则不会创建新的Transaction对象.Same对象T1用于M2

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

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

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

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

**隔离级别:**这是关于事务可能受其他并发事务的活动影响的程度。它支持一致性,使数据保持在一致状态的多个表中。它涉及锁定数据库中的行和/或表。
多个事务的问题
如果T1事务从表A1读取由另一个并发事务T2写入的数据。如果在T2回滚的路上,T1获得的数据是无效的.Eg a = 2是原始数据。如果T1读取a = 1由T2写入。如果T2回滚,则a = 1将在DB中回滚到a = 2.但是,现在,T1具有= 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被称为44476885幻影读取。**

因此,隔离级别是可以防止的方案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();
    }

}

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


#3 热门回答(72 赞)

其他答案给出了对每个参数的充分解释;但是你要求一个真实世界的例子,这里是澄清不同传播选项的目的:
假设你负责实施asignup服务,其中向用户发送确认电子邮件。你提出了两个服务对象,一个是用户注册的,另一个是forsendinge-mail,后者在第一个服务对象中调用。例如这样的事情:

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