有人能通过真实的例子解释@Transactional注释中的隔离和传播参数是用于什么吗?
基本上,我应该在什么时候以及为什么选择更改它们的默认值。
有人能通过真实的例子解释@Transactional注释中的隔离和传播参数是用于什么吗?
基本上,我应该在什么时候以及为什么选择更改它们的默认值。
当前回答
我已经用不同的传播模式运行了outerMethod, method_1和method_2。
下面是不同传播模式的输出。
外部方法
@Transactional
@Override
public void outerMethod() {
customerProfileDAO.method_1();
iWorkflowDetailDao.method_2();
}
Method_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()) {
}
}
}
Method_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 -没有事务 传播。mandatory) - Method_2 -仅事务注释 method_1将抛出不存在事务的异常
OuterMethod -没有事务 Method_1 -仅事务注释 方法2 -传播。mandatory) 输出:method_2将抛出不存在事务的异常 输出:method_1将保存数据库中的记录。
OuterMethod -带有事务 Method_1 -仅事务注释 方法2 -传播。mandatory) 输出:method_2将记录保存在数据库中。 输出:method_1将保存数据库中的记录。 方法1和方法2都使用了Main Outer现有事务
OuterMethod -带有事务 传播。mandatory) Method_2 -仅事务注释并抛出异常 输出:没有记录保存在数据库中意味着回滚完成。
OuterMethod -带有事务 方法1 -传播。requires_new) Method_2 - Propagation.REQUIRES_NEW)并抛出1/0异常 输出:method_2将抛出异常,因此method_2记录不被保存。 输出:method_1将保存数据库中的记录。 输出:method_1没有回滚
其他回答
好问题,虽然不是一个微不足道的问题。
传播
定义事务如何相互关联。常见的选项:
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,那么如果事务在进入例程时已经打开,那么事务将保持打开状态。 还要注意,回滚的结果可能不同,因为多个执行可能参与同一个事务。
我们可以很容易地用一个测试来验证行为,看看结果在传播级别上有什么不同:
@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:我们期望所有东西都回滚了,备份存储没有改变。
You almost never want to use Read Uncommited since it's not really ACID compliant. Read Commmited is a good default starting place. Repeatable Read is probably only needed in reporting, rollup or aggregation scenarios. Note that many DBs, postgres included don't actually support Repeatable Read, you have to use Serializable instead. Serializable is useful for things that you know have to happen completely independently of anything else; think of it like synchronized in Java. Serializable goes hand in hand with REQUIRES_NEW propagation.
我对所有运行UPDATE或DELETE查询的函数以及“服务”级函数都使用require。对于只运行select的DAO级函数,我使用SUPPORTS,如果一个已经启动(即从服务函数调用),它将参与TX。
事务表示数据库的一个工作单元。具有自己的txns(或没有txn)的多个服务中的事务行为称为事务传播。事务隔离定义了当两个事务并发作用于同一个数据库实体时的数据库状态。
TransactionDefinition接口,该接口定义了与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种主要问题:
脏读:从另一个tx(事务)中读取未提交的数据。 不可重复读取:从另一个tx读取已提交的更新。 幻影读取:从另一个tx读取已提交的insert和/或delete
下面的图表显示了哪个事务隔离级别可以解决哪些并发问题:
+---------------------------+--------------+----------------------+----------------+
| 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 |
+---------------------------+--------------+----------------------+----------------+
为例子
其他答案对每个参数都给出了足够的解释;但是,您要求的是一个真实世界的示例,下面是一个阐明不同传播选项的目的的示例:
Suppose you're in charge of implementing a注册服务
in which a confirmation e-mail is sent to the user. You come up with two service objects, one for招收
the user and one for发送
e-mails, which the latter is called inside the first one. For example something like this:/* 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服务器宕机、无效电子邮件或其他原因)。你可能不希望整个过程回滚,比如从数据库中删除用户信息或其他东西;因此,在单独的事务中调用第二个服务。
Back to our example, this time you are concerned about the database security, so you define your DAO classes this way:/* User DAO */
@Transactional(Propagation=MANDATORY)
class UserDAO{
// some CRUD methods
}
这意味着无论何时创建一个DAO对象,以及因此对DB的潜在访问,我们都需要确保调用是从我们的一个服务内部发出的,这意味着应该存在一个活动事务;否则会出现异常。因此,传播类型为MANDATORY。
你可以这样用:
@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();
}