隔离,传播
有人可以通过真实世界的例子解释@Transactional
注释中的隔离和传播参数。 基本上什么时候和为什么我应该选择改变他们的默认值。
好问题,虽然不是一个可以回答的小问题。
传播
定义交易如何相互关联。 通用选项
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 Comitted
,它只允许您读取已由其他正在运行的事务处理的值,并结合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
我们期望一切都回滚和支持存储不变。
PROPAGATION_REQUIRED = 0 ; 如果DataSourceTransactionObject T1已为方法M1启动。如果需要另一个方法M2 Transaction对象,则不会创建新的Transaction对象。同名对象T1用于M2
PROPAGATION_MANDATORY = 2 ; 方法必须在事务内运行。 如果没有正在进行的事务,则会抛出异常
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 ; 没有任何方法在事务上下文中运行。
一个隔离级别:一个事务可能会受到其他并发事务的活动影响多少。它支持一致性,使数据跨多个表保持一致的状态。 它涉及锁定数据库中的行和/或表。
多重交易的问题
场景1:如果T1事务从表A1中读取的数据是由另一个并发事务T2写入的。如果T2在回滚的方式中,T1获得的数据是无效的。例如,如果a = 2是原始数据,则读取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可以预防的范围。 您可以通过实现锁定来获得完整的隔离级别。这样可以防止对同一数据发生并发读取和写入操作,但会影响性能。隔离级别取决于应用程序对应用程序的隔离级别。
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();
}
}
您可以调试并查看具有不同隔离和传播值的结果。
其他答案给出了有关每个参数的足够解释; 然而,你要求一个真实世界的例子,下面是澄清不同传播选项的目的:
假设您负责实施向用户发送确认电子邮件的注册服务。 你想出两个服务对象,一个用于注册用户,一个用于发送电子邮件,后者在第一个内部被称为。 例如这样的事情:/* 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 。
链接地址: http://www.djcxy.com/p/30135.html下一篇: What is the difference between CMD and ENTRYPOINT in a Dockerfile?