Spring @Transactional-隔离,传播


Answers:


442

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

传播

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

  • 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:我们希望一切都回滚而后备存储保持不变。


最后一个链接与您所谈论的内容有何关系?根据链接的文档,似乎是会话在说明当前事务是什么,而不是会话工厂。
Donal Fellows

@Donal,哦,抱歉,不清楚。我的观点是,自从sessionFactory.getCurrentTransaction()添加以来,不再需要运行HibernateTemplate来管理事务。我将其删除了:)
JohanSjöberg

我的问题确实是关于链接指向的位置。:-)
Donal Fellows


303

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 = T2回写的1

方案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();
    }

}

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



2
隔离级别传播之间有什么相互作用?如果方法1以隔离级别(例如READ_COMMITTED)启动事务,然后再以REPEATABLE_READ级别调用method2,则方法2是否必须在其自己的新事务中执行,无论它指定了哪种传播行为(例如,仅是必需的)?
Cornel Masson

这确实是晚了,但是当PROPAGATION_REQUIRES_NEW时,如果M1发生另一个新的呼叫,T1(M1使用的)会发生什么?(例如M1.1)
Tim Z.

115

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

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

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


26
REQUIRES_NEW的完美范例。
拉维·萨普利雅

5
很好的解释!顺便说一句,默认的传播方式是什么?如果您也可以给出这样的示例以进行隔离,则效果也会更好。非常感谢。
Prakash K


59

隔离级别定义一个事务对某个数据存储库所做的更改如何影响其他同时进行的并发事务,以及更改后的数据如何以及何时可用于其他事务。当我们使用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行为将在任何事务范围之外执行。如果已经存在一个已打开的事务,它将被暂停。

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


4
如果您可以添加何时使用哪一个,将会更加有益。
Kumar Manish

举一些例子,这对初学者会很有帮助
nitinsridar '19

23

事务表示数据库的工作单元。

在Spring TransactionDefinition界面中,该界面定义了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(事务)读取未提交的数据。
  • 不可重复读取UPDATES从另一个TX 提交的读取。
  • 幻像读取:读取已提交INSERTS和/或DELETES从另一个TX 读取

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

+---------------------------+----------------+----------------------+----------------+
| 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       |
+---------------------------+----------------+----------------------+----------------+

举些例子


19

您几乎永远不想使用Read Uncommited它,因为它实际上并不ACID合规。Read Commmited是一个很好的默认起点。Repeatable Read仅在报告,汇总或聚合方案中才需要。请注意,包括Postgres在内的许多数据库实际上并不支持重复读取,Serializable而必须使用它。Serializable对于您必须完全独立于其他任何事情发生的事情很有用;就像synchronized在Java中那样想。可序列化与REQUIRES_NEW传播齐头并进。

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


13

事务隔离和事务传播虽然相关,但是显然是两个非常不同的概念。在这两种情况下,都可以使用声明式事务管理程序化事务管理在客户端边界组件上自定义默认值。可以在下面的参考链接中找到每个隔离级别和传播属性的详细信息。

交易隔离

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

交易传播

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

参考:


1
太好了,所有信息都集中在一个地方,链接非常有帮助,谢谢@Gladwin Burboz
nitinsridar '19

7

我已经运行了outerMethodmethod_1并且method_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()) {
            }
        }
    }
      • outsideMethod-没有事务
      • method_1-Propagation.MANDATORY)-
      • method_2-仅交易注释
      • 输出:method_1将抛出异常,表明没有现有事务
      • outsideMethod-没有事务
      • method_1-仅交易注释
      • method_2-传播。(必填)
      • 输出:method_2将抛出异常,表明没有现有事务
      • 输出:method_1将永久记录在数据库中。
      • outsideMethod-有交易
      • method_1-仅交易注释
      • method_2-传播。(必填)
      • 输出:method_2将永久记录在数据库中。
      • 输出:method_1将永久记录在数据库中。-此处方法1和2均使用的主要外部现有交易记录
      • outsideMethod-有交易
      • method_1-Propagation.MANDATORY)-
      • method_2-仅事务注释,并引发异常
      • 输出:数据库中没有任何持久记录意味着回滚已完成。
      • outsideMethod-有交易
      • method_1-传播。REQUIRES_NEW)
      • method_2-Propagation.REQUIRES_NEW)并抛出1/0异常
      • 输出:method_2将引发异常,因此method_2记录不持久。
      • 输出:method_1将永久记录在数据库中。
      • 输出:method_1没有回滚

3

我们可以为此添加:

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

1

您可以这样使用:

@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();
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.