Spring @事务性-隔离、传播

谁能解释一下传播参数是在@Transactional注释通过现实世界的例子?

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

369370 次浏览

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

传播

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

  • REQUIRED:代码总是在事务中运行。创建一个新的事务或重用一个可用的事务。
  • REQUIRES_NEW:代码总是在一个新的事务中运行。如果存在当前事务,则暂停当前事务。

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

隔离

定义事务之间的数据契约。

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

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

缺省值在不同数据库之间可能有所不同。例如,对于MariaDB,它是REPEATABLE 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,如果事务在进入例程时已经打开,则使用事务将继续开放。 还要注意,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:我们期望所有东西都回滚了,备份存储没有改变。

你几乎永远不想使用Read Uncommited,因为它不是真正的ACID兼容。Read Commmited是一个很好的默认起始位置。Repeatable Read可能只在报告、汇总或聚合场景中需要。请注意,许多db, postgres实际上不支持可重复读取,你必须使用Serializable代替。Serializable对于那些你知道必须完全独立于其他事情发生的事情是有用的;就像Java中的synchronized。可序列化与REQUIRES_NEW传播密切相关。

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

# EYZ0;如果方法M1的DataSourceTransactionObject T1已经启动。如果需要另一个Method M2事务对象,则不创建新的事务对象。同样的对象T1用于M2。

< p > # EYZ0;方法必须在事务中运行。如果没有

.当前事务正在进行中,将引发异常

# EYZ0;如果DataSourceTransactionObject T1已经为方法M1启动,并且正在进行中(执行方法M1)。如果另一个方法M2开始执行,那么T1将在方法M2的持续时间内挂起,并为M2创建新的DataSourceTransactionObject T2。M2在它自己的事务上下文中运行。

# EYZ0;如果方法M1的DataSourceTransactionObject T1已经启动。如果另一个方法M2同时运行。那么M2不应该在事务上下文中运行。T1暂停,直到M2完成。

# EYZ0;没有一个方法在事务上下文中运行。


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

多重事务的问题

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

< p > # EYZ0。如果T1事务从表A1读取数据。如果另一个并发事务(T2)更新表A1上的数据。那么T1读取的数据是 不同于表格A1。因为T2更新了表A1上的数据。例如,如果T1读取a=1, T2更新a=2。然后一个! = b . < / p > < p > # EYZ0。如果T1事务从表A1读取一定行数的数据。如果另一个并发事务(T2)在表A1上插入更多行。的 T1读取的行数与表A1上的行数不同

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

}

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

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

假设您负责实现注册服务,其中向用户发送确认电子邮件。您将得到两个服务对象,一个用于招收用户,另一个用于发送电子邮件,后者在第一个服务对象中调用。例如:

/* 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的潜在访问,我们都需要确保调用是从我们的一个服务内部发出的,这意味着应该存在一个活动事务;否则会出现异常。因此传播类型为强制性的

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

事务隔离

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

事务传播

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

参考:

我已经用不同的传播模式运行了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 -没有事务
    • 传播。mandatory) -
    • Method_2 -仅事务注释
    • method_1将抛出不存在事务的异常

    • OuterMethod -没有事务
    • Method_1 -仅事务注释
    • 方法2 -传播。mandatory)
    • 输出:method_2将抛出不存在事务的异常
    • 输出:method_1将保存数据库中的记录。

    • OuterMethod -带有事务
    • Method_1 -仅事务注释
    • 方法2 -传播。mandatory)
    • 输出:method_2将记录保存在数据库中。
    • 输出:method_1将保存数据库中的记录。 方法1和方法2都使用了外部现有事务

    • OuterMethod -带有事务
    • 传播。mandatory)
    • Method_2 -仅事务注释并抛出异常
    • 输出:没有记录保存在数据库中意味着回滚完成。

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

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

你可以这样用:

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

我们可以为此添加:

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

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

在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(事务)读取未提交的数据。
  • 不可重复读:从另一个tx读取已提交的UPDATES
  • 幻读:从另一个tx读取已提交的INSERTS和/或DELETES

下面的图表显示了哪个事务隔离级别可以解决哪些并发问题:

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

for example