谁能解释一下什么是隔离 & propagation 参数用于 @Transactional
注释中的真实示例?
基本上什么时候以及为什么我应该选择更改它们的默认值。
好问题,虽然不是一个微不足道的问题。
传播
定义事务如何相互关联。常见选项:
要求:代码将始终在事务中运行。创建一个新事务或重用一个(如果有)。
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
,则事务 would remain open 如果在进入例程时事务已经打开。另请注意,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() 没有回滚,因为它创建了自己的子事务。
必需的:我们希望一切都回滚并且后备存储没有改变。
PROPAGATION_REQUIRED = 0;如果 DataSourceTransactionObject T1 已经为方法 M1 启动。如果需要另一个 Method M2 Transaction 对象,则不会创建新的 Transaction 对象。相同的对象 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 事务从表 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();
}
}
您可以使用不同的隔离和传播值进行调试并查看结果。
其他答案给出了关于每个参数的足够解释;但是,您要求提供一个真实世界的示例,以下是阐明不同传播选项目的的示例:
注册服务
招生
发送
/* 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 服务器关闭、无效电子邮件或其他原因)。您可能不希望整个过程回滚,例如从数据库或其他东西中删除用户信息;因此,您在单独的事务中调用第二个服务。
/* User DAO */
@Transactional(Propagation=MANDATORY)
class UserDAO{
// some CRUD methods
}
这意味着,无论何时创建 DAO 对象,并因此创建对 DB 的潜在访问权限,我们都需要确保调用是从我们的一个服务内部进行的,这意味着应该存在实时事务;否则,会发生异常。因此传播是MANDATORY类型的。
隔离级别定义了一个事务对某些数据存储库所做的更改如何影响其他并发并发事务,以及更改的数据如何以及何时可用于其他事务。当我们使用 Spring 框架定义事务时,我们还可以配置在哪个隔离级别执行相同的事务。
@Transactional(isolation=Isolation.READ_COMMITTED)
public void someTransactionalMethod(Object obj) {
}
READ_UNCOMMITTED 隔离级别表明一个事务可能会读取其他事务仍未提交的数据。
READ_COMMITTED 隔离级别表明一个事务不能读取其他事务尚未提交的数据。
REPEATABLE_READ 隔离级别规定,如果一个事务从数据库中多次读取一条记录,那么所有这些读取操作的结果必须始终相同。
SERIALIZABLE 隔离级别是所有隔离级别中限制性最强的。事务在所有级别(读取、范围和写入锁定)上都使用锁定执行,因此它们看起来好像是以序列化方式执行的。
传播是决定如何将业务方法封装在逻辑或物理事务中的能力。
Spring REQUIRED 行为意味着如果在当前 bean 方法执行上下文中有一个已经打开的事务,则将使用相同的事务。
REQUIRES_NEW 行为意味着容器总是会创建一个新的物理事务。
NESTED 行为使嵌套的 Spring 事务使用相同的物理事务,但在嵌套调用之间设置保存点,因此内部事务也可以独立于外部事务回滚。
MANDATORY 行为表明现有打开的事务必须已经存在。如果不是,容器将抛出异常。
NEVER 行为表明现有打开的事务必须不存在。如果事务存在,容器将抛出异常。
NOT_SUPPORTED 行为将在任何事务的范围之外执行。如果已打开的事务已经存在,它将被暂停。
如果已打开的事务已存在,则 SUPPORTS 行为将在事务范围内执行。如果没有已打开的事务,则该方法无论如何都会执行,但以非事务方式执行。
一个事务代表一个使用数据库的工作单元。具有自己的 txn(或没有 txn)的多个服务中的事务行为称为事务传播。事务隔离定义了两个事务同时作用于同一个数据库实体时的数据库状态。
在定义 Spring 兼容事务属性的 spring TransactionDefinition
接口中。 @Transactional
注释描述了方法或类的事务属性。
@Autowired
private TestDAO testDAO;
@Transactional(propagation=TransactionDefinition.PROPAGATION_REQUIRED,isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
public void someTransactionalMethod(User user) {
// Interact with testDAO
}
传播(复制):用于交易间关系。 (类似于java线程间通信)
+-------+---------------------------+------------------------------------------------------------------------------------------------------+
| value | Propagation | Description |
+-------+---------------------------+------------------------------------------------------------------------------------------------------+
| -1 | TIMEOUT_DEFAULT | Use the default timeout of the underlying transaction system, or none if timeouts are not supported. |
| 0 | PROPAGATION_REQUIRED | Support a current transaction; create a new one if none exists. |
| 1 | PROPAGATION_SUPPORTS | Support a current transaction; execute non-transactionally if none exists. |
| 2 | PROPAGATION_MANDATORY | Support a current transaction; throw an exception if no current transaction exists. |
| 3 | PROPAGATION_REQUIRES_NEW | Create a new transaction, suspending the current transaction if one exists. |
| 4 | PROPAGATION_NOT_SUPPORTED | Do not support a current transaction; rather always execute non-transactionally. |
| 5 | PROPAGATION_NEVER | Do not support a current transaction; throw an exception if a current transaction exists. |
| 6 | PROPAGATION_NESTED | Execute within a nested transaction if a current transaction exists. |
+-------+---------------------------+------------------------------------------------------------------------------------------------------+
隔离:隔离是数据库事务的 ACID(原子性、一致性、隔离性、持久性)属性之一。隔离决定了事务完整性如何对其他用户和系统可见。它用于资源锁定,即并发控制,确保在给定点只有一个事务可以访问资源。
锁定感知:隔离级别决定了持有锁的持续时间。
+---------------------------+-------------------+-------------+-------------+------------------------+
| Isolation Level Mode | Read | Insert | Update | Lock Scope |
+---------------------------+-------------------+-------------+-------------+------------------------+
| READ_UNCOMMITTED | uncommitted data | Allowed | Allowed | No Lock |
| READ_COMMITTED (Default) | committed data | Allowed | Allowed | Lock on Committed data |
| REPEATABLE_READ | committed data | Allowed | Not Allowed | Lock on block of table |
| SERIALIZABLE | committed data | Not Allowed | Not Allowed | Lock on full table |
+---------------------------+-------------------+-------------+-------------+------------------------+
阅读感知:出现以下3种主要问题:
脏读:从另一个 tx(transaction) 中读取未提交的数据。
不可重复读取:从另一个 tx 读取已提交的更新。
幻读:从另一个 tx 读取已提交的 INSERTS 和/或 DELETES
下面的图表显示了哪个事务隔离级别解决了哪些并发问题:
+---------------------------+--------------+----------------------+----------------+
| Isolation Level Mode | Dirty reads | Non-repeatable reads | Phantoms reads |
+---------------------------+--------------+----------------------+----------------+
| READ_UNCOMMITTED | X | X | X |
| READ_COMMITTED (Default) | solves | X | X |
| REPEATABLE_READ | solves | solves | X |
| SERIALIZABLE | solves | solves | solves |
+---------------------------+--------------+----------------------+----------------+
您几乎不想使用 Read Uncommited
,因为它并不真正符合 ACID
。 Read Commmited
是一个很好的默认起点。 Repeatable Read
可能仅在报告、汇总或聚合方案中需要。请注意,许多数据库(包括 postgres)实际上并不支持可重复读取,您必须改用 Serializable
。 Serializable
对于您知道必须完全独立于其他任何事情发生的事情很有用;把它想象成 Java 中的 synchronized
。可序列化与 REQUIRES_NEW
传播密切相关。
我将 REQUIRES
用于所有运行 UPDATE 或 DELETE 查询的函数以及“服务”级函数。对于只运行 SELECT 的 DAO 级别函数,我使用 SUPPORTS
如果已经启动(即从服务函数调用),它将参与 TX。
Transaction Isolation 和 Transaction Propagation 虽然相关但显然是两个截然不同的概念。在这两种情况下,默认值都是通过使用 Declarative transaction management 或 Programmatic transaction management 在客户端边界组件处自定义的。每个隔离级别和传播属性的详细信息可以在下面的参考链接中找到。
对于给定的两个或多个正在运行的事务/与数据库的连接,一个事务中的查询如何以及何时做出更改对不同事务中的查询产生影响/可见。它还与将使用哪种数据库记录锁定来隔离此事务中的更改与其他事务有关,反之亦然。这通常由参与事务的数据库/资源实现。
.
在任何给定请求/处理的企业应用程序中,都涉及许多组件来完成工作。其中一些组件标记了将在各个组件及其子组件中使用的事务的边界(开始/结束)。对于组件的事务边界,事务传播指定各个组件是否将参与事务,以及调用组件是否已经创建/启动事务时会发生什么。这与 Java EE 事务属性相同。这通常由客户端事务/连接管理器实现。
参考:
Spring事务管理
Wiki 事务隔离(数据库系统)
事务隔离级别的 Oracle
Java EE 事务属性(传播)
Spring Framework 事务传播
我用不同的传播模式运行了 outerMethod
、method_1
和 method_2
。
下面是不同传播模式的输出。
外部方法
@Transactional
@Override
public void outerMethod() {
customerProfileDAO.method_1();
iWorkflowDetailDao.method_2();
}
方法_1
@Transactional(propagation=Propagation.MANDATORY)
public void method_1() {
Session session = null;
try {
session = getSession();
Temp entity = new Temp(0l, "XXX");
session.save(entity);
System.out.println("Method - 1 Id "+entity.getId());
} finally {
if (session != null && session.isOpen()) {
}
}
}
方法_2
@Transactional()
@Override
public void method_2() {
Session session = null;
try {
session = getSession();
Temp entity = new Temp(0l, "CCC");
session.save(entity);
int i = 1/0;
System.out.println("Method - 2 Id "+entity.getId());
} finally {
if (session != null && session.isOpen()) {
}
}
}
OuterMethod - 没有事务 Method_1 - Propagation.MANDATORY) - Method_2 - 仅事务注释输出:method_1 将抛出没有现有事务的异常
OuterMethod - 没有事务 Method_1 - 仅事务注释 Method_2 - Propagation.MANDATORY) 输出:method_2 将抛出没有现有事务的异常输出:method_1 将在数据库中保留记录。
OuterMethod - 使用事务 Method_1 - 仅事务注释 Method_2 - Propagation.MANDATORY) 输出:method_2 将记录保存在数据库中。输出:method_1 将记录保存在数据库中。 -- 这里主要外部现有事务用于方法 1 和 2
OuterMethod - 使用事务 Method_1 - Propagation.MANDATORY) Method_2 - 仅事务注释并抛出异常输出:数据库中没有记录意味着回滚完成。
OuterMethod - 使用事务 Method_1 - Propagation.REQUIRES_NEW) Method_2 - Propagation.REQUIRES_NEW) 并抛出 1/0 异常输出:method_2 将抛出异常,因此 method_2 记录不会被持久化。输出:method_1 将记录保存在数据库中。输出:method_1 没有回滚
我们可以为此添加:
@Transactional(readOnly = true)
public class Banking_CustomerService implements CustomerService {
public Customer getDetail(String customername) {
// do something
}
// these settings have precedence for this method
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateCustomer(Customer customer) {
// do something
}
}
你可以这样使用:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public EventMessage<ModificaOperativitaRapporto> activate(EventMessage<ModificaOperativitaRapporto> eventMessage) {
//here some transaction related code
}
你也可以使用这个东西:
public interface TransactionStatus extends SavepointManager {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
void flush();
boolean isCompleted();
}
不定期副业成功案例分享
sessionFactory.getCurrentTransaction()
,因此不再需要运行HibernateTemplate
来管理事务。我删除了它:)