diff --git a/src/main/java/alfio/manager/AdminReservationManager.java b/src/main/java/alfio/manager/AdminReservationManager.java index 706edecac2..44dbabc358 100644 --- a/src/main/java/alfio/manager/AdminReservationManager.java +++ b/src/main/java/alfio/manager/AdminReservationManager.java @@ -49,7 +49,6 @@ import org.springframework.context.MessageSource; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; -import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Component; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; @@ -88,7 +87,6 @@ public class AdminReservationManager { private final TicketReservationManager ticketReservationManager; private final TicketCategoryRepository ticketCategoryRepository; private final TicketRepository ticketRepository; - private final NamedParameterJdbcTemplate jdbc; private final SpecialPriceRepository specialPriceRepository; private final TicketReservationRepository ticketReservationRepository; private final EventRepository eventRepository; @@ -552,7 +550,7 @@ private Result checkExistingCategory(TicketsInfo ti, Event event private void createMissingTickets(Event event, int tickets) { final MapSqlParameterSource[] params = generateEmptyTickets(event, Date.from(ZonedDateTime.now(event.getZoneId()).toInstant()), tickets, Ticket.TicketStatus.FREE).toArray(MapSqlParameterSource[]::new); - jdbc.batchUpdate(ticketRepository.bulkTicketInitialization(), params); + ticketRepository.bulkTicketInitialization(params); } @Transactional @@ -696,15 +694,11 @@ private void removeTicketsFromReservation(TicketReservation reservation, Event e ticketRepository.resetCategoryIdForUnboundedCategoriesWithTicketIds(ticketIds); ticketFieldRepository.deleteAllValuesForTicketIds(ticketIds); - MapSqlParameterSource[] args = ticketIds.stream().map(id -> new MapSqlParameterSource("ticketId", id) - .addValue("reservationId", reservationId) - .addValue("eventId", event.getId()) - .addValue("newUuid", UUID.randomUUID().toString()) - ).toArray(MapSqlParameterSource[]::new); + List reservationIds = ticketRepository.findReservationIds(ticketIds); List ticketUUIDs = ticketRepository.findUUIDs(ticketIds); - int[] results = jdbc.batchUpdate(ticketRepository.batchReleaseTickets(), args); - Validate.isTrue(Arrays.stream(results).sum() == args.length, "Failed to update tickets"); + int[] results = ticketRepository.batchReleaseTickets(reservationId, ticketIds, event); + Validate.isTrue(Arrays.stream(results).sum() == ticketIds.size(), "Failed to update tickets"); if(!removeReservation) { if(forceInvoiceReceiptUpdate) { auditingRepository.insert(reservationId, userId, event.getId(), FORCED_UPDATE_INVOICE, date, RESERVATION, reservationId); diff --git a/src/main/java/alfio/manager/EventManager.java b/src/main/java/alfio/manager/EventManager.java index bd7dc6d379..f2f785aac2 100644 --- a/src/main/java/alfio/manager/EventManager.java +++ b/src/main/java/alfio/manager/EventManager.java @@ -51,7 +51,6 @@ import org.springframework.core.env.Environment; import org.springframework.core.env.Profiles; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; -import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; @@ -91,7 +90,6 @@ public class EventManager { private final TicketRepository ticketRepository; private final SpecialPriceRepository specialPriceRepository; private final PromoCodeDiscountRepository promoCodeRepository; - private final NamedParameterJdbcTemplate jdbc; private final ConfigurationManager configurationManager; private final TicketFieldRepository ticketFieldRepository; private final EventDeleterRepository eventDeleterRepository; @@ -339,7 +337,7 @@ public void updateEventPrices(EventAndOrganizationId original, EventModification Event modified = eventRepository.findById(eventId); if(seatsDifference > 0) { final MapSqlParameterSource[] params = generateEmptyTickets(modified, Date.from(ZonedDateTime.now(modified.getZoneId()).toInstant()), seatsDifference, TicketStatus.RELEASED).toArray(MapSqlParameterSource[]::new); - jdbc.batchUpdate(ticketRepository.bulkTicketInitialization(), params); + ticketRepository.bulkTicketInitialization(params); } else { List ids = ticketRepository.selectNotAllocatedTicketsForUpdate(eventId, Math.abs(seatsDifference), singletonList(TicketStatus.FREE.name())); Validate.isTrue(ids.size() == Math.abs(seatsDifference), "cannot lock enough tickets for deletion."); @@ -570,7 +568,7 @@ private Integer insertCategory(TicketCategoryModification tc, Event event) { TicketCategory ticketCategory = ticketCategoryRepository.getByIdAndActive(category.getKey(), eventId); if(tc.isBounded()) { List lockedTickets = ticketRepository.selectNotAllocatedTicketsForUpdate(eventId, ticketCategory.getMaxTickets(), asList(TicketStatus.FREE.name(), TicketStatus.RELEASED.name())); - jdbc.batchUpdate(ticketRepository.bulkTicketUpdate(), lockedTickets.stream().map(id -> new MapSqlParameterSource("id", id).addValue("categoryId", ticketCategory.getId()).addValue("srcPriceCts", ticketCategory.getSrcPriceCts())).toArray(MapSqlParameterSource[]::new)); + ticketRepository.bulkTicketUpdate(lockedTickets, ticketCategory); if(tc.isTokenGenerationRequested()) { insertTokens(ticketCategory); ticketRepository.revertToFree(eventId, ticketCategory.getId(), lockedTickets); @@ -699,9 +697,7 @@ void handleTicketNumberModification(Event event, TicketCategory original, Ticket //the updated category contains more tickets than the older one List lockedTickets = ticketRepository.selectNotAllocatedTicketsForUpdate(event.getId(), addedTickets, asList(TicketStatus.FREE.name(), TicketStatus.RELEASED.name())); Validate.isTrue(addedTickets == lockedTickets.size(), "Cannot add %d tickets. There are only %d free tickets.", addedTickets, lockedTickets.size()); - jdbc.batchUpdate(ticketRepository.bulkTicketUpdate(), lockedTickets.stream() - .map(id -> new MapSqlParameterSource("id", id).addValue("categoryId", updated.getId()).addValue("srcPriceCts", updated.getSrcPriceCts())) - .toArray(MapSqlParameterSource[]::new)); + ticketRepository.bulkTicketUpdate(lockedTickets, updated); if(updated.isAccessRestricted()) { //since the updated category is not public, the tickets shouldn't be distributed to waiting people. ticketRepository.revertToFree(event.getId(), updated.getId(), lockedTickets); @@ -718,14 +714,14 @@ void handleTicketNumberModification(Event event, TicketCategory original, Ticket } ticketRepository.invalidateTickets(ids); final MapSqlParameterSource[] params = generateEmptyTickets(event, Date.from(ZonedDateTime.now(event.getZoneId()).toInstant()), absDifference, TicketStatus.RELEASED).toArray(MapSqlParameterSource[]::new); - jdbc.batchUpdate(ticketRepository.bulkTicketInitialization(), params); + ticketRepository.bulkTicketInitialization(params); } } private void createAllTicketsForEvent(Event event, EventModification em) { Validate.notNull(em.getAvailableSeats()); final MapSqlParameterSource[] params = prepareTicketsBulkInsertParameters(ZonedDateTime.now(event.getZoneId()), event, em.getAvailableSeats(), TicketStatus.FREE); - jdbc.batchUpdate(ticketRepository.bulkTicketInitialization(), params); + ticketRepository.bulkTicketInitialization(params); } private int insertEvent(EventModification em) { diff --git a/src/main/java/alfio/manager/WaitingQueueManager.java b/src/main/java/alfio/manager/WaitingQueueManager.java index cdcec2ed75..b070e2f26e 100644 --- a/src/main/java/alfio/manager/WaitingQueueManager.java +++ b/src/main/java/alfio/manager/WaitingQueueManager.java @@ -46,6 +46,7 @@ import java.time.ZonedDateTime; import java.util.*; import java.util.function.Supplier; +import java.util.stream.Collectors; import java.util.stream.Stream; import static alfio.model.system.ConfigurationKeys.*; @@ -190,11 +191,11 @@ private void preReserveTickets(Event event, int ticketsNeeded, int eventId, int .sorted(Comparator.comparing(t -> t.getExpiration(event.getZoneId()))) .map(tc -> Pair.of(determineAvailableSeats(ticketCategoriesStats.get(tc.getId()), eventStatisticView), ticketCategoriesStats.get(tc.getId()))) .collect(new PreReservedTicketDistributor(toBeGenerated)); - MapSqlParameterSource[] candidates = collectedTickets.stream() + List ids = collectedTickets.stream() .flatMap(p -> selectTicketsForPreReservation(eventId, p).stream()) - .map(id -> new MapSqlParameterSource().addValue("id", id)) - .toArray(MapSqlParameterSource[]::new); - jdbc.batchUpdate(ticketRepository.preReserveTicket(), candidates); + .collect(Collectors.toList()); + + ticketRepository.preReserveTicket(ids); } private List selectTicketsForPreReservation(int eventId, Pair p) { diff --git a/src/main/java/alfio/repository/TicketRepository.java b/src/main/java/alfio/repository/TicketRepository.java index 5d6ad6430a..8a051f6836 100644 --- a/src/main/java/alfio/repository/TicketRepository.java +++ b/src/main/java/alfio/repository/TicketRepository.java @@ -16,10 +16,7 @@ */ package alfio.repository; -import alfio.model.FullTicketInfo; -import alfio.model.Ticket; -import alfio.model.TicketInfo; -import alfio.model.TicketWithReservationAndTransaction; +import alfio.model.*; import ch.digitalfondue.npjt.Bind; import ch.digitalfondue.npjt.Query; import ch.digitalfondue.npjt.QueryRepository; @@ -39,12 +36,19 @@ public interface TicketRepository { String REVERT_TO_FREE = "update ticket set status = 'FREE' where status = 'RELEASED' and event_id = :eventId"; - @Query(type = QueryType.TEMPLATE, value = "insert into ticket (uuid, creation, category_id, event_id, status, original_price_cts, paid_price_cts, src_price_cts)" - + "values(:uuid, :creation, :categoryId, :eventId, :status, 0, 0, :srcPriceCts)") - String bulkTicketInitialization(); + //TODO: refactor, try to move the MapSqlParameterSource inside the default method! + default void bulkTicketInitialization(MapSqlParameterSource[] args) { + getNamedParameterJdbcTemplate().batchUpdate("insert into ticket (uuid, creation, category_id, event_id, status, original_price_cts, paid_price_cts, src_price_cts)" + + "values(:uuid, :creation, :categoryId, :eventId, :status, 0, 0, :srcPriceCts)", args); + } - @Query(type = QueryType.TEMPLATE, value = "update ticket set category_id = :categoryId, src_price_cts = :srcPriceCts where id = :id") - String bulkTicketUpdate(); + default void bulkTicketUpdate(List ids, TicketCategory ticketCategory) { + MapSqlParameterSource[] params = ids.stream().map(id -> new MapSqlParameterSource("id", id) + .addValue("categoryId", ticketCategory.getId()) + .addValue("srcPriceCts", ticketCategory.getSrcPriceCts())) + .toArray(MapSqlParameterSource[]::new); + getNamedParameterJdbcTemplate().batchUpdate("update ticket set category_id = :categoryId, src_price_cts = :srcPriceCts where id = :id", params); + } @Query("select id from ticket where status in (:requiredStatuses) and category_id = :categoryId and event_id = :eventId and tickets_reservation_id is null order by id limit :amount for update") List selectTicketInCategoryForUpdate(@Bind("eventId") int eventId, @Bind("categoryId") int categoryId, @Bind("amount") int amount, @Bind("requiredStatuses") List requiredStatus); @@ -247,8 +251,14 @@ default Set findAllReservationsConfirmedButNotAssignedForUpdate(int even @Query(RELEASE_TICKET_QUERY) int releaseTicket(@Bind("reservationId") String reservationId, @Bind("newUuid") String newUuid, @Bind("eventId") int eventId, @Bind("ticketId") int ticketId); - @Query(value = RELEASE_TICKET_QUERY, type = QueryType.TEMPLATE) - String batchReleaseTickets(); + default int[] batchReleaseTickets(String reservationId, List ticketIds, Event event) { + MapSqlParameterSource[] args = ticketIds.stream().map(id -> new MapSqlParameterSource("ticketId", id) + .addValue("reservationId", reservationId) + .addValue("eventId", event.getId()) + .addValue("newUuid", UUID.randomUUID().toString()) + ).toArray(MapSqlParameterSource[]::new); + return getNamedParameterJdbcTemplate().batchUpdate(RELEASE_TICKET_QUERY, args); + } @Query("update ticket set status = 'RELEASED', uuid = :newUuid, " + RESET_TICKET + " where id = :ticketId and status = 'PENDING' and tickets_reservation_id = :reservationId and event_id = :eventId") int releaseExpiredTicket(@Bind("reservationId") String reservationId, @Bind("eventId") int eventId, @Bind("ticketId") int ticketId, @Bind("newUuid") String newUuid); @@ -289,8 +299,12 @@ default void resetTickets(List ticketIds) { @Query("select count(*) from ticket where status = 'PRE_RESERVED'") Integer countPreReservedTickets(@Bind("eventId") int eventId); - @Query(type = QueryType.TEMPLATE, value = "update ticket set status = 'PRE_RESERVED' where id = :id") - String preReserveTicket(); + default void preReserveTicket(List ids) { + MapSqlParameterSource[] params = ids.stream() + .map(id -> new MapSqlParameterSource().addValue("id", id)) + .toArray(MapSqlParameterSource[]::new); + getNamedParameterJdbcTemplate().batchUpdate("update ticket set status = 'PRE_RESERVED' where id = :id", params); + } @Query("select * from ticket where status = 'FREE' and event_id = :eventId") List findFreeByEventId(@Bind("eventId") int eventId); diff --git a/src/test/java/alfio/manager/EventManagerCategoriesTest.java b/src/test/java/alfio/manager/EventManagerCategoriesTest.java index 015ec0cd4d..b76c3db6c7 100644 --- a/src/test/java/alfio/manager/EventManagerCategoriesTest.java +++ b/src/test/java/alfio/manager/EventManagerCategoriesTest.java @@ -56,7 +56,7 @@ void init() { EventRepository eventRepository = mock(EventRepository.class); event = mock(Event.class); when(event.getId()).thenReturn(eventId); - eventManager = new EventManager(null, eventRepository, null, ticketCategoryRepository, ticketCategoryDescriptionRepository, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); + eventManager = new EventManager(null, eventRepository, null, ticketCategoryRepository, ticketCategoryDescriptionRepository, null, null, null, null, null, null, null, null, null, null, null, null, null, null); when(eventRepository.countExistingTickets(0)).thenReturn(availableSeats); when(event.getZoneId()).thenReturn(ZoneId.systemDefault()); } diff --git a/src/test/java/alfio/manager/EventManagerHandleTicketModificationTest.java b/src/test/java/alfio/manager/EventManagerHandleTicketModificationTest.java index 4d24579e51..c519843fdf 100644 --- a/src/test/java/alfio/manager/EventManagerHandleTicketModificationTest.java +++ b/src/test/java/alfio/manager/EventManagerHandleTicketModificationTest.java @@ -23,21 +23,16 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; -import org.springframework.jdbc.core.namedparam.SqlParameterSource; import java.time.ZoneId; import java.util.Arrays; import java.util.List; import static java.util.Collections.singletonList; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @@ -47,7 +42,6 @@ public class EventManagerHandleTicketModificationTest { private TicketCategory original; private TicketCategory updated; private TicketRepository ticketRepository; - private NamedParameterJdbcTemplate jdbc; private EventManager eventManager; private final int eventId = 10; private int originalCategoryId = 20; @@ -60,9 +54,9 @@ void init() { original = mock(TicketCategory.class); updated = mock(TicketCategory.class); ticketRepository = mock(TicketRepository.class); - jdbc = mock(NamedParameterJdbcTemplate.class); + when(event.getId()).thenReturn(eventId); - eventManager = new EventManager(null, null, null, null, null, ticketRepository, null, null, jdbc, null, null, null, null, null, null, null, null, null, null, null); + eventManager = new EventManager(null, null, null, null, null, ticketRepository, null, null, null, null, null, null, null, null, null, null, null, null, null); when(original.getId()).thenReturn(originalCategoryId); when(updated.getId()).thenReturn(updatedCategoryId); when(original.getSrcPriceCts()).thenReturn(1000); @@ -71,7 +65,6 @@ void init() { when(updated.getMaxTickets()).thenReturn(11); when(original.isBounded()).thenReturn(true); when(event.getZoneId()).thenReturn(ZoneId.systemDefault()); - when(ticketRepository.bulkTicketUpdate()).thenReturn("bulk"); } @DisplayName("throw exception if there are tickets already sold") @@ -96,7 +89,7 @@ void invalidateExceedingTickets() { void doNothingIfZero() { eventManager.handleTicketNumberModification(event, original, updated, 0, false); verify(ticketRepository, never()).invalidateTickets(anyList()); - verify(jdbc, never()).batchUpdate(anyString(), any(SqlParameterSource[].class)); + verify(ticketRepository, never()).bulkTicketUpdate(any(), any()); } @Test @@ -105,9 +98,7 @@ void insertTicketIfDifference1() { when(ticketRepository.selectNotAllocatedTicketsForUpdate(eq(eventId), eq(1), eq(Arrays.asList(Ticket.TicketStatus.FREE.name(), Ticket.TicketStatus.RELEASED.name())))).thenReturn(singletonList(1)); eventManager.handleTicketNumberModification(event, original, updated, 1, false); verify(ticketRepository, never()).invalidateTickets(anyList()); - ArgumentCaptor captor = ArgumentCaptor.forClass(SqlParameterSource[].class); - verify(jdbc, times(1)).batchUpdate(anyString(), captor.capture()); - assertEquals(1, captor.getValue().length); + verify(ticketRepository, times(1)).bulkTicketUpdate(any(), any()); } @Test diff --git a/src/test/java/alfio/manager/EventManagerHandleTokenModificationTest.java b/src/test/java/alfio/manager/EventManagerHandleTokenModificationTest.java index 4ce3352422..1f8906983a 100644 --- a/src/test/java/alfio/manager/EventManagerHandleTokenModificationTest.java +++ b/src/test/java/alfio/manager/EventManagerHandleTokenModificationTest.java @@ -53,7 +53,7 @@ void init() { TicketRepository ticketRepository = mock(TicketRepository.class); when(event.getId()).thenReturn(eventId); eventManager = new EventManager(null, null, null, null, - null, ticketRepository, specialPriceRepository, null, null, null, null, null, null, null, null, null, null, null, null, null); + null, ticketRepository, specialPriceRepository, null, null, null, null, null, null, null, null, null, null, null, null); when(original.getId()).thenReturn(20); when(updated.getId()).thenReturn(30); when(original.getSrcPriceCts()).thenReturn(1000); diff --git a/src/test/java/alfio/manager/EventManagerUnbindTicketsTest.java b/src/test/java/alfio/manager/EventManagerUnbindTicketsTest.java index fabb3c9fc6..87528d3b60 100644 --- a/src/test/java/alfio/manager/EventManagerUnbindTicketsTest.java +++ b/src/test/java/alfio/manager/EventManagerUnbindTicketsTest.java @@ -85,7 +85,7 @@ void setUp() { eventDescriptionRepository, ticketCategoryRepository, ticketCategoryDescriptionRepository, ticketRepository, specialPriceRepository, null, null, null, null, null, null, - null, null, null, organizationRepository, + null, null, organizationRepository, null, null, null); } diff --git a/src/test/java/alfio/manager/WaitingQueueManagerTest.java b/src/test/java/alfio/manager/WaitingQueueManagerTest.java index 69a58dfdfd..821a2d8891 100644 --- a/src/test/java/alfio/manager/WaitingQueueManagerTest.java +++ b/src/test/java/alfio/manager/WaitingQueueManagerTest.java @@ -157,7 +157,7 @@ void processPreReservations() { verify(ticketRepository).countWaiting(eq(eventId)); verify(ticketRepository, never()).revertToFree(eq(eventId)); verify(ticketRepository).countPreReservedTickets(eq(eventId)); - verify(ticketRepository).preReserveTicket(); + verify(ticketRepository).preReserveTicket(anyList()); verify(ticketRepository).selectWaitingTicketsForUpdate(eq(eventId), anyString(), anyInt()); } } \ No newline at end of file