问题
有人可以通过真实世界的例子来解释876477709隔离和传播**参数在@Transactional
注释中的用途。基本上何时以及为什么我应该选择更改其默认值。
#1 热门回答(293 赞)
好问题,虽然不是一个微不足道的答案。
传播
定义事务彼此之间的关系。常见选项
- 必需:代码将始终在事务中运行。创建新事务或重用一个(如果可用)。
- Requires_new:代码将始终在新事务中运行。暂停当前事务(如果存在)。
隔离
定义事务之间的数据协定。 - Read Uncommitted:允许脏读
- Read Committed:不允许脏读
- 可重复读取:如果在同一个事务中读取两次行,则结果将始终相同
- Serializable:按顺序执行所有事务
不同级别在多线程应用程序中具有不同的性能特征。我想如果你了解dirty reads
概念,你将能够选择一个好的选择。
可能发生脏读的示例
thread 1 thread 2
| |
write(x) |
| |
| read(x)
| |
rollback |
v v
value (x) is now dirty (incorrect)
因此,一个理智的默认值(如果可以声明)可以是Read Comitted
,它只允许你读取已经由其他正在运行的事务进行评估的值,并结合传播级别Required
。如果你的应用程序有其他需求,那么你可以从那里工作。
一个实际示例,在输入566716586例程时将始终创建新事务,并在离开时完成。
public class FooService {
private Repository repo1;
private Repository repo2;
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void provideService() {
repo1.retrieveFoo();
repo2.retrieveFoo();
}
}
如果在进入例程时事务已经打开,我们使用了Required
而不是事务will remain open。另请注意,arollback
的结果可能会有所不同,因为多次执行可能会参与同一事务。
我们可以通过测试轻松验证行为,并查看结果与传播级别的差异
@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 ...
}
传播水平为
- 需要新的我们期望fooService.provideService()没有回滚,因为它创建了它自己的子事务。
- 要求我们希望所有内容都回滚并支持商店不变。
#2 热门回答(201 赞)
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;这些方法都不在事务上下文中运行。
**隔离级别:**这是关于事务可能受其他并发事务的活动影响的程度。它支持一致性,使数据保持在一致状态的多个表中。它涉及锁定数据库中的行和/或表。
多个事务的问题
如果T1事务从表A1读取由另一个并发事务T2写入的数据。如果在T2回滚的路上,T1获得的数据是无效的.Eg a = 2是原始数据。如果T1读取a = 1由T2写入。如果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被称为44476885幻影读取。**
因此,隔离级别是可以防止的方案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();
}
}
你可以调试并查看具有不同隔离和传播值的结果。
#3 热门回答(72 赞)
其他答案给出了对每个参数的充分解释;但是你要求一个真实世界的例子,这里是澄清不同传播选项的目的:
假设你负责实施asignup服务,其中向用户发送确认电子邮件。你提出了两个服务对象,一个是用户注册的,另一个是forsendinge-mail,后者在第一个服务对象中调用。例如这样的事情:
/* 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。