Spring @Transactional - 隔离,传播

时间:2011-12-13 14:28:48

标签: java spring transactional isolation propagation

有人能解释一下隔离& 传播参数是通过实际示例在@Transactional注释中进行的?

基本上我应该选择更改默认值的时间和原因。

10 个答案:

答案 0 :(得分:367)

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

Propagation

定义交易如何相互关联。常见选项

  • Required:代码将始终在事务中运行。创建新事务或重用一个(如果可用)。
  • Requires_new:代码将始终在新事务中运行。暂停当前​​事务(如果存在)。

Isolation

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

  • 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 Comitted,它只允许您读取已经由其他正在运行的事务进行评估的值,并结合Required的传播级别。如果您的应用程序有其他需求,那么您可以从那里工作。


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

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。 另请注意,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() NOT 回滚,因为它创建了自己的子事务。

  • Required我们希望所有内容都回滚并支持商店不变。

答案 1 :(得分:245)

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 ;这些方法都不在事务上下文中运行。

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

多笔交易的问题

场景1 。如果T1事务从表A1读取由另一个并发事务T2写入的数据。如果在T2回滚的路上,获取的数据由 T1是无效的.Eg a = 2是原始数据。如果T1读取由T2写入的a = 1.如果T2回滚那么a = 1将在DB中回滚到a = 2.但是,现在,T1有一个= 1但在DB中 表将其更改为a = 2.

场景2 。如果T1事务从表A1读取数据。如果另一个并发事务(T2)更新表A1上的数据。那么T1读取的数据是 与表A1不同。因为T2已更新表A1.E.g上的数据,如果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 :(得分:91)

其他答案给出了对每个参数的充分解释;不过你要求一个真实世界的例子,这里有一个澄清不同传播选项的目的:

假设您负责实施注册服务,其中向用户发送确认电子邮件。您提出了两个服务对象,一个用于注册用户,另一个用于发送电子邮件,后者在第一个内部调用。例如:

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

答案 3 :(得分:45)

隔离级别定义了一个事务对某些数据存储库所做的更改如何影响其他并发并发事务,以及这些更改后的数据如何以及何时可用于其他事务。当我们使用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 :(得分:19)

您几乎从不想使用Read Uncommited,因为它不符合ACIDRead Commmited是一个很好的默认起点。 Repeatable Read可能仅在报告,汇总或聚合方案中需要。请注意,包含postgres的许多DB实际上不支持Repeatable Read,您必须使用SerializableSerializable对于你知道必须完全独立于其他任何事情的事情是有用的;把它想象成Java中的synchronized。 Serializable与REQUIRES_NEW传播密切相关。

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

答案 5 :(得分:12)

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

Transaction Isolation

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

Transaction Propagation

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

<强>参考:

答案 6 :(得分:8)

交易代表数据库的工作单位。

在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(Atomicity,Consistency,Isolation,Durability)属性之一。隔离确定了其他用户和系统对事务完整性的可见性。它用于资源锁定,即并发控制,确保只有一个事务可以在给定点访问资源。

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

+---------------------------+-------------------+-------------+-------------+------------------------+
| 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(事务)中读取未提交的数据。
  • 不可重复的读取:从另一个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

答案 7 :(得分:7)

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

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

  • 外部方法

    @Transactional
    @Override
    public void outerMethod() {
        customerProfileDAO.method_1();
        iWorkflowDetailDao.method_2();
    }
    
  • Method_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()) {
            }
        }
    }
    
  • Method_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 - Propagation.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 - Propagation.MANDATORY) -
      • method_2 - 仅限交易注释并抛出异常
      • 输出:数据库中没有记录持续存在意味着回滚完成。
      • outerMethod - 使用交易
      • method_1 - Propagation.REQUIRES_NEW)
      • method_2 - Propagation.REQUIRES_NEW)并抛出1/0异常
      • 输出:method_2将抛出异常,因此method_2记录不会保留。
      • 输出:method_1将在数据库中保留记录。
      • 输出:method_1
      • 没有回滚

答案 8 :(得分: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
    }
}

答案 9 :(得分: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();
}