我有一个Spring应用程序,它使用@Transactional
方法更新MySQL DB中的特定实体详细信息,并且在同一方法中,我试图使用@Async
调用另一个端点,这是另一个Spring应用程序从MySql DB读取相同的实体并更新Redis存储中的值。
现在的问题是,每次我更新实体的某些值时,有时会在redis中更新它,有时却没有。
当我尝试调试时,我发现有时第二个应用程序从MySql读取实体时会选择旧值而不是更新值。
有人可以建议我如何避免这种情况,并确保第二个应用程序始终从Mysql中选择该实体的更新值?
答案 0 :(得分:2)
M。Deinum的答案很好,但是还有另一种方法可以实现此目标,这取决于您当前应用程序的状态,因此可能更简单。
您可以简单地将对async方法的调用包装在一个事件中,该事件将在当前事务提交后处理,因此您每次都将正确地从db中读取更新的实体。
这样做很简单,让我向您展示:
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
@Transactional
public void doSomething() {
// application code here
// this code will still execute async - but only after the
// outer transaction that surrounds this lambda is completed.
executeAfterTransactionCommits(() -> theOtherServiceWithAsyncMethod.doIt());
// more business logic here in the same transaction
}
private void executeAfterTransactionCommits(Runnable task) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
public void afterCommit() {
task.run();
}
});
}
基本上,这里发生的是我们为当前事务回调提供一个实现,并且仅覆盖afterCommit方法-那里还有其他可能有用的方法,请检查一下。为了避免在其他部分使用相同的样板代码,或者只是为了使方法更具可读性,请避免输入相同的样板代码,我在辅助方法中提取了该样板代码。
答案 1 :(得分:1)
解决方案并不难,显然您希望在将数据写入数据库后触发和更新。 @Transactional
仅在方法完成执行后提交。如果在该方法的末尾调用了另一个@Async
方法,则根据提交的持续时间(或实际的REST调用),该事务可能已提交或未提交。
由于事务外的某些事物只能看到已提交的数据,因此可能会看到已更新的数据(如果已提交)或仍旧的数据。这也取决于事务的序列化级别,但是出于性能原因,您通常不想在数据库上使用排他锁。
要解决此问题,不应从@Async
内部调用@Transactional
方法,而应在其后调用。这样,数据将始终被提交,其他服务将看到更新后的数据。
@Service
public class WrapperService {
private final TransactionalEntityService service1;
private final AsyncService service2;
public WrapperService(TransactionalEntityService service1, AsyncService service2) {
this.service1=service1;
this.service2=service2;
}
public updateAndSyncEntity(Entity entity) {
service1.update(entity); // Update in DB first
service2.sync(entity); // After commit trigger a sync with remote system
}
}
此服务是非事务性的,因此service1.update
(假定为@Transactional
)将更新数据库。完成后,您可以触发外部同步。