Пессимистическая блокировка не работает Spring Boot Data JPA - PullRequest
0 голосов
/ 02 марта 2019

Я использую Spring Boot 2 и Spring Data JPA.

У меня есть служба с аннотацией @Transactional, которая читает записи из репозитория, затем добавляет записи, если они не существуют, и сохраняет все.Я создал тестовый метод, который выполняет сервисный метод 5 раз параллельно.Так как я использую @Lock (LockModeType.PESSIMISTIC_WRITE), я ожидаю, что одна получит блокировку при чтении Доступности, а другим 4 потокам придется ждать, пока транзакция (createReservation) не будет завершена, но вместо этого метод будет запущен 5 раз и возвращаетнет записей, поэтому все потоки пытаются вставить новую запись, и все они терпят неудачу (кроме первой) с уникальным индексом или нарушением первичного ключа.Для теста я использую базу данных H2.

ReservationService:

@Service
public class ReservationService {

  @Autowired
  private AvailabilityService availabilityService;
  @Autowired
  private ReservationRepository repository;

  @Transactional
  public Reservation createReservation(Reservation r) {
    availabilityService.updateAvailability( r);
    return reservationRepository.save( r);
  }
}

AvailabilityService:

@Service
public class DayAvailabilityService {

  @Autowired
  private AvailabilityRepository availabilityRepository;

  public List<Availability> updateAvailability(Reservation reservation) {
    List<LocalDate> dates = reservation.getStart().datesUntil(reservation.getEnd()).collect(Collectors.toList());
    List<Availability> availabilities = availabilityRepository.findAllById(dates);
    // check availability, add records to this list if a record does not exist
    /// ...
    return availabilityRepository.saveAll(availabilities);
  }

}

public interface AvailabilityRepository extends JpaRepository<Availability, LocalDate> {

@Override
@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Availability> findAllById(Iterable<LocalDate> iterable);

}

Сущность доступности:

@Entity
@Table(name = "Availability")
public class Availability {

  @Column(name = "Date")
  @Id
  @NotNull
  private LocalDate date;
  @Column(name = "Availability")
  private int availability;
  @Column(name = "MaxAvailability")
  private int maxAvailability;
}

Это тестовый класс:

@SpringBootTest
@RunWith(SpringRunner.class)
public class ReservationServiceIntegrationTest {

  @Autowired
  private ReservationService service;
  @Autowired
  private ReservationRepository repository;

  @Test
  public void testConcurrentCreateReservation() throws InterruptedException {
    Reservation reservation = new Reservation("John", "Doe", "johndoe@mail.com",
            LocalDate.now().plusDays(4), LocalDate.now().plusDays(6), 30);
    runMultithreaded(() -> {
        try {
            service.createReservation(reservation);
        } catch (NoAvailabilityException e) {
            System.out.println("no availability.");
        }
    }, 5);

    long count = repository.count();
    assertEquals(3, count);

  }

  public static void runMultithreaded(Runnable  runnable, int threadCount) throws InterruptedException {
    List<Thread> threadList = new LinkedList<>();

    for(int i = 0 ; i < threadCount; i++) {
        threadList.add(new Thread(runnable));
    }

    for( Thread t :  threadList) {
        t.start();
    }

    for( Thread t :  threadList) {
        t.join();
    }
  }
}

в журналах. Я вижу, что транзакция создается для каждого метода createReservation.

Getting transaction for [com.company.app.service.ReservationService.createReservation]

Затем я вижу 5 журналов, таких как:

Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAllById]

Затем я вижу запрос на выбор, выполненный 5 раз, с «для обновления» в конце.поэтому блокировки должны работать, но я не вижу ожидаемого результата.

Что не так с моим кодом?

Спасибо.

1 Ответ

0 голосов
/ 02 марта 2019

Мне кажется, проблема в том, что вы хотите вставить записи с одинаковым идентификатором в каждый поток.

// check availability, add records to this list if a record does not exist

Блокировки не работают для новых записей.Вы должны как-то заблокировать всю таблицу.Вы можете либо synchronize свой метод, если вы абсолютно уверены, что на вашем сервере будет работать только один экземпляр, или вы можете создать специальную таблицу с 'lock-records' и прочитать эту запись с блокировкой, прежде чем создавать новые записи в фактическомтаблицы, затем снимите эту блокировку.

Первый подход довольно прост, но второй более надежен.

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