我有以下使用Spring用Java编写的Web服务,我用@Transactional注释将其包装起来,以确保在需要时可以回滚。
除了两次调用该服务,第二次调用发生在第一个调用完成之前的情况之外,它都工作正常。
在这种情况下,由于第一个事务仍在运行并且尚未提交给DB,因此第二个事务将通过full方法,插入重复的行,再次更新状态并调用sendAlert()。
这是伪代码。
@Transactional
public ServiceResponse update(ServiceRequest serviceRequest) {
....
if (myDao.getStatus() == "COMPLETE") {
return serviceError;
}
myDao.insertRow();
myDao.updateStatus("COMPLETE");
sendAlert();
}
如何防止第二笔交易在第一笔交易之前进行?不能将隔离级别设置为“读未提交”,因为数据库不支持隔离级别。
答案 0 :(得分:0)
您可以使用数据库来执行此操作,即使状态列似乎已经在使用中也可能如此。我并不是要在这里处理所有细节...只是为了传达想法。这里的巨大之处在于,这种机制可以在线程,进程甚至机器,我遇到的任何数据库中运行,而您无需设置其他任何东西。当您想扩展到多个实例时,无需Redis等。
您将创建另一个状态,并使用测试并设置操作:
public ServiceResponse update(ServiceRequest serviceRequest) {
....
while true:
String status = myDao.getStatus();
if (status.equals("COMPLETE")) {
return serviceError;
}
else if (status.equals("PROCESSING")) {
// do whatever you want to do if some other process or thread is
// already handling this. Maybe also return an error
return serviceError;
}
else if (myDao.testAndUpdateStatus(status, "PROCESSING")) {
// You would probably want to re-introduce a transaction here, maybe
// by moving this block to its own method. Sorry for cutting a
// corner here trying to just demonstrate the lock idea, which needs
// to not be in a transaction.
try {
myDao.insertRow();
myDao.updateStatus("COMPLETE");
sendAlert();
return serviceOK;
} catch (Exception ex) {
// What you do for the failure case will be very app specific. This
// is mostly to point out that you would want to set the status
// explicitly in the case of an error, to whatever is appropriate
myDao.updateStatus("COMPLETE")
return serviceError;
}
}
}
您需要使锁定操作不是事务性的...的重点是每个操作都是真正的原子操作。如果您确实需要事务语义,则希望将最终处理块以某种方式包装在事务中。我并不是想让交易中的事情恰到好处。我指出了一种使用数据库本身来控制同步和竞争条件的常用方法。该机制不属于事务处理,事务仅在线程从true
返回testAndUpdateStatus
后才开始。
此处的关键是testAndUpdateStatus()
仅会设置状态,并且如果作为第一个参数传入的状态是当前状态,则返回true
。否则,它将不执行任何操作,并返回false
。这解决了争用条件,其中一个进程对状态进行采样,然后另一个进程对状态值进行采样,然后再将状态设置为"PROCESSING"
,最后两个进程处理相同的更新。两者之一将失败,因为当状态不再是该进程读取该值时的状态时,数据库将使操作失败。
请注意,这不仅适用于单个进程,而且适用于跨进程,甚至适用于机器。
答案 1 :(得分:0)
不幸的是,在这种情况下,Hibernate依赖于数据库功能。 Hibernate设计为能够支持使用完全相同的数据库的其他服务。
在某些情况下,我们可以通过修改生成器策略来手动设置将来的主键。但这并不能防止内容“重复”。
一种解决方案可能是使用具有Rest-Entity的ReSTful API,该实体“请求”处于特定状态的数据库状态。
例如:
为防止这种情况,我们可以存储汽车存在请求。
或者只是切换到PostGreSQL
答案 2 :(得分:0)
根据您的问题,我假设将在某种程度的并发性下调用update(..)
。
我发现使用外部数据存储进行协调的这种方法存在一些问题。对于默认的“ 读取已提交”隔离,您将遇到现在遇到的问题,但是,即使您可以使用“ 读取未提交”,也会遇到问题:已读取脏的“ COMPLETE”数据的第二个事务返回,但是第一个事务仍可能失败并回滚。
我提出了几种方法(我对课程做了很多假设)
答案 3 :(得分:-1)
这两个调用将打开不同的线程,因此具有不同的事务。除非您在某个地方有一些独立于数据库的东西,可以告诉您线程正在使用此资源(例如带有标志的文件),否则,我认为唯一的其他方法是同步代码块。