弹簧@Transactional - 隔离、传播

有人可以通过真实的例子来解释注释中的隔离传播参数是什么吗?@Transactional

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


答案 1

好问题,虽然不是一个微不足道的问题。

增殖

定义事务如何相互关联。常用选项:

  • REQUIRED:代码将始终在事务中运行。创建新事务或重用事务(如果可用)。
  • REQUIRES_NEW:代码将始终在新事务中运行。挂起当前事务(如果存在)。

的默认值为 ,这通常是您想要的。@TransactionalREQUIRED

隔离

定义事务之间的数据协定。

  • ISOLATION_READ_UNCOMMITTED:允许脏读。
  • ISOLATION_READ_COMMITTED:不允许脏读。
  • ISOLATION_REPEATABLE_READ:如果在同一事务中读取一行两次,则结果将始终相同。
  • ISOLATION_SERIALIZABLE:按顺序执行所有事务。

在多线程应用程序中,不同的级别具有不同的性能特征。我认为,如果您了解脏读的概念,您将能够选择一个不错的选择。

默认值可能因差异数据库而异。例如,对于MariaDB来说,它是.REPEATABLE READ


发生脏读时的示例:

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

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


一个实际示例,说明在进入例程时始终创建新事务,在离开时始终创建新事务:provideService

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

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

如果我们改用 ,如果事务在进入例程时已经打开,则事务将保持打开状态。另请注意,的结果可能不同,因为多个执行可能参与同一事务。REQUIREDrollback


我们可以通过测试轻松验证行为,并查看结果如何随传播水平而变化:

@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:我们预计所有内容都已回滚,并且后备商店保持不变。


答案 2

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

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

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

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

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


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

多事务的问题

方案 1.如果 T1 事务从表 A1 中读取由另一个并发事务 T2 写入的数据。如果在 T2 回滚途中,则 T1 获取的数据无效。例如,a=2 是原始数据。如果 T1 读取由 T2 写入的 a=1。如果 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();
    }
   
}

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


推荐