Spring @Transactional - 隔离,传播

Mat*_* B. 412 java spring transactional isolation propagation

有人可以通过现实世界的例子解释注释中的隔离传播参数@Transactional.基本上何时以及为什么我应该选择更改其默认值.

Joh*_*erg 410

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

传播

定义事务彼此之间的关系.常见选项

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

隔离

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

  • Read Uncommitted:允许脏读
  • Read Committed:不允许脏读
  • Repeatable Read:如果在同一个transaciton中读取两次行,则结果将始终相同
  • Serializable:按顺序执行所有事务

不同级别在多线程应用程序中具有不同的性能特征.我想如果您理解这个dirty reads概念,您将能够选择一个好的选择.


可能发生脏读的示例

  thread 1   thread 2      
      |         |
    write(x)    |
      |         |
      |        read(x)
      |         |
    rollback    |
      v         v 
           value (x) is now dirty (incorrect)
Run Code Online (Sandbox Code Playgroud)

因此,一个合理的默认值(如果可以声明)可以是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();
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我们使用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 ... 
}
Run Code Online (Sandbox Code Playgroud)

具有传播水平

  • Requires new我们希望fooService.provideService(),因为它创造了它自己的子事务回滚.

  • Required 我们希望一切都回滚,支持商店不变.


abi*_*rai 271

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读取a =由T2写入的1.如果T2回滚则a = 1将在DB中回滚到a = 2.但是,现在,T1具有= 1但在DB表中它被改变为a = 2.

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

}
Run Code Online (Sandbox Code Playgroud)

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

  • *隔离级别*和*传播*之间的相互作用是什么?如果方法1启动具有隔离级别的事务(例如,READ_COMMITTED),然后调用具有级别REPEATABLE_READ的method2,则无论方法2必须在其自己的新事务中执行,而不管它指定了什么传播行为(例如,仅需要)? (2认同)

ye9*_*ane 104

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

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

/* 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)
 }
}
Run Code Online (Sandbox Code Playgroud)

您可能已经注意到第二个服务的传播类型为REQUIRES_NEW,而且它可能会抛出异常(SMTP服务器关闭,无效的电子邮件或其他原因).您可能不希望整个过程回滚,如从数据库或其他东西中删除用户信息; 因此,您在单独的事务中调用第二个服务.

回到我们的示例,这次您关注数据库安全性,因此您可以通过以下方式定义DAO类:

/* User DAO */
@Transactional(Propagation=MANDATORY)
class UserDAO{
 // some CRUD methods
}
Run Code Online (Sandbox Code Playgroud)

这意味着每当创建DAO对象并因此创建对db的潜在访问时,我们需要确保调用是从我们的一个服务内部进行的,这意味着应该存在实时事务; 否则会发生异常.因此传播的类型为MANDATORY.

  • REQUIRES_NEW的完美示例. (23认同)
  • 很好的解释!顺便说一下,传播的默认值是什么?如果你能给出一个像这样的例子来隔离也会更好.非常感谢. (5认同)
  • @PrakashK默认是必需的.(https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Propagation.html) (5认同)

reo*_*eos 56

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

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

}
Run Code Online (Sandbox Code Playgroud)

READ_UNCOMMITTED隔离级别指出事务可能读取其他事务仍未提交的数据.

READ_COMMITTED隔离级别指出事务无法读取其他事务尚未提交的数据.

REPEATABLE_READ隔离级别指出,如果事务多次从数据库中读取一条记录,则所有这些读取操作的结果必须始终相同.

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

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

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

REQUIRES_NEW行为意味着容器将始终创建新的物理事务.

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

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

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

NOT_SUPPORTED行为将在任何事务的范围之外执行.如果已打开的交易已存在,则会暂停.

如果已打开的事务已存在,则SUPPORTS行为将在事务范围内执行.如果没有已经打开的事务,该方法将以任何方式执行,但是以非事务方式执行.

  • 如果你可以添加何时使用哪一个,那将更有益. (4认同)

Ang*_*own 19

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

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


Pre*_*raj 18

事务表示与数据库一起工作的单元.

在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

}
Run Code Online (Sandbox Code Playgroud)

传播(Reproduction):用于交易间关系.(类似于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.                                 |
+-------+---------------------------+------------------------------------------------------------------------------------------------------+
Run Code Online (Sandbox Code Playgroud)

隔离:隔离是数据库事务的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     |
+---------------------------+-------------------+-------------+-------------+------------------------+
Run Code Online (Sandbox Code Playgroud)

阅读感知:出现以下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       |
+---------------------------+----------------+----------------------+----------------+
Run Code Online (Sandbox Code Playgroud)

举些例子


Gla*_*boz 13

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

交易隔离

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

.

交易传播

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

参考:


NIr*_*odi 7

我已经运行了outerMethod,method_1并且method_2具有不同的传播模式.

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

  • 外部方法

    @Transactional
    @Override
    public void outerMethod() {
        customerProfileDAO.method_1();
        iWorkflowDetailDao.method_2();
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 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()) {
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 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()) {
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
      • 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没有回滚