我有一个与第三方系统连接以创建和存储汽车数据的系统。用户选择一个try
并使用它在我的系统中创建一个ThirdPartyCar
。
一种维修方法可以节省汽车。但是,只有在其他人尚未尝试使用该Car
来保存汽车时,它才应该保存:
ThirdPatyCar
以下是导致问题的情况:
@Transactional(transactionManager="mySystemTransactionManager", isolation=Isolation.?)
public void saveNewCarAndMapToThirdPartyCar(Car car, Long thirdPartyCarId) {
// Mapping table tracks which ThirdPartyCar was used to create my Car.
// The thirdPartyCarId is the primary key of the table.
if (!thirdPartyCarMapRepo.existsById(thirdPartyCarId)) {
// sleep to help test concurrency issues
log.debug("sleep");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("awake");
Car car = carRepository.save(car);
thirdPartyCarMapRepo.save(new ThirdPartyCarMap(thirdPartyCarId, car));
}
}
将隔离设置为低于Isolation.SERIALIZABLE的任何值,这两个用户似乎都将具有existById返回false。这样就创建了两辆汽车,只有最后保存的一辆被映射回第三方汽车。
如果我设置了Isolation.SERIALIZABLE,则即使来自不同的第三方汽车,用户也无法同时创建汽车。
当第三方汽车与用户A相同时,如何阻止用户B创建汽车,但是当第三方汽车与用户A不同时,仍然允许两者同时创建?
更新
经过更多思考和研究之后,我相信这可能不是事务隔离问题。
这是ThirdPartyCarMap表:
User A User B
existsById |
| |
| existsById
| |
| |
carRepo.save |
thirdPartyCarMapRepo.save |
| |
| |
| carRepo.save
| thirdPartyCarMapRepo.save
以上面的方案图为例:
用户A thirdPartyCarId (PK, bigint, not null)
carId (FK, bigint, not null) /* reference my system's car table */
这样做:
thirdPartyCarMapRepo.save
但是,用户B inserts into ThirdPartyCarMap (thirdPartyCarId, carId) values (45,1)
确实:
thirdPartyCarMapRepo.save
因此,导致问题的原因是保存调用的双重性质。我认为我有以下可能的解决方案:
update ThirdPartyCarMap set carId =2 where thirdPartyCarId=45
作为主键,但使其唯一。我认为最后一个选项可能是最好的,但是每个选项都有问题: -方法1:即使读取数据,isNew也会返回true -方法2:看起来有点像骇客 -方法3:似乎是为了JPA而将额外的数据添加到表中。
还有没有这些问题的更好的方法吗?
答案 0 :(得分:0)
一种选择是在数据库级别上使用唯一约束,这可以保证您不能拥有两个具有相同VIN的PhysicalCar(如果这是您在PhysicalCar数据库中的唯一标识)。这意味着在您的情况下,当两个线程将尝试添加PhysicalCar时,其中一个将因唯一约束冲突而失败。 Spring已经将数据库异常包装到DataIntegrityViolationException中,但是您可以从中提取约束名称,如果您希望在PhysicalCar重复的情况下抛出该约束名称,则可以对其执行操作。
答案 1 :(得分:0)
您可以使用ThirdPartyCar
或MD5创建用户A和用户B的Car
和hashCode()
对象的校验和,并检查是否相等。如果您发现用户B的汽车与用户A的ThirdPartyCar
相同,则抛出RuntimeException
,否则继续。