有人可以通过真实示例解释 @Transactional
注释中的 隔离 和 传播 参数吗?
基本上什么时候以及为什么我应该选择更改它们的默认值。
原文由 MatBanik 发布,翻译遵循 CC BY-SA 4.0 许可协议
有人可以通过真实示例解释 @Transactional
注释中的 隔离 和 传播 参数吗?
基本上什么时候以及为什么我应该选择更改它们的默认值。
原文由 MatBanik 发布,翻译遵循 CC BY-SA 4.0 许可协议
PROPAGATION_REQUIRED = 0 ;如果 DataSourceTransactionObject T1 已经为方法 M1 启动。如果需要另一个方法 M2 事务对象,则不会创建新的事务对象。同一对象 T1 用于 M2。
PROPAGATION_MANDATORY = 2 ;方法必须在事务中运行。如果没有正在进行的现有事务,将抛出异常。
PROPAGATION_REQUIRES_NEW = 3 ;如果 DataSourceTransactionObject T1 已经为方法 M1 启动并且正在进行(执行方法 M1)。如果另一个方法 M2 开始执行,则 T1 在方法 M2 的持续时间内暂停,并为 M2 使用新的 DataSourceTransactionObject T2。 M2 在其自己的事务上下文中运行。
PROPAGATION_NOT_SUPPORTED = 4 ;如果 DataSourceTransactionObject T1 已经为方法 M1 启动。如果同时运行另一个方法M2。那么 M2 不应该在事务上下文中运行。 T1 暂停,直到 M2 结束。
PROPAGATION_NEVER = 5 ;没有一个方法在事务上下文中运行。
隔离级别: 它是关于一个事务可能受其他并发事务的活动影响的程度。它支持一致性,使许多表中的数据保持一致状态。它涉及锁定数据库中的行和/或表。
多次交易的问题
场景 1 。如果 T1 事务从另一个并发事务 T2 写入的表 A1 中读取数据。如果途中T2回滚,T1获取的数据是无效的。例如 a=2 是原始数据。如果 T1 读到由 T2 写入的 a=1。如果 T2 回滚,则 a=1 将回滚到数据库中的 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();
}
}
您可以使用不同的隔离值和传播值来调试和查看结果。
原文由 abishkar bhattarai 发布,翻译遵循 CC BY-SA 4.0 许可协议
15 回答8.4k 阅读
8 回答6.2k 阅读
1 回答4k 阅读✓ 已解决
3 回答2.2k 阅读✓ 已解决
2 回答3.1k 阅读
2 回答3.8k 阅读
3 回答1.7k 阅读✓ 已解决
好问题,虽然不是一个微不足道的问题。
传播
定义事务如何相互关联。常用选项:
REQUIRED
:代码将始终在事务中运行。创建一个新事务或重用一个(如果有)。REQUIRES_NEW
:代码将始终在新事务中运行。如果存在,则暂停当前事务。@Transactional
的默认值是REQUIRED
,这通常是你想要的。隔离
定义事务之间的数据契约。
ISOLATION_READ_UNCOMMITTED
:允许脏读。ISOLATION_READ_COMMITTED
:不允许脏读。ISOLATION_REPEATABLE_READ
:如果在同一个事务中读取一行两次,结果总是相同的。ISOLATION_SERIALIZABLE
:按顺序执行所有事务。不同级别在多线程应用程序中具有不同的性能特征。我认为,如果您了解 脏读 概念,您将能够选择一个不错的选择。
默认值可能因不同的数据库而异。例如,对于 MariaDB ,它是
REPEATABLE READ
。可能发生脏读的示例:
因此,合理的默认值(如果可以声明)可能是
ISOLATION_READ_COMMITTED
,它只允许您读取其他正在运行的事务已经提交的值,并结合传播级别REQUIRED
。然后,如果您的应用程序有其他需求,您可以从那里开始工作。在进入
provideService
例程时总是会创建新事务并在离开时完成的实际示例:如果我们改为使用
REQUIRED
,如果在进入例程时事务已经打开,则事务 将保持打开 状态。另请注意,rollback
的结果可能不同,因为多个执行可能参与同一事务。我们可以通过测试轻松验证行为,并查看结果与传播级别有何不同:
传播水平为
REQUIRES_NEW
:我们预计fooService.provideService()
不会 回滚,因为它创建了自己的子交易。REQUIRED
:我们希望一切都回滚并且后备存储没有改变。