Ticket Purchase Service Layer

In this lesson, we'll build the service layer functionality for purchasing tickets, implementing concurrent access handling and QR code generation to create a robust ticket purchasing system.

Understanding the Purchase Flow

The ticket purchase process involves several key steps:

  1. Finding the user and ticket type in the database
  2. Checking if tickets are still available
  3. Creating a new ticket record
  4. Generating a QR code for the ticket

Let's implement this in our TicketTypeServiceImpl class:

@Service @RequiredArgsConstructor public class TicketTypeServiceImpl implements TicketTypeService { private final UserRepository userRepository; private final TicketTypeRepository ticketTypeRepository; private final TicketRepository ticketRepository; private final QrCodeService qrCodeService; @Override @Transactional public Ticket purchaseTicket(UUID userId, UUID ticketTypeId) { // Look up the user User user = userRepository.findById(userId) .orElseThrow(() -> new UserNotFoundException( String.format("User with ID %s was not found", userId) )); // Get ticket type with pessimistic lock TicketType ticketType = ticketTypeRepository.findByIdWithLock(ticketTypeId) .orElseThrow(() -> new TicketTypeNotFoundException( String.format("Ticket type with ID %s was not found", ticketTypeId) )); // Check ticket availability int purchasedTickets = ticketRepository.countByTicketTypeId(ticketType.getId()); Integer totalAvailable = ticketType.getTotalAvailable(); if(purchasedTickets + 1 > totalAvailable) { throw new TicketsSoldOutException(); } // Create new ticket Ticket ticket = new Ticket(); ticket.setStatus(TicketStatusEnum.PURCHASED); ticket.setTicketType(ticketType); ticket.setPurchaser(user); // Save and generate QR code Ticket savedTicket = ticketRepository.save(ticket); qrCodeService.generateQrCode(savedTicket); return ticketRepository.save(savedTicket); } }

Handling Concurrent Access

To prevent overselling tickets when multiple users try to purchase at the same time, we use a pessimistic lock:

@Repository public interface TicketTypeRepository extends JpaRepository<TicketType, UUID> { @Query("SELECT tt FROM TicketType tt WHERE tt.id = :id") @Lock(LockModeType.PESSIMISTIC_WRITE) Optional<TicketType> findByIdWithLock(@Param("id") UUID id); }

This ensures that when one user is purchasing a ticket, other users must wait until the transaction completes before they can access the same ticket type.

Error Handling

We handle several error cases:

  • User not found
  • Ticket type not found
  • Tickets sold out
  • QR code generation failure

These are caught and handled by our global exception handler to provide clear error messages to users.

Summary

  • Implemented the initial purchase ticket functionality
© 2026 Devtiro Ltd. All rights reserved