Как реализовать операцию бронирования билетов с учетом таблиц мест, пользователей и фильмов?
Что проверяют
Умеете ли вы спроектировать модель, в которой одно место нельзя продать дважды, и видите ли, где в такой операции гонка. Следом почти всегда спрашивают: что будет, если два человека нажмут «купить» одновременно, и как держать место, пока идёт оплата.
Как отвечать
Начните со схемы. Место принадлежит залу, а не фильму, и занято оно не вообще, а на конкретном сеансе:
hall,seat: зал и его места (ряд, номер), статичный справочник;movie,screening: фильм и сеанс, то есть показ фильма в зале в конкретное время;booking: пользователь, сеанс, статус (HELD,PAID,CANCELLED) и срок удержанияexpires_at;booking_seat: пары «сеанс + место» с ограничениемUNIQUE (screening_id, seat_id).
Уникальное ограничение и есть главная защита. Даже если проверка в коде пропустила гонку, вторая вставка упадёт, а в Spring это придёт как DataIntegrityViolationException, которую вы превращаете в ответ «место уже занято».
Сама операция идёт в одной транзакции (@Transactional): вставить строки в booking_seat для всех выбранных мест, создать booking со статусом HELD и сроком удержания минут на десять. Если нужна явная блокировка, подойдёт SELECT ... FOR UPDATE по местам сеанса или оптимистическая блокировка через @Version в JPA. Просроченные удержания снимает фоновая задача (@Scheduled), либо запрос свободных мест просто не учитывает брони с истёкшим expires_at.
Добавьте идемпотентность: повтор запроса после таймаута не должен создавать вторую бронь.
Где спотыкаются
Заводят флаг занятости прямо в таблице мест. Тогда место оказывается занятым на все сеансы сразу, и схема ломается уже на втором показе.
Пишут «проверить, свободно ли, потом обновить» двумя запросами без блокировки и без уникального ограничения. На уровне изоляции READ COMMITTED, который в PostgreSQL стоит по умолчанию, обе транзакции увидят место свободным и обе его займут.
Забывают про удержание на время оплаты. Без статуса HELD и срока истечения место либо висит занятым навсегда после брошенной корзины, либо уходит другому, пока первый покупатель вводит карту.