有人能通过真实的例子解释@Transactional注释中的隔离和传播参数是用于什么吗?

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


当前回答

You almost never want to use Read Uncommited since it's not really ACID compliant. Read Commmited is a good default starting place. Repeatable Read is probably only needed in reporting, rollup or aggregation scenarios. Note that many DBs, postgres included don't actually support Repeatable Read, you have to use Serializable instead. Serializable is useful for things that you know have to happen completely independently of anything else; think of it like synchronized in Java. Serializable goes hand in hand with REQUIRES_NEW propagation.

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

其他回答

Propagation_required = 0;如果方法M1的DataSourceTransactionObject T1已经启动。如果需要另一个Method 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;如果方法M1的DataSourceTransactionObject T1已经启动。如果另一个方法M2同时运行。那么M2不应该在事务上下文中运行。T1暂停,直到M2完成。

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


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

多重事务的问题

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

Scenario2。如果T1事务从表A1读取数据。如果另一个并发事务(T2)更新表A1上的数据。那么T1读取的数据是 不同于表格A1。因为T2更新了表A1上的数据。例如,如果T1读取a=1, T2更新a=2。然后一个! = b。

场景3。如果T1事务从表A1读取一定行数的数据。如果另一个并发事务(T2)在表A1上插入更多行。的 T1读取的行数与表A1上的行数不同。

场景1称为脏读。

场景2称为不可重复读取。

场景3被称为幻影读取。

因此,隔离级别是可以防止场景1、场景2、场景3的扩展。 您可以通过实现锁定来获得完整的隔离级别。这阻止了对同一数据的并发读写。但它会影响性能。隔离级别取决于应用程序与应用程序之间所需的隔离程度。

ISOLATION_READ_UNCOMMITTED:允许读取尚未提交的更改。它遭受场景一,场景二,场景三。

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

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

隔离级别定义了一个事务对某个数据存储库所做的更改如何影响其他同时并发的事务,以及更改后的数据如何以及何时对其他事务可用。当我们使用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行为将在事务范围内执行。如果没有已经打开的事务,该方法将以非事务的方式执行。

其他答案对每个参数都给出了足够的解释;但是,您要求的是一个真实世界的示例,下面是一个阐明不同传播选项的目的的示例:

Suppose you're in charge of implementing a

注册服务

in which a confirmation e-mail is sent to the user. You come up with two service objects, one for

招收

the user and one for

发送

e-mails, which the latter is called inside the first one. For example something like this:
/* 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服务器宕机、无效电子邮件或其他原因)。你可能不希望整个过程回滚,比如从数据库中删除用户信息或其他东西;因此,在单独的事务中调用第二个服务。

Back to our example, this time you are concerned about the database security, so you define your DAO classes this way:
/* User DAO */
@Transactional(Propagation=MANDATORY)
class UserDAO{
 // some CRUD methods
}

这意味着无论何时创建一个DAO对象,以及因此对DB的潜在访问,我们都需要确保调用是从我们的一个服务内部发出的,这意味着应该存在一个活动事务;否则会出现异常。因此,传播类型为MANDATORY。

You almost never want to use Read Uncommited since it's not really ACID compliant. Read Commmited is a good default starting place. Repeatable Read is probably only needed in reporting, rollup or aggregation scenarios. Note that many DBs, postgres included don't actually support Repeatable Read, you have to use Serializable instead. Serializable is useful for things that you know have to happen completely independently of anything else; think of it like synchronized in Java. Serializable goes hand in hand with REQUIRES_NEW propagation.

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

我们可以为此添加:

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