我正在使用 Spring Boot 2 和 Spring Data JPA。
我有一个带有 @Transactional 注释的服务,它从存储库中读取记录,然后添加记录(如果不存在)并保存所有记录。 我创建了一个并行执行服务方法 5 次的测试方法。 由于我使用 @Lock(LockModeType.PESSIMISTIC_WRITE) 我希望一个人在读取可用性时会获得锁,而其他 4 个线程必须等待事务 (createReservation) 完成,但方法会运行 5 次并返回没有记录,因此所有线程都尝试插入新记录,但它们都因唯一索引或主键冲突而失败(第一个记录除外)。 对于测试,我使用 H2 数据库。
预订服务:
@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);
}
}
可用性服务:
@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”。所以锁应该可以工作,但我没有看到我期望的结果。
我的代码有什么问题吗?
谢谢。
最佳答案
我认为问题是你想在每个线程中插入具有相同ID的记录。
// check availability, add records to this list if a record does not exist
锁不适用于新记录。你必须以某种方式锁定整个表。如果您完全确定您的服务器将只运行一个实例,您可以同步
您的方法,或者您可以创建一个带有“锁记录”的特殊表,并在创建该记录之前使用锁读取该记录。实际表中的新记录,然后释放该锁。
第一种方法非常简单,但第二种方法更安全。
关于java - 悲观锁不起作用 Spring Boot Data JPA,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54953003/