У меня есть система, которая взаимодействует со сторонней системой для создания и хранения данных об автомобиле. Пользователь выбирает 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, пользователь не сможет одновременно создавать автомобили даже из разных сторонних автомобилей.
Как я могу запретить пользователю B создавать автомобиль, когда сторонний автомобиль такой же, как у пользователя A, но при этом разрешить обоим одновременно создавать, когда сторонние автомобили разные?
Обновить
Немного подумав и изучив этот вопрос, я пришел к выводу, что это может быть не проблема изоляции транзакций.
Вот таблица ThirdPartyCarMap:
thirdPartyCarId (PK, bigint, not null)
carId (FK, bigint, not null) /* reference my system's car table */
В качестве примера на приведенной выше схеме сценария:
Пользователь А thirdPartyCarMapRepo.save
делает:
inserts into ThirdPartyCarMap (thirdPartyCarId, carId) values (45,1)
Однако пользователь B thirdPartyCarMapRepo.save
делает:
update ThirdPartyCarMap set carId =2 where thirdPartyCarId=45
Таким образом, двойная природа вызова сохранения вызывает проблемы. Я думаю, что у меня есть следующие возможные решения:
- Подход 1. Реализуйте Persistable и переопределите поведение isNew, как описано здесь
- Подход 2: добавьте собственный запрос в репозиторий, который выполняет вставку
- Подход 3: добавьте суррогатный первичный ключ (например, автоматически увеличивающийся идентификатор) и удалите
thirdPartyCarId
в качестве основного, но сделайте его уникальным.
Я думаю, что последний вариант, вероятно, лучше, но у каждого варианта есть проблемы: - Подход 1: isNew вернет true даже при чтении данных - Подход 2: немного похоже на взлом - Подход 3: Дополнительные данные добавлены в таблицу просто ради JPA кажется.
Есть ли другой лучший подход, который не имеет этих проблем?
ThirdPartyCarMap
и сохранить новый экземпляр с нулевым значением? - person yegodm   schedule 18.04.2019thirdPartyCarId
, но проблема в том, что PK не равен нулю при сохранении, потому что он всегда имеет значение из сторонней системы. Итак, я попытался заменить ПК на независимый от сторонней системы (т.е. автоматически увеличивающийся идентификатор). Пожалуйста, смотрите обновленный пост, чтобы узнать мои мысли об этом подходе и обновленный вопрос. Спасибо. - person James   schedule 18.04.2019Key(thirdPartyCarId:Long, carId:Long)
, сопоставленный с полемkey: Key
с аннотацией@EmbeddedId
. - person yegodm   schedule 18.04.2019