Spring @Transactional - 隔离、传播

新手上路,请多包涵

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

基本上什么时候以及为什么我应该选择更改它们的默认值。

原文由 MatBanik 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 619
2 个回答

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

传播

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

  • REQUIRED :代码将始终在事务中运行。创建一个新事务或重用一个(如果有)。
  • REQUIRES_NEW :代码将始终在新事务中运行。如果存在,则暂停当前事务。

@Transactional 的默认值是 REQUIRED ,这通常是你想要的。

隔离

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

  • 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_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 ,如果在进入例程时事务已经打开,则事务 将保持打开 状态。另请注意, 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 :我们希望一切都回滚并且后备存储没有改变。

原文由 Johan Sjöberg 发布,翻译遵循 CC BY-SA 4.0 许可协议

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

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

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

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

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


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

多次交易的问题

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

}

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

原文由 abishkar bhattarai 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题