悲观锁不起作用Spring Boot Data JPA

时间:2019-03-01 22:12:46

标签: java spring hibernate spring-boot spring-data-jpa

我正在使用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”。因此锁应该可以正常工作,但我看不到预期的结果。

我的代码有什么问题?

谢谢。

1 个答案:

答案 0 :(得分:1)

我认为问题在于您想在每个线程中插入具有相同ID的记录。

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

锁对新记录无效。您必须以某种方式锁定整个表。如果您完全确定服务器将仅运行一个实例,则可以synchronize方法,也可以使用'lock-records'创建一个特殊的表并使用锁读取该记录,然后再在其中创建新记录。实际的表,然后释放该锁。

第一种方法很简单,但是第二种方法更安全。