Я использую 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 раз, с «для обновления» в конце.поэтому блокировки должны работать, но я не вижу ожидаемого результата.
Что не так с моим кодом?
Спасибо.