有人可以通过实际示例解释注释中的隔离和传播参数吗?@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
,如果在进入例程时事务已经打开,则该事务将保持打开状态。另请注意, a 的结果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 启动。如果需要另一个 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 服务器关闭、无效电子邮件或其他原因)。您可能不希望整个过程回滚,例如从数据库或其他东西中删除用户信息;因此,您在单独的事务中调用第二个服务。
回到我们的例子,这次你关心的是数据库安全,所以你定义你的 DAO 类是这样的:/* 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 行为将在事务范围内执行。如果没有已打开的事务,则该方法无论如何都会执行,但以非事务方式执行。
一个事务代表一个使用数据库的工作单元。具有自己的 txns(或没有 txn)的多个服务中的事务行为称为事务传播。事务隔离定义了两个事务同时作用于同一个数据库实体时的数据库状态。
TransactionDefinition
在定义 Spring 兼容事务属性的spring接口中。@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种主要问题:
UPDATES
:从另一个 tx提交的读取。INSERTS
和/或DELETES
从另一个 tx不同类型读取的隔离级别:
+---------------------------+----------------+----------------------+----------------+
| Isolation Level Mode | Dirty reads | Non-repeatable reads | Phantoms reads |
+---------------------------+----------------+----------------------+----------------+
| READ_UNCOMMITTED | allows | allows | allows |
| READ_COMMITTED (Default) | prevents | allows | allows |
| REPEATABLE_READ | prevents | prevents | allows |
| SERIALIZABLE | prevents | prevents | prevents |
+---------------------------+----------------+----------------------+----------------+
您几乎从不想使用Read Uncommited
,因为它并不真正ACID
合规。Read Commmited
是一个很好的默认起点。Repeatable Read
可能仅在报告、汇总或聚合方案中需要。请注意,许多数据库(包括 postgres)实际上并不支持可重复读取,您必须Serializable
改用它。Serializable
对于您知道必须完全独立于其他任何事情发生的事情很有用;把它想象成synchronized
Java。REQUIRES_NEW
可序列化与传播齐头并进。
我REQUIRES
用于所有运行 UPDATE 或 DELETE 查询的函数以及“服务”级函数。对于只运行 SELECTs 的 DAO 级函数,SUPPORTS
如果一个已经启动(即从服务函数调用),我将使用它将参与 TX。
Transaction Isolation 和 Transaction Propagation 虽然相关但显然是两个截然不同的概念。在这两种情况下,默认值都是通过使用声明式事务管理或编程式事务管理在客户端边界组件处定制的。每个隔离级别和传播属性的详细信息可以在下面的参考链接中找到。
对于给定的两个或多个正在运行的事务/与数据库的连接,一个事务中的查询如何以及何时做出更改对不同事务中的查询产生影响/可见。它还涉及将使用哪种数据库记录锁定来隔离此事务中的更改与其他事务,反之亦然。这通常由参与事务的数据库/资源实现。
.
在任何给定请求/处理的企业应用程序中,都涉及许多组件来完成工作。其中一些组件标记了将在各个组件及其子组件中使用的事务的边界(开始/结束)。对于组件的这个事务边界,事务传播指定各个组件是否将参与事务以及如果调用组件已经具有或没有已创建/启动的事务会发生什么。这与 Java EE 事务属性相同。这通常由客户端事务/连接管理器实现。
参考:
我已经运行了outerMethod
,method_1
并且method_2
使用了不同的传播模式。
下面是不同传播模式的输出。
@Transactional
@Override
public void outerMethod() {
customerProfileDAO.method_1();
iWorkflowDetailDao.method_2();
}
@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()) {
}
}
}
@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()) {
}
}
}
我们可以为此添加:
@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();
}