无法使用 Spring jpa 获取更新的实体,并且唯一约束不会停止更新



我的情况是:一个公共方法一开始试图更新实体,最后试图查询更新后的实体并通过SNS发送消息。我有两个问题。

第一个问题是,如果我调用DeviceDao.save(),我可以获得更新的实体,但如果我在UpdateClass中调用自定义更新方法updateAsset,我就无法获得更新的主体。

第二个问题是,代码仍然会传递到messagePublisher,我可以在"messagePublisher.doRealthing(("中获得更新的实体,即使UpdateClass.doSth()抛出org.hibernate.exception.ConstationViolationException(我对资产id有一个唯一的约束(,但最终,更新会被回滚。

我的问题是,为什么我会遇到这两个问题?对于第二个问题,除了提前查询资产Id之外,我如何避免它?

这是我的密码。

public interface ExampleDeviceDao extends JpaRepository<Device, UUID>, JpaSpecificationExecutor<Device> {
@Modifying
@Query("UPDATE device a SET a.asset = ?1 WHERE a.device = ?2")
int updateAsset(UUID asset, UUID device);
}

我的公共服务和方法:


@Component
public class Service {
@Autowired
UpdateClass updateClass;
@Autowired
MiddleClass middleClass;
@Autowired
MessagePublisher messagePublisher;
@org.springframework.transaction.annotation.Transactional
public void updateAsset(UUID deviceId, UUID assetId) {
updateClass.doSth(deviceId, assetId);
middleClass.doSth(deviceId, assetId);
messagePublisher.doSth(deviceId, assetId);
}
}
public abstract class AbstractClass {
protected abstract void doRealThing(UUID deviceId, UUID assetId);
public void doSth(UUID deviceId, UUID assetId) {
doRealThing(deviceId, assetId);
}
}
@Component
public class UpdateClass extends AbstractClass{
@Autowired
ExampleDeviceDao deviceDao;
protected void doRealThing(UUID deviceId, UUID assetId) {
Optional<Device> device = deviceDao.findById(deviceId);
if (device.isPresent()) {
device.get().setAsset(assetId);
/** approach 1:*/
deviceDao.save(device.get());
/**
* approach 2:
* deviceDao.updateAsset(assetId, deviceId);
*/
}
}
}
@Component
public class MiddleClass extends AbstractClass{

protected void doRealThing(UUID deviceId, UUID assetId) {
//do other things, not db update or query.
}
}
@Component
public class MessagePublisher extends AbstractClass{
@Autowired
ExampleDeviceDao deviceDao;
@Autowired
SNSMessagePublisher snsMessagePublisher;
protected void doRealThing(UUID deviceId, UUID assetId) {
Optional<Device> device = deviceDao.findById(deviceId);
if (device.isPresent()) {
snsMessagePublisher.publishMessage(device.get());
}
}
}

问题可能是您的一级缓存(也称为持久性上下文(包含执行DML语句(update device ...(所针对的实体。不过,Hibernate不知道DML语句会改变持久性上下文中实体的状态,也不能对此做任何明智的事情。因此,更改是在数据库上执行的,但持久性上下文中的实体仍然保持旧状态。您可以做的唯一明智的事情是刷新实体(如果需要的话(,或者通过调用EntityManager.detach()将其从持久性上下文中删除。

据我所知,您的第二个问题是,即使数据库事务失败,您的发布者代码也会发布SNS消息。在这种情况下,你总是会有一个可能的问题,因为SNS客户端不是交易的一部分,也不可能。

您可以在交易完成后通过发送消息

  • @Transactional方法返回后调用发布者
  • 注册事务性提交后回调,请参阅https://stackoverflow.com/a/15026797/412446

如果JVM在事务完成后关闭但尚未发送SNS消息,或者由于某种原因SNS消息发送失败,则可能会错过发送某些消息。

为了解决这些问题,您通常会使用某种具有重试机制的作业队列。如果你对此解决方案感兴趣,你可以看看Blaze Notify,这是一个可以用来高效实现的工具包。您没有立即发送SNS消息,而是保留一个表示消息的实体,稍后将异步处理该消息并使用重试机制发送。

关于第一期,请参阅@ChristianBeikov的答案。

第二个问题与第一个问题的原因相同。你做

begin of updateAsset() method
device.get().setAsset(assetId); (1)
/** approach 1:*/
deviceDao.save(device.get()); (2)
Optional<Device> device = deviceDao.findById(deviceId); (3)
snsMessagePublisher.publishMessage(device.get()); (4)

end of updateAsset() method (5)
  1. 更改内存中的设备(持久上下文(。

  2. 将设备保存到数据库中。实际上Hibernate在这一点上什么都不做。您可以启用SQL日志记录,并看到不会发生任何事情。

  3. 您可以从数据库中获取Device。Hibernate再次对这里的数据库不做任何操作。它只是从内存中获取您在(1(步骤中所做的更改。请使用SQL日志进行验证。

  4. 更改后的Device被发送到SNS。

  5. @Transactional方法的结束。从方法返回后,Hibernate将所有更改从点(1(刷新到数据库(请参阅SQL日志(,并发生验证异常。但SNS消息已经发送了新的变化。数据库有旧的更改,因为事务已回滚。

可能的解决方案

您可以在发布SNS消息之前刷新数据库更改

deviceDao.save(device.get());
deviceDao.flushChanges();  // repository.flush()
snsMessagePublisher.publishMessage(device.get());

最新更新