弹簧@Transactional - 隔离、传播
有人可以通过真实的例子来解释注释中的隔离和传播参数是什么吗?@Transactional
基本上,我应该选择何时以及为什么选择更改其默认值。
有人可以通过真实的例子来解释注释中的隔离和传播参数是什么吗?@Transactional
基本上,我应该选择何时以及为什么选择更改其默认值。
好问题,虽然不是一个微不足道的问题。
定义事务如何相互关联。常用选项:
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
:我们预计所有内容都已回滚,并且后备商店保持不变。
PROPAGATION_REQUIRED = 0;如果数据源事务对象 T1 已为方法 M1 启动。如果对于另一个方法需要 M2 事务对象,则不会创建新的事务对象。相同的对象 T1 用于 M2。
PROPAGATION_MANDATORY = 2;方法必须在事务中运行。如果没有正在进行的现有事务,将引发异常。
PROPAGATION_REQUIRES_NEW = 3;如果 DataSourceTransactionObject T1 已为方法 M1 启动并且正在进行中(正在执行方法 M1)。如果另一个方法 M2 开始执行,则在方法 M2 的持续时间内,T1 将挂起,并带有新的 DataSourceTransactionObject T2 for M2。M2 在其自己的事务上下文中运行。
PROPAGATION_NOT_SUPPORTED = 4;如果数据源事务对象 T1 已为方法 M1 启动。如果同时运行另一个方法 M2。那么 M2 就不应该在事务上下文中运行。T1暂停,直到M2完成。
PROPAGATION_NEVER = 5;这些方法均不在事务上下文中运行。
隔离级别:它是关于一个事务可能受到其他并发事务的活动的影响。它支持一致性,使许多表中的数据保持一致状态。它涉及锁定数据库中的行和/或表。
多事务的问题
方案 1.如果 T1 事务从表 A1 中读取由另一个并发事务 T2 写入的数据。如果在 T2 回滚途中,则 T1 获取的数据无效。例如,a=2 是原始数据。如果 T1 读取由 T2 写入的 a=1。如果 T2 回滚,则 a=1 将在 DB 中回滚到 a=2。但是,现在,T1 具有 a=1,但在 DB 表中,它被更改为 a=2。
方案 2.如果 T1 事务从表 A1 读取数据。如果另一个并发事务 (T2) 更新表 A1 上的数据。则 T1 读取的数据与表 A1 不同。因为 T2 更新了表 A1 上的数据。例如,如果 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();
}
}
您可以调试并使用不同的隔离和传播值查看结果。