在餐厅场景中处理同时预订请求

时间:2017-11-22 07:11:29

标签: java spring multithreading hibernate jpa

我正在使用Spring数据JPA为Spring引导和HSQLDB的餐厅预订系统开发REST API项目。该场景是用户可以在特定时间致电预订服务以预订餐桌。在指定的时间段内只允许一次预订。

我的问题是如何处理两个或多个不同用户同时调用REST API以同时预订同一个表的场景。

我搜索了这个并发现乐观锁定可用于此但我不确定它如何在场景中有所帮助,除非我对餐厅表记录进行任何更新。

以下是我的项目结构。

预订DAO

@Entity
public class Booking {

    @Id
    @GeneratedValue
    private Long id;
    private String bookingId;
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm")
    private LocalDateTime bookingStart;
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm")
    private LocalDateTime bookingEnd;
    @OneToOne
    private RestaurantTable table;
    private String customerName;

    ..Setter/Getter
}

餐厅DAO

@Entity
public class RestaurantTable {

    @Id
    @GeneratedValue
    private Long id;
    private Integer tableId;
    private Integer capacity;
    private String description;

    //Setter/Getter
}

预订创建服务。

@Service
public class BookingService{

    @Transactional
    public Booking addBooking(final BookingDTO bookingRequest) {
        LOG.debug("Creating new booking for request: {}", bookingRequest);
        validateBookingData(bookingRequest);
        return bookingCreateService.createBooking(bookingRequest);
    }
}

@Service
public class BookingCreateService {

    @Autowired
    private BookingRepository bookingRepository;
    @Autowired
    private TableRepository tableRepository;

    public Booking createBooking(final BookingDTO bookingRequest) {
        Booking booking = null;
        final LocalDateTime bookingStart = bookingRequest.getBookingTime();
        final LocalDateTime bookingEnd = bookingRequest.getBookingTime().plusHours(1);
        RestaurantTable table = tableRepository
                .findAvailableTableWithAdequateSeatingCapacity(bookingStart, bookingEnd, bookingRequest.getCustomers())
                .stream().findFirst().orElse(null);

        if (table != null) {
            booking = saveBooking(table, bookingStart, bookingEnd, bookingRequest.getCustomerName());
        } else {
            throwErrorWithRecomemdedTime(bookingRequest.getCustomers(), bookingStart);
        }
        return booking;
    }

    private Booking saveBooking(final RestaurantTable table, final LocalDateTime bookingStart,
            final LocalDateTime bookingEnd, final String customerName) {
        Booking booking = new Booking();
        booking.setBookingStart(bookingStart);
        booking.setBookingEnd(bookingEnd);
        booking.setTable(table);
        booking.setBookingId(RandomStringUtils.randomAlphanumeric(7));
        booking.setCustomerName(customerName);
        return bookingRepository.save(booking);
    }

    private void throwErrorWithRecomemdedTime(final Integer customerCount, final LocalDateTime bookingStart) {
        Booking closestBookingWithFreeSlot = bookingRepository.findClosestBookingToInputTime(bookingStart);
        StringBuilder errorMsg = new StringBuilder();
        errorMsg.append("Table not available for capacity: ").append(customerCount).append(".");
        if (closestBookingWithFreeSlot != null) {
            errorMsg.append(" Table available after " + closestBookingWithFreeSlot.getBookingEnd() + ".");
        }
        throw new DataValidationException(errorMsg.toString());
    }
}

如果我使用BookingService完成Thread.sleep并创建另一个预订相同的表,那么它将同时为同一个表创建两个预订。我怎么能避免这个?我应该同步吗?会同步导致性能问题吗?

2 个答案:

答案 0 :(得分:1)

如果在数据库表上添加一个包含日期,表和时间段的唯一约束,只要您使用事务,就应该在其中一个上出现错误。 您可以捕获错误并向客户端返回消息,表明时间不再可用。

答案 1 :(得分:0)

使用@Version注释的乐观锁定在这里无济于事。 @Version用作以下

@Entity
public class Booking {

  @Id
  @GeneratedValue
  private Long id;

  @Version
  Long version;

  private String bookingId;
  @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm")
  private LocalDateTime bookingStart;
  @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm")
  private LocalDateTime bookingEnd;
  @OneToOne
  private RestaurantTable table;
  private String customerName;

  ..Setter/Getter
}

因此在使用@Version之后,您无法使用相同的版本更新预订,以确保不会同时更新两个预订。但事实并非如此,因为您只是创建了一个新的预订,因此我认为@Transactional对于单线程应用程序来说足够了,因为您要锁定整个过程直到预订完成