Spring @Transactional – 隔离,传播

有人可以通过真实世界的例子来解释@Transactional注释中的隔离传播参数。 基本上什么时候和为什么我应该select改变他们的默认值。

好问题,虽然不是一个小问题要回答。

传播

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

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

隔离

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

  • Read Uncommitted :允许脏读
  • Read Committed :不允许脏读
  • Repeatable Read :如果一行在同一个交易所中被读两次,结果总是相同的
  • Serializable :按顺序执行所有事务

multithreading应用程序中的不同级别具有不同的性能特征。 我想如果你明白dirty reads概念,你将能够select一个好的select。


可能发生脏读的示例

  thread 1 thread 2 | | write(x) | | | | read(x) | | rollback | vv value (x) is now dirty (incorrect) 

因此,一个理智的默认值(如果可以声明的话)可以是Read Comitted ,它只允许你读取已经被其他正在运行的事务处理过的值,并结合一个传播级别Required 。 那么你可以从那里工作,如果你的应用程序有其他的需求。


一个实际的例子,在进入提供provideService程序时总是创build一个新的事务,并在离开时完成。

 public class FooService { private Repository repo1; private Repository repo2; @Transactional(propagation=Propagation.REQUIRES_NEW) public void provideService() { repo1.retrieveFoo(); repo2.retrieveFoo(); } } 

如果我们使用了Required ,那么如果事务在进入例程时已经打开,事务将保持打开状态。 还要注意, rollback的结果可能会不同,因为多个执行可能参与相同的事务。


我们可以很容易地通过一个testing来validation行为,看看结果与传播水平有何不同

 @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() 不会回滚,因为它创build它自己的子事务。

  • Required我们期望一切都回滚和支持存储不变。

PROPAGATION_REQUIRED = 0 ; 如果DataSourceTransactionObject T1已为方法M1启动。如果对于另一个方法M2 Transaction对象是必需的,则不会创build新的Transaction对象。同名对象T1用于M2

PROPAGATION_MANDATORY = 2 ; 方法必须在事务内运行。 如果没有正在进行的事务,则会抛出exception

PROPAGATION_REQUIRES_NEW = 3 ; 如果对于方法M1已经启动了DataSourceTransactionObject T1并且正在进行(正在执行方法M1)。如果另一个方法M2开始执行,则在方法M2的持续时间内暂停T1,对于M2.M2在其自身的事务上下文中运行新的DataSourceTransactionObject T2

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

PROPAGATION_NEVER = 5 ; 没有任何方法在事务上下文中运行。

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

多重交易的问题

场景1:如果T1事务从表A1中读取的数据是由另一个并发事务T2写入的。如果在T2回滚的方式中,T1获得的数据是无效的。例如,aa = 2是原始数据。如果T1读取a = 1是由T 2写的。如果T2回滚,那么a = 1将在数据库中回滚到a = 2。但是,现在,T1具有a = 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的扩展。 您可以通过实现locking来获得完整的隔离级别,即阻止对同一数据的并发读取和写入操作,但会影响性能。隔离级别取决于应用程序对应用程序的隔离级别。

ISOLATION_READ_UNCOMMITTED :允许读取尚未提交的更改。scheme1,scheme2,scheme3

ISOLATION_READ_COMMITTED :允许从已提交的并发事务中读取数据。 它可能遭受场景2和场景3.因为其他事务可能正在更新数据。

ISOLATION_REPEATABLE_READ :同一个字段的多次读取将产生相同的结果,直到它被自己改变。它可能会遭受场景3.因为其他事务可能正在插入数据

ISOLATION_SERIALIZABLE :场景1,场景2,场景3不会发生,是完全隔离的,它包含完全locking,由于locking而影响性能。

你可以testing使用

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

您可以debugging并查看具有不同隔离和传播值的结果。

关于每个参数的足够解释由其他答案给出; 然而,你要求一个真实世界的例子,下面是澄清不同的传播select的目的:

假设您负责实施注册服务,在该服务中将确认电子邮件发送给用户。 你想出两个服务对象,一个用于注册用户,一个用于发送电子邮件,后者在第一个内部被称为。 比如像这样的东西:

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

您可能已经注意到,第二个服务是传播typesREQUIRES_NEW ,而且有可能引发exception(SMTP服务器closures,无效的电子邮件或其他原因)。您可能不希望整个过程回滚,如从数据库或其他东西中删除用户信息; 因此您可以在另一个事务中调用第二个服务。

回到我们的例子,这次你关心数据库的安全性,所以你这样定义你的DAO类:

 /* User DAO */ @Transactional(Propagation=MANDATORY) class UserDAO{ // some CRUD methods } 

这意味着无论何时创build一个DAO对象,并因此创build一个对db的潜在访问权限,我们都需要保证这个调用是从我们的一个服务中进行的,这意味着一个活动事务应该存在; 否则会发生exception。因此,传播types是MANDATORY

隔离级别定义了一个事务对某些数据存储库所做的更改如何影响其他同时发生的并发事务,以及该更改的数据如何以及何时可用于其他事务。 当我们使用Spring框架定义一个事务时,我们也可以configuration在哪个隔离级别执行相同的事务。

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

READ_UNCOMMITTED隔离级别指出事务可以读取其他事务尚未提交的数据。

READ_COMMITTED隔离级别指出事务不能读取其他事务尚未提交的数据。

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

SERIALIZABLE隔离级别是所有隔离级别中最严格的。 事务在所有级别都被locking执行(读取,范围和写入locking),使得它们看起来好像是以序列化方式执行的。

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

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

REQUIRES_NEW行为意味着一个新的物理事务总是由容器创build的。

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

强制行为规定现有的已打开的事务必须已经存在。 如果没有exception将被容器抛出。

从来没有行为表明,现有的打开的交易不能已经存在。 如果事务存在,容器将抛出exception。

NOT_SUPPORTED行为将在任何事务的范围之外执行。 如果打开的事务已经存在,它将被暂停。

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

您几乎从不想使用Read Uncommited因为它不符合ACID标准。 Read Commmited是一个很好的默认开始的地方。 Repeatable Read可能仅在报告,汇总或汇总scheme中需要。 请注意,包含的许多DB,实际上并不支持可重复读取,您必须使用SerializableSerializable对于你知道必须完全独立于其他事物的事物是有用的; 认为它像在Java中synchronized 。 可序列化与REQUIRES_NEW传播携手并进。

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

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

事务隔离

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

交易传播

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

参考:

  • 春季交易pipe理

  • Wiki事务隔离(数据库系统)

  • Oracle关于事务隔离级别

  • Java EE事务属性(传播)

  • Spring框架事务传播

我已经运行不同传播模式的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将抛出不存在事务的exception
      • outerMethod – 没有事务
      • method_1 – 仅限交易注释
      • method_2 – Propagation.MANDATORY)
      • 输出:method_2将抛出不存在事务的exception
      • 输出:method_1将坚持logging在数据库中。
      • outerMethod – 使用事务
      • method_1 – 仅限交易注释
      • method_2 – Propagation.MANDATORY)
      • 输出:method_2将持久logging在数据库中。
      • 输出:method_1将坚持logging在数据库中。 – 这里用于方法1和方法2的主要外部现有事务
      • outerMethod – 使用事务
      • method_1 – Propagation.MANDATORY) –
      • method_2 – 只有交易注释并抛出exception
      • 输出:数据库中没有logging意味着回滚完成。
      • outerMethod – 使用事务
      • method_1 – Propagation.REQUIRES_NEW)
      • method_2 – Propagation.REQUIRES_NEW)并抛出1/0exception
      • 输出:method_2将抛出exception,所以method_2logging不会持久。
      • 输出:method_1将坚持logging在数据库中。
      • 输出:method_1没有回滚

我们可以添加这个:

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

你可以这样使用:

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