Jak działa mechanizm Optymistycznej Blokady w Spring Boot przy weryfikacji wersji w commandzie i bazie danych?

0

Cześć wszystkim,

Mam problem z testowaniem mechanizmu Optymistycznej Blokady w mojej aplikacji Spring Boot. W mojej abstrakcyjnej klasie encji mam pole @Version private Long id. Metoda updatePerson przyjmuje EditPersonCommand, w którym znajduje się pole private Long version.

Podczas testowania przez Postmana, podaję w commandzie wersję różną od tej w bazie, ale mimo to otrzymuję odpowiedź 200 OK, a encja jest aktualizowana, przy czym wersja w bazie rośnie o 1. Wygląda na to, że mechanizm działa, ale nie weryfikuje, czy wersja w command jest zgodna z wersją w bazie.

Pytanie się nasuwa takie, że skoro podnosi wersję o 1, to mechanizm działa. Jednak nie weryfikuje tego, czy wersja jest zgodna z tym, co jest w bazie, a tym, co przekazuję w commandzie.

Czy źle rozumiem działanie Optymistycznego Locka? Czy może on najpierw pobiera wersję z bazy przez findById, a potem, jeśli wersja się nie zmieniła do końca transakcji, nie wyrzuca wyjątku?

I w takim razie, czy nie potrzebuję przekazywać wersji w commandzie, bo on pilnuje tego na poziomie transakcji findById (version: 0) -> update (version: 0)?

Metoda do podglądu:

@Transactional
public PersonDTO updatePerson(Long id, EditPersonCommand command) {

    Person existingPerson = personRepository.findById(id)
            .orElseThrow(() -> new PersonNotFoundException("Person with id " + id + " not found"));
    log.info("Existing person: " + existingPerson.getVersion());

    modelMapper.map(command, existingPerson);
    log.info("Existing person after mapping: " + existingPerson.getVersion());

    validatePerson(existingPerson);

    Person savedPerson = personRepository.persistAndFlush(existingPerson);
    log.info("Saved person to DB: " + savedPerson.getVersion());

    return convertPersonToDTO(savedPerson);
}

Jak dodaje sobie takiego ifa, to oczywiście wyjątek wyrzuca, ale powinno działać i bez niego.

if (!existingPerson.getVersion().equals(command.getVersion())) {
    throw new OptimisticLockingFailureException("Version mismatch for Person with id " + id);
}

Z góry dziękuję za pomoc.

1

Hej. W optimistic locking chodzi o to, aby zablokować możliwość równoległej modyfikacji encji.
To co robi ten kod, to pobiera encje, edytuje ją i zapisuje (plus podbija encje). Robi to w jednym wątku i w jednej transakcji.
Spróbuj wywołać ten kod z dwóch różnych wątków. Wtedy ten wyjątek poleci - dodaj też gdzieś try {}catch, bo jest RuntimeException.
W jednym z projektów używaliśmy tego gdy mieliśmy synchronizacje dużej ilości danych z zewnętrznym systemem, i był o ryzyko, że jeden proces zrobi coś szybciej niż drugi.

3

Optimistic locking nie służy do tego abyś pobierał na front ten numer wersji a potem go odsyłał.

Powinieneś to kompletnie pominąć, pobrać sobie tego Persona na front, zrobić co chesz, spróbować zaktualizować a magia związana z lockowaniem powinna się dziać w metodzie updatePerson:

  1. pobierasz tam encję person przez personRepository.findById() (btw wysyłasz jakiś identyfikator typu uuid a nie id bazodanowy prawda? PRAWDA??) i przypisujesz do existingPerson obiekt z wersją np. 2
  2. aktualizujesz sobie encje (modelMapper.map())
  3. przy próbie zapis jeżeli w db dalej najnowsza wersja to 2 (pokrywa się z twoją z referencji existingPerson) to wszystko jest ok, jeżeli w międzyczasie ktoś tę samą encję zaktualizuje i numer wersji się podbije to aktualizacja poprzez persistAndFlush się nie powiedzie (bo Hibernate przy wykoywaniu update w warunku podaje numer wersji, w twoim przypadku 2, i nie będzie spełniony where) dostanie w odpowiedzi, że zaktualizowane 0 wierszy i finalnie poleci stosowny wyjątek który ty możesz w jakiś sposób obsłuzyć (np. przesłać na front stosowne info z prośbą o ponowienie albo po stronie backendu samemu ponowić ale narażasz się na niezrozumiałą utratę aktualizacji względem kogoś kto w międzyczasie dokonał aktualizacji).

Podsumowując:
informacje o numerze wersji w kontekście tego lockowania nigdy nie powinny wypłynąć poza Twój backend.

3
RequiredNickname napisał(a):

Podsumowując:
informacje o numerze wersji w kontekście tego lockowania nigdy nie powinny wypłynąć poza Twój backend.

Nic takiego nie wynika. Version można tez używać na froncie. Dlatego, że ktoś może chcieć wiedzieć, że w miedzyczasie ktoś wrzucił inną wersje oryginalnego dokumentu (i teraz mu nadpisujemy)
jeśli version jest tylko po stronie backendu to po prostu nadpisujemy czyjeś zmiany i jest to olane.

Co do oryginlnego posta:
pytanie co dokładnie robi modelMapper (ale może być wiele innych problemów) - kto durnej magii używa ten od durnej magii ginie.

W ramach testów wywaliłbym jeszcze persistAndFlush - i tak jest to niepotrzebne (a może właśnie bruździ- niestety nie pamiętam już troche Springa).

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.