Запретить двум транзакциям создание одной и той же сущности, но разрешить одновременное создание - PullRequest
0 голосов
/ 17 апреля 2019

У меня есть система, которая взаимодействует со сторонней системой для создания и хранения данных автомобиля.Пользователь выбирает 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, похоже, что оба пользователя hasBBII возвращают false.Это приводит к созданию двух автомобилей, и только последний сохраненный автомобиль отображается на стороннем автомобиле.

Если я установлю Isolation.SERIALIZABLE, пользователь не сможет одновременно создавать автомобили даже из разных сторонних автомобилей.

Как я могу запретить пользователю B создавать автомобиль, если сторонний автомобиль такой же, как пользователь A, но все же позволяет одновременно создавать оба, когда автомобили сторонних производителей отличаются?

Обновление

Подумав и исследуя это немного больше, я полагаю, что это не может быть проблемой изоляции транзакции.

Вот таблица 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, как описано здесь
  • Подход 2: Добавить собственный запросв репозиторий, который выполняет вставку
  • Подход 3. Добавьте суррогатный первичный ключ (например, идентификатор с автоматическим приращением) и удалите thirdPartyCarId в качестве первичного, но сделайте его уникальным.

Я думаю, что последний вариант, вероятно, самый лучший, но у каждого из них есть проблемы: - Подход 1: isNew будет возвращать true, даже при чтении данных - Подход 2: Похоже на хак - Подход 3: Дополнительные данные добавляются в таблицу только дляпохоже на JPA.

Есть ли другой лучший подход, который не имеет этих проблем?

Ответы [ 2 ]

0 голосов
/ 18 апреля 2019

Вы можете создать контрольную сумму объекта ThirdPartyCar и Car пользователя A и пользователя B с помощью hashCode() или MD5 и проверить на равенство. Если вы обнаружите, что автомобиль пользователя B совпадает с автомобилем пользователя A ThirdPartyCar, бросьте RuntimeException, иначе продолжайте.

0 голосов
/ 18 апреля 2019

Одним из вариантов будет использование уникального ограничения на уровне БД, которое может гарантировать, что у вас не может быть двух PhysicalCars с одним и тем же VIN (если это ваша уникальная идентификация в вашей БД для PhysicalCar).Это означает, что в вашем случае, когда два потока попытаются добавить PhysicalCar, один из них потерпит неудачу с уникальным нарушением ограничения.Spring уже включает исключение DB в DataIntegrityViolationException, но вы можете извлечь из него имя ограничения, и, если вы ожидаете, что оно будет выброшено в случае дубликатов для вашей PhysicalCar, вы можете воздействовать на него.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...