【问题标题】:Spring @Transactional - isolation, propagationSpring @Transactional - 隔离、传播
【发布时间】:2012-01-19 10:09:13
【问题描述】:

有人可以通过实际示例解释@Transactional 注释中的 isolationpropagation 参数是什么吗?

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

【问题讨论】:

    标签: java spring transactional isolation propagation


    【解决方案1】:

    好问题,虽然不是一个容易回答的问题。

    Propagation

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

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

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

    Isolation

    定义交易之间的数据契约。

    • ISOLATION_READ_UNCOMMITTED:允许脏读。
    • ISOLATION_READ_COMMITTED:不允许脏读。
    • ISOLATION_REPEATABLE_READ:如果在同一个事务中读取一行两次,结果总是一样的。
    • ISOLATION_SERIALIZABLE:按顺序执行所有事务。

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

    默认值可能因不同的数据库而异。例如,MariaDBREPEATABLE 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,则事务would remain open 如果在进入例程时事务已经打开。 另请注意,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:我们希望一切都回滚,并且后备存储没有改变。

    【讨论】:

    • 最后一个链接与您所说的有什么关系?根据链接的文档,似乎是会话说明了当前事务是什么,而不是会话工厂。
    • @Donal,抱歉,不清楚。我的意思是,由于添加了sessionFactory.getCurrentTransaction(),因此不再需要运行HibernateTemplate 来管理事务。我删除了它:)
    • 我的问题只是关于链接指向的位置,真的。 :-)
    • 如何获取当前事务中所做的更改-stackoverflow.com/questions/36132667/…
    【解决方案2】:

    PROPAGATION_REQUIRED = 0;如果 DataSourceTransactionObject T1 已经为方法 M1 启动。如果需要另一个 Method M2 Transaction 对象,则不会创建新的 Transaction 对象。相同的对象 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 事务从表 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();
        }
       
    }
    

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

    【讨论】:

    • 如何获取当前事务中所做的更改-stackoverflow.com/questions/36132667/…
    • 隔离级别传播之间的交互是什么?如果方法 1 以隔离级别(例如 READ_COMMITTED)启动事务,然后以 REPEATABLE_READ 级别调用方法 2,那么方法 2 肯定必须在其自己的新事务中执行,而不管它指定什么传播行为(例如,仅 REQUIRED)?
    • 这真的很晚了,但是当 PROPAGATION_REQUIRES_NEW 时,如果另一个新调用发生在 M1 上,T1(由 M1 使用)会发生什么? (比如 M1.1)
    • @CornelMasson 我相信我有一个与您非常相似的问题。我为它创建了a specific SO question
    【解决方案3】:

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

    假设您负责实施注册服务,其中向用户发送确认电子邮件。您提出了两个服务对象,一个用于注册用户,一个用于发送电子邮件,后者在第一个内部调用。例如这样的:
    /* 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 类型的。

    【讨论】:

    【解决方案4】:

    隔离级别定义了一个事务对某些数据存储库所做的更改如何影响其他并发并发事务,以及更改的数据如何以及何时可用于其他事务。当我们使用 Spring 框架定义事务时,我们还可以配置在哪个隔离级别执行相同的事务。

    @Transactional(isolation=Isolation.READ_COMMITTED)
    public void someTransactionalMethod(Object obj) {
    
    }
    

    READ_UNCOMMITTED 隔离级别表明一个事务可以读取其他事务仍未提交的数据。

    READ_COMMITTED 隔离级别表明事务无法读取其他事务尚未提交的数据。

    REPEATABLE_READ 隔离级别规定,如果一个事务从数据库中多次读取一条记录,那么所有这些读取操作的结果必须始终相同。

    SERIALIZABLE 隔离级别是所有隔离级别中限制性最强的。事务在所有级别(读取、范围和写入锁定)的锁定下执行,因此它们看起来好像是以序列化方式执行的。

    传播是决定业务方法应如何封装在逻辑或物理事务中的能力。

    Spring REQUIRED 行为意味着如果当前 bean 方法执行上下文中已经打开了事务,则将使用相同的事务。

    REQUIRES_NEW 行为意味着容器总是会创建一个新的物理事务。

    NESTED 行为使嵌套的 Spring 事务使用相同的物理事务,但在嵌套调用之间设置保存点,因此内部事务也可以独立于外部事务回滚。

    MANDATORY 行为表明现有打开的事务必须已经存在。否则容器会抛出异常。

    NEVER 行为表明现有打开的事务必须不存在。如果事务存在,容器将抛出异常。

    NOT_SUPPORTED 行为将在任何事务范围之外执行。如果一个打开的事务已经存在,它将被暂停。

    如果已打开的事务已存在,则支持行为将在事务范围内执行。如果没有已经打开的事务,该方法无论如何都会以非事务方式执行。

    【讨论】:

    • 如果您可以添加何时使用哪个,会更有益。
    • 举几个例子,对初学者很有帮助
    • 请解开这个疑问,隔离级别是只关注数据库操作还是服务层内部发生的所有操作?如果连接到服务层的所有操作,read_uncommitted是什么意思?
    【解决方案5】:

    一个事务代表一个数据库的工作单元。具有自己的 txn(或没有 txn)的多个服务中的事务行为称为事务传播事务隔离定义了两个事务同时作用于同一个数据库实体时的数据库状态。

    在 springTransactionDefinition 接口中定义了 Spring 兼容的事务属性。 @Transactional 注解描述了方法或类的事务属性。

    @Autowired
    private TestDAO testDAO;
    
    @Transactional(propagation=TransactionDefinition.PROPAGATION_REQUIRED,isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
    public void someTransactionalMethod(User user) {
    
      // Interact with testDAO
    
    }
    

    传播(复制):用于交易间关系。 (类似于java线程间通信)

    +-------+---------------------------+------------------------------------------------------------------------------------------------------+
    | value |        Propagation        |                                             Description                                              |
    +-------+---------------------------+------------------------------------------------------------------------------------------------------+
    |    -1 | TIMEOUT_DEFAULT           | Use the default timeout of the underlying transaction system, or none if timeouts are not supported. |
    |     0 | PROPAGATION_REQUIRED      | Support a current transaction; create a new one if none exists.                                      |
    |     1 | PROPAGATION_SUPPORTS      | Support a current transaction; execute non-transactionally if none exists.                           |
    |     2 | PROPAGATION_MANDATORY     | Support a current transaction; throw an exception if no current transaction exists.                  |
    |     3 | PROPAGATION_REQUIRES_NEW  | Create a new transaction, suspending the current transaction if one exists.                          |
    |     4 | PROPAGATION_NOT_SUPPORTED | Do not support a current transaction; rather always execute non-transactionally.                     |
    |     5 | PROPAGATION_NEVER         | Do not support a current transaction; throw an exception if a current transaction exists.            |
    |     6 | PROPAGATION_NESTED        | Execute within a nested transaction if a current transaction exists.                                 |
    +-------+---------------------------+------------------------------------------------------------------------------------------------------+
    

    隔离:隔离是数据库事务的 ACID(原子性、一致性、隔离性、持久性)属性之一。隔离决定了事务完整性如何对其他用户和系统可见。它用于资源锁定,即并发控制,确保只有一个事务可以在给定点访问资源。

    锁定感知:隔离级别决定了锁定的持续时间。

    +---------------------------+-------------------+-------------+-------------+------------------------+
    | Isolation Level Mode      |  Read             |   Insert    |   Update    |       Lock Scope       |
    +---------------------------+-------------------+-------------+-------------+------------------------+
    | READ_UNCOMMITTED          |  uncommitted data | Allowed     | Allowed     | No Lock                |
    | READ_COMMITTED (Default)  |   committed data  | Allowed     | Allowed     | Lock on Committed data |
    | REPEATABLE_READ           |   committed data  | Allowed     | Not Allowed | Lock on block of table |
    | SERIALIZABLE              |   committed data  | Not Allowed | Not Allowed | Lock on full table     |
    +---------------------------+-------------------+-------------+-------------+------------------------+
    

    阅读感知:主要出现以下3种问题:

    • 脏读:从另一个 tx(transaction) 中读取未提交的数据。
    • 不可重复读取:从另一个 tx 读取提交的 UPDATES
    • 幻读:从另一个 tx 读取提交的 INSERTS 和/或 DELETES

    不同类型读取的隔离级别:

    +---------------------------+----------------+----------------------+----------------+
    | Isolation Level Mode      |  Dirty reads   | Non-repeatable reads | Phantoms reads |
    +---------------------------+----------------+----------------------+----------------+
    | READ_UNCOMMITTED          | allows         | allows               | allows         |
    | READ_COMMITTED (Default)  | prevents       | allows               | allows         |
    | REPEATABLE_READ           | prevents       | prevents             | allows         |
    | SERIALIZABLE              | prevents       | prevents             | prevents       |
    +---------------------------+----------------+----------------------+----------------+
    

    for examples

    【讨论】:

    【解决方案6】:

    您几乎不想使用Read Uncommited,因为它并不真正符合ACIDRead Commmited 是一个很好的默认起点。 Repeatable Read 可能仅在报告、汇总或聚合场景中需要。请注意,许多数据库(包括 postgres)实际上并不支持可重复读取,您必须改用 SerializableSerializable 对于您知道必须完全独立于其他任何事情发生的事情很有用;把它想象成 Java 中的synchronized。可序列化与REQUIRES_NEW 传播密切相关。

    我将REQUIRES 用于所有运行 UPDATE 或 DELETE 查询的函数以及“服务”级函数。对于只运行 SELECT 的 DAO 级函数,我使用SUPPORTS,如果一个已经启动(即从服务函数调用),它将参与 TX。

    【讨论】:

      【解决方案7】:

      事务隔离和事务传播虽然相关但显然是两个截然不同的概念。在这两种情况下,默认值都是通过使用Declarative transaction managementProgrammatic transaction management 在客户端边界组件处定制的。每个隔离级别和传播属性的详细信息可以在下面的参考链接中找到。

      Transaction Isolation

      对于给定两个或多个正在运行的事务/与数据库的连接,一个事务中的查询如何以及何时做出更改会影响/对不同事务中的查询可见。它还涉及将使用哪种数据库记录锁定来隔离此事务中的更改与其他事务,反之亦然。这通常由参与事务的数据库/资源​​实现。

      .

      Transaction Propagation

      在任何给定请求/处理的企业应用程序中,都涉及许多组件来完成工作。其中一些组件标记了将在各个组件及其子组件中使用的事务的边界(开始/结束)。对于组件的这个事务边界,事务传播指定各个组件是否将参与事务以及如果调用组件已经具有或没有已创建/启动的事务会发生什么。这与 Java EE 事务属性相同。这通常由客户端事务/连接管理器实现。

      参考:

      【讨论】:

      • 太好了,所有信息都集中在一个地方,链接非常有用,谢谢@Gladwin Burboz
      【解决方案8】:

      我用不同的传播模式运行了outerMethodmethod_1method_2

      以下是不同传播模式的输出。

      外部方法

          @Transactional
          @Override
          public void outerMethod() {
              customerProfileDAO.method_1();
              iWorkflowDetailDao.method_2();
          }
      

      方法_1

          @Transactional(propagation=Propagation.MANDATORY)
          public void method_1() {
              Session session = null;
              try {
                  session = getSession();
                  Temp entity = new Temp(0l, "XXX");
                  session.save(entity);
                  System.out.println("Method - 1 Id "+entity.getId());
              } finally {
                  if (session != null && session.isOpen()) {
      
                  }
              }
          }
      

      方法_2

          @Transactional()
          @Override
          public void method_2() {
              Session session = null;
              try {
                  session = getSession();
                  Temp entity = new Temp(0l, "CCC");
                  session.save(entity);
                  int i = 1/0;
                  System.out.println("Method - 2 Id "+entity.getId());
              } finally {
                  if (session != null && session.isOpen()) {
      
                  }
              }
          }
      
        • OuterMethod - 没有事务
        • Method_1 - 传播.MANDATORY) -
        • Method_2 - 仅事务注释
        • 输出:method_1 将抛出没有现有事务的异常

        • OuterMethod - 没有事务
        • Method_1 - 仅事务注释
        • Method_2 - Propagation.MANDATORY)
        • 输出:method_2 将抛出没有现有事务的异常
        • 输出:method_1 将记录保存在数据库中。

        • OuterMethod - 带有事务
        • Method_1 - 仅事务注释
        • Method_2 - Propagation.MANDATORY)
        • 输出:method_2 将记录保存在数据库中。
        • 输出:method_1 将记录保存在数据库中。 -- 这里主要外部现有事务用于方法1和2

        • OuterMethod - 带有事务
        • Method_1 - 传播.MANDATORY)
        • Method_2 - 仅事务注释并引发异常
        • 输出:数据库中没有记录意味着回滚已完成。

        • OuterMethod - 带有事务
        • 方法_1 - 传播.REQUIRES_NEW)
        • Method_2 - Propagation.REQUIRES_NEW) 并引发 1/0 异常
        • 输出:method_2 将引发异常,因此不会保留 method_2 记录。
        • 输出:method_1 将记录保存在数据库中。
        • 输出:method_1 没有回滚

      【讨论】:

        【解决方案9】:

        我们可以为此添加:

        @Transactional(readOnly = true)
        public class Banking_CustomerService implements CustomerService {
        
            public Customer getDetail(String customername) {
                // do something
            }
        
            // these settings have precedence for this method
            @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
            public void updateCustomer(Customer customer) {
                // do something
            }
        }
        

        【讨论】:

          【解决方案10】:

          你可以这样使用:

          @Transactional(propagation = Propagation.REQUIRES_NEW)
          public EventMessage<ModificaOperativitaRapporto> activate(EventMessage<ModificaOperativitaRapporto> eventMessage) {
          //here some transaction related code
          }
          

          你也可以用这个东西:

          public interface TransactionStatus extends SavepointManager {
              boolean isNewTransaction();
              boolean hasSavepoint();
              void setRollbackOnly();
              boolean isRollbackOnly();
              void flush();
              boolean isCompleted();
          }
          

          【讨论】:

            猜你喜欢
            • 2018-05-14
            • 2010-12-09
            • 2015-07-24
            • 2017-06-11
            • 2017-01-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多