为了账号安全,请及时绑定邮箱和手机立即绑定

Spring @Transactional-隔离,传播

Spring @Transactional-隔离,传播

慕尼黑8549860 2019-11-03 04:04:10
有人可以通过实际示例解释注释中的隔离和传播参数@Transactional吗?基本上,何时和为什么我应该选择更改其默认值。
查看完整描述

3 回答

?
天涯尽头无女友

TA贡献1831条经验 获得超9个赞

好的问题,尽管不是一个简单的答案。


传播


定义事务之间的关系。常用选项:


Required:代码将始终在事务中运行。创建一个新事务或重用一个事务(如果有)。

Requires_new:代码将始终在新事务中运行。如果存在当前事务,则将其挂起。

隔离


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


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 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,则在进入例程时如果事务已经打开,则事务将保持打开状态。还要注意,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 ... 

}

传播水平为


Requires new:我们希望fooService.provideService()它不会回滚,因为它创建了它自己的子事务。


Required:我们希望一切都回滚而后备存储保持不变。



查看完整回答
反对 回复 2019-11-04
?
阿晨1998

TA贡献2037条经验 获得超6个赞

关于其他参数的足够解释由其他答案给出;但是,您要求提供一个真实的示例,以下示例阐明了不同传播选项的目的:


假设您负责实施注册服务,在该服务中向用户发送确认电子邮件。您想到了两个服务对象,一个用于注册用户,另一个用于发送电子邮件,后者在第一个中被称为。例如这样的事情:

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



查看完整回答
反对 回复 2019-11-04
  • 3 回答
  • 0 关注
  • 227 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信