【发布时间】:2019-04-17 18:44:33
【问题描述】:
我有一个与第三方系统交互以创建和存储汽车数据的系统。用户选择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));
}
}
这是导致问题的场景:
User A User B
existsById |
| |
| existsById
| |
| |
carRepo.save |
thirdPartyCarMapRepo.save |
| |
| |
| carRepo.save
| thirdPartyCarMapRepo.save
将隔离设置为低于 Isolation.SERIALIZABLE 的任何值,似乎两个用户都会让 existsById 返回 false。这导致创建了两辆汽车,并且只有最后保存的一辆被映射回第三方汽车。
如果我设置了 Isolation.SERIALIZABLE,即使是来自不同的第三方汽车,用户也不能同时创建汽车。
当第三方汽车与用户A相同时,如何防止用户B创建汽车,但当第三方汽车不同时仍允许两者同时创建?
更新
在对此进行了更多思考和研究之后,我相信这可能不是事务隔离问题。
这是 ThirdPartyCarMap 表:
thirdPartyCarId (PK, bigint, not null)
carId (FK, bigint, not null) /* reference my system's car table */
以上面的场景图为例:
用户 A thirdPartyCarMapRepo.save 这样做:
inserts into ThirdPartyCarMap (thirdPartyCarId, carId) values (45,1)
但是,用户 B thirdPartyCarMapRepo.save 会:
update ThirdPartyCarMap set carId =2 where thirdPartyCarId=45
因此,导致问题的是保存调用的双重性质。我认为我有以下可能的解决方案:
- 方法 1:实现 Persistable 并覆盖 isNew 行为,如 here 所述
- 方法 2:向执行插入操作的存储库添加本机查询
- 方法 3:添加代理主键(例如自动递增的 id)并删除
thirdPartyCarId作为主键,但使其唯一。
我认为最后一个选项可能是最好的,但每个选项都有问题: - 方法 1:即使在读取数据时 isNew 也会返回 true - 方法 2:看起来有点像 hack - 方法 3:似乎只是为了 JPA 而将额外数据添加到表中。
还有没有这些问题的更好的方法吗?
【问题讨论】:
-
您是否尝试将身份添加到
ThirdPartyCarMap,并使用空值保存新实例? -
@yegodm,是的,我的 id 为
thirdPartyCarId,但问题是 PK 在保存 b/c 时不为空,它始终具有来自第三方系统的值。因此,我尝试用独立于第三方系统的 PK 替换(即自动递增 id)。请参阅更新后的帖子,了解我对这种方法和更新问题的看法。谢谢。 -
哦,抱歉,我没有仔细阅读方法#3。这基本上就是我的意思。然而,我仍然会选择那个,而不是担心额外的数据。从另一个角度来看,如果我没记错的话,还有一种可能的方法。这两个 id 可以构成一个复合键,例如
Key(thirdPartyCarId:Long, carId:Long)映射到带有@EmbeddedId注释的字段key: Key。 -
不用担心。感谢您提供的附加选项,它可以避免数据库中的额外列,但我想可能会添加更多代码。我倾向于方法 3。
标签: spring multithreading jpa spring-transactions transaction-isolation