我正在使用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次选择查询,最后是“ for update”。因此锁应该可以正常工作,但我看不到预期的结果。
我的代码有什么问题?
谢谢。
答案 0 :(得分:1)
我认为问题在于您想在每个线程中插入具有相同ID的记录。
// check availability, add records to this list if a record does not exist
锁对新记录无效。您必须以某种方式锁定整个表。如果您完全确定服务器将仅运行一个实例,则可以synchronize
方法,也可以使用'lock-records'创建一个特殊的表并使用锁读取该记录,然后再在其中创建新记录。实际的表,然后释放该锁。
第一种方法很简单,但是第二种方法更安全。