diff --git a/backend/backend-submodule b/backend/backend-submodule index 84b387b62..6c3ca1421 160000 --- a/backend/backend-submodule +++ b/backend/backend-submodule @@ -1 +1 @@ -Subproject commit 84b387b6292af52ca799ca7b04fb43e549cc24c7 +Subproject commit 6c3ca1421f3ccf5a12e1eca3b466e3ef62e17a01 diff --git a/backend/src/main/java/hanglog/listener/DeleteEventListener.java b/backend/src/main/java/hanglog/listener/DeleteEventListener.java index 2566e8755..8323b0dd8 100644 --- a/backend/src/main/java/hanglog/listener/DeleteEventListener.java +++ b/backend/src/main/java/hanglog/listener/DeleteEventListener.java @@ -1,19 +1,9 @@ package hanglog.listener; -import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW; - -import hanglog.expense.domain.repository.ExpenseRepository; -import hanglog.login.domain.repository.RefreshTokenRepository; import hanglog.member.domain.MemberDeleteEvent; import hanglog.trip.domain.TripDeleteEvent; import hanglog.trip.domain.repository.CustomDayLogRepository; import hanglog.trip.domain.repository.CustomItemRepository; -import hanglog.trip.domain.repository.DayLogRepository; -import hanglog.trip.domain.repository.ImageRepository; -import hanglog.trip.domain.repository.ItemRepository; -import hanglog.trip.domain.repository.PlaceRepository; -import hanglog.trip.domain.repository.TripCityRepository; -import hanglog.trip.domain.repository.TripRepository; import hanglog.trip.dto.ItemElement; import java.util.List; import lombok.RequiredArgsConstructor; @@ -28,68 +18,38 @@ public class DeleteEventListener { private final CustomDayLogRepository customDayLogRepository; private final CustomItemRepository customItemRepository; - private final PlaceRepository placeRepository; - private final ExpenseRepository expenseRepository; - private final ImageRepository imageRepository; - private final ItemRepository itemRepository; - private final DayLogRepository dayLogRepository; - private final TripCityRepository tripCityRepository; - private final TripRepository tripRepository; - private final RefreshTokenRepository refreshTokenRepository; + private final TransactionalDeleteProcessor transactionalDeleteProcessor; @Async - @Transactional(propagation = REQUIRES_NEW) - @TransactionalEventListener(fallbackExecution = true) + @Transactional + @TransactionalEventListener public void deleteMember(final MemberDeleteEvent event) { final List dayLogIds = customDayLogRepository.findDayLogIdsByTripIds(event.getTripIds()); - final List itemElements = customItemRepository.findItemIdsByDayLogIds(dayLogIds); - - deletePlaces(itemElements); - deleteExpenses(itemElements); - deleteImageAndItems(itemElements); - - dayLogRepository.deleteByIds(dayLogIds); - tripRepository.deleteByMemberId(event.getMemberId()); - refreshTokenRepository.deleteByMemberId(event.getMemberId()); + transactionalDeleteProcessor.deleteRefreshTokens(event.getMemberId()); + transactionalDeleteProcessor.deleteTrips(event.getMemberId()); + transactionalDeleteProcessor.deleteTripCitesByTripIds(event.getTripIds()); + deleteTripElements(dayLogIds); } @Async - @Transactional(propagation = REQUIRES_NEW) - @TransactionalEventListener(fallbackExecution = true) + @Transactional + @TransactionalEventListener public void deleteTrip(final TripDeleteEvent event) { final List dayLogIds = customDayLogRepository.findDayLogIdsByTripId(event.getTripId()); - final List itemElements = customItemRepository.findItemIdsByDayLogIds(dayLogIds); - - deletePlaces(itemElements); - deleteExpenses(itemElements); - deleteImageAndItems(itemElements); - - dayLogRepository.deleteByIds(dayLogIds); - tripCityRepository.deleteAllByTripId(event.getTripId()); - } - - private void deletePlaces(final List itemElements) { - final List placeIds = itemElements.stream() - .map(ItemElement::getPlaceId) - .toList(); - - placeRepository.deleteByIds(placeIds); - } - - private void deleteExpenses(final List itemElements) { - final List expenseIds = itemElements.stream() - .map(ItemElement::getExpenseId) - .toList(); - - expenseRepository.deleteByIds(expenseIds); + transactionalDeleteProcessor.deleteTripCitesByTripId(event.getTripId()); + deleteTripElements(dayLogIds); } - private void deleteImageAndItems(final List itemElements) { + private void deleteTripElements(final List dayLogIds) { + final List itemElements = customItemRepository.findItemIdsByDayLogIds(dayLogIds); final List itemIds = itemElements.stream() .map(ItemElement::getItemId) .toList(); - imageRepository.deleteByItemIds(itemIds); - itemRepository.deleteByIds(itemIds); + transactionalDeleteProcessor.deleteDayLogs(dayLogIds); + transactionalDeleteProcessor.deleteItems(itemIds); + transactionalDeleteProcessor.deletePlaces(itemElements); + transactionalDeleteProcessor.deleteExpenses(itemElements); + transactionalDeleteProcessor.deleteImages(itemIds); } } diff --git a/backend/src/main/java/hanglog/listener/TransactionalDeleteProcessor.java b/backend/src/main/java/hanglog/listener/TransactionalDeleteProcessor.java new file mode 100644 index 000000000..f7080aa40 --- /dev/null +++ b/backend/src/main/java/hanglog/listener/TransactionalDeleteProcessor.java @@ -0,0 +1,73 @@ +package hanglog.listener; + +import hanglog.expense.domain.repository.ExpenseRepository; +import hanglog.login.domain.repository.RefreshTokenRepository; +import hanglog.trip.domain.repository.DayLogRepository; +import hanglog.trip.domain.repository.ImageRepository; +import hanglog.trip.domain.repository.ItemRepository; +import hanglog.trip.domain.repository.PlaceRepository; +import hanglog.trip.domain.repository.TripCityRepository; +import hanglog.trip.domain.repository.TripRepository; +import hanglog.trip.dto.ItemElement; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +@Transactional(propagation = Propagation.REQUIRES_NEW) +public class TransactionalDeleteProcessor { + + private final PlaceRepository placeRepository; + private final ExpenseRepository expenseRepository; + private final ImageRepository imageRepository; + private final ItemRepository itemRepository; + private final DayLogRepository dayLogRepository; + private final TripCityRepository tripCityRepository; + private final TripRepository tripRepository; + private final RefreshTokenRepository refreshTokenRepository; + + public void deleteTrips(final Long memberId) { + tripRepository.deleteByMemberId(memberId); + } + + public void deleteTripCitesByTripId(final Long tripId) { + tripCityRepository.deleteAllByTripId(tripId); + } + + public void deleteTripCitesByTripIds(final List tripIds) { + tripCityRepository.deleteAllByTripIds(tripIds); + } + + public void deleteDayLogs(final List dayLogIds) { + dayLogRepository.deleteByIds(dayLogIds); + } + + public void deletePlaces(final List itemElements) { + final List placeIds = itemElements.stream() + .map(ItemElement::getPlaceId) + .toList(); + placeRepository.deleteByIds(placeIds); + } + + public void deleteExpenses(final List itemElements) { + final List expenseIds = itemElements.stream() + .map(ItemElement::getExpenseId) + .toList(); + expenseRepository.deleteByIds(expenseIds); + } + + public void deleteItems(final List itemIds) { + itemRepository.deleteByIds(itemIds); + } + + public void deleteImages(final List itemIds) { + imageRepository.deleteByItemIds(itemIds); + } + + public void deleteRefreshTokens(final Long memberId) { + refreshTokenRepository.deleteByMemberId(memberId); + } +} diff --git a/backend/src/main/java/hanglog/trip/domain/repository/TripCityRepository.java b/backend/src/main/java/hanglog/trip/domain/repository/TripCityRepository.java index e0224c698..56e6c4bdd 100644 --- a/backend/src/main/java/hanglog/trip/domain/repository/TripCityRepository.java +++ b/backend/src/main/java/hanglog/trip/domain/repository/TripCityRepository.java @@ -10,6 +10,13 @@ public interface TripCityRepository extends JpaRepository { + @Query(""" + SELECT new hanglog.trip.dto.TripCityElement (tc.trip.id, tc.city) + FROM TripCity tc + WHERE tc.trip.id IN :tripIds + """) + List findTripIdAndCitiesByTripIds(@Param("tripIds") final List tripIds); + @Modifying @Query(""" UPDATE TripCity tripCity @@ -18,10 +25,11 @@ public interface TripCityRepository extends JpaRepository { """) void deleteAllByTripId(@Param("tripId") final Long tripId); + @Modifying @Query(""" - SELECT new hanglog.trip.dto.TripCityElement (tc.trip.id, tc.city) - FROM TripCity tc - WHERE tc.trip.id IN :tripIds + UPDATE TripCity tripCity + SET tripCity.status = 'DELETED' + WHERE tripCity.trip.id IN :tripIds """) - List findTripIdAndCitiesByTripIds(@Param("tripIds") final List tripIds); + void deleteAllByTripIds(@Param("tripId") final List tripIds); } diff --git a/backend/src/test/java/hanglog/member/event/DeleteEventListenerTest.java b/backend/src/test/java/hanglog/member/event/DeleteEventListenerTest.java index c2ad68496..7caa4eded 100644 --- a/backend/src/test/java/hanglog/member/event/DeleteEventListenerTest.java +++ b/backend/src/test/java/hanglog/member/event/DeleteEventListenerTest.java @@ -7,19 +7,12 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import hanglog.expense.domain.repository.ExpenseRepository; +import hanglog.listener.TransactionalDeleteProcessor; import hanglog.listener.DeleteEventListener; -import hanglog.login.domain.repository.RefreshTokenRepository; import hanglog.member.domain.MemberDeleteEvent; import hanglog.trip.domain.TripDeleteEvent; import hanglog.trip.domain.repository.CustomDayLogRepository; import hanglog.trip.domain.repository.CustomItemRepository; -import hanglog.trip.domain.repository.DayLogRepository; -import hanglog.trip.domain.repository.ImageRepository; -import hanglog.trip.domain.repository.ItemRepository; -import hanglog.trip.domain.repository.PlaceRepository; -import hanglog.trip.domain.repository.TripCityRepository; -import hanglog.trip.domain.repository.TripRepository; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.DisplayName; @@ -36,22 +29,10 @@ class DeleteEventListenerTest { private CustomDayLogRepository customDayLogRepository; @Mock private CustomItemRepository customItemRepository; + @Mock - private PlaceRepository placeRepository; - @Mock - private ExpenseRepository expenseRepository; - @Mock - private ImageRepository imageRepository; - @Mock - private ItemRepository itemRepository; - @Mock - private DayLogRepository dayLogRepository; - @Mock - private TripRepository tripRepository; - @Mock - private RefreshTokenRepository refreshTokenRepository; - @Mock - private TripCityRepository tripCityRepository; + private TransactionalDeleteProcessor transactionalDeleteProcessor; + @InjectMocks private DeleteEventListener listener; @@ -62,28 +43,32 @@ void deleteMember() { final MemberDeleteEvent event = new MemberDeleteEvent(List.of(1L, 2L, 3L), 1L); when(customDayLogRepository.findDayLogIdsByTripIds(event.getTripIds())).thenReturn(new ArrayList<>()); + doNothing().when(transactionalDeleteProcessor).deleteRefreshTokens(anyLong()); + doNothing().when(transactionalDeleteProcessor).deleteTrips(anyLong()); + doNothing().when(transactionalDeleteProcessor).deleteTripCitesByTripIds(anyList()); + when(customItemRepository.findItemIdsByDayLogIds(anyList())).thenReturn(new ArrayList<>()); - doNothing().when(placeRepository).deleteByIds(anyList()); - doNothing().when(expenseRepository).deleteByIds(anyList()); - doNothing().when(imageRepository).deleteByItemIds(anyList()); - doNothing().when(itemRepository).deleteByIds(anyList()); - doNothing().when(dayLogRepository).deleteByIds(anyList()); - doNothing().when(tripRepository).deleteByMemberId(anyLong()); - doNothing().when(refreshTokenRepository).deleteByMemberId(anyLong()); + doNothing().when(transactionalDeleteProcessor).deleteDayLogs(anyList()); + doNothing().when(transactionalDeleteProcessor).deleteItems(anyList()); + doNothing().when(transactionalDeleteProcessor).deletePlaces(anyList()); + doNothing().when(transactionalDeleteProcessor).deleteExpenses(anyList()); + doNothing().when(transactionalDeleteProcessor).deleteImages(anyList()); // when listener.deleteMember(event); // then verify(customDayLogRepository, times(1)).findDayLogIdsByTripIds(event.getTripIds()); + verify(transactionalDeleteProcessor, times(1)).deleteRefreshTokens(anyLong()); + verify(transactionalDeleteProcessor, times(1)).deleteTrips(anyLong()); + verify(transactionalDeleteProcessor, times(1)).deleteTripCitesByTripIds(anyList()); + verify(customItemRepository, times(1)).findItemIdsByDayLogIds(anyList()); - verify(placeRepository, times(1)).deleteByIds(anyList()); - verify(expenseRepository, times(1)).deleteByIds(anyList()); - verify(imageRepository, times(1)).deleteByItemIds(anyList()); - verify(itemRepository, times(1)).deleteByIds(anyList()); - verify(dayLogRepository, times(1)).deleteByIds(anyList()); - verify(tripRepository, times(1)).deleteByMemberId(anyLong()); - verify(refreshTokenRepository, times(1)).deleteByMemberId(anyLong()); + verify(transactionalDeleteProcessor, times(1)).deletePlaces(anyList()); + verify(transactionalDeleteProcessor, times(1)).deleteExpenses(anyList()); + verify(transactionalDeleteProcessor, times(1)).deleteItems(anyList()); + verify(transactionalDeleteProcessor, times(1)).deleteImages(anyList()); + verify(transactionalDeleteProcessor, times(1)).deleteDayLogs(anyList()); } @DisplayName("deleteTrip 메서드에서 올바르게 레포지토리의 메서드를 호출한다.") @@ -93,13 +78,14 @@ void deleteTrip() { final TripDeleteEvent event = new TripDeleteEvent(1L); when(customDayLogRepository.findDayLogIdsByTripId(event.getTripId())).thenReturn(new ArrayList<>()); + doNothing().when(transactionalDeleteProcessor).deleteTripCitesByTripId(anyLong()); + when(customItemRepository.findItemIdsByDayLogIds(anyList())).thenReturn(new ArrayList<>()); - doNothing().when(placeRepository).deleteByIds(anyList()); - doNothing().when(expenseRepository).deleteByIds(anyList()); - doNothing().when(imageRepository).deleteByItemIds(anyList()); - doNothing().when(itemRepository).deleteByIds(anyList()); - doNothing().when(dayLogRepository).deleteByIds(anyList()); - doNothing().when(tripCityRepository).deleteAllByTripId(anyLong()); + doNothing().when(transactionalDeleteProcessor).deleteDayLogs(anyList()); + doNothing().when(transactionalDeleteProcessor).deleteItems(anyList()); + doNothing().when(transactionalDeleteProcessor).deletePlaces(anyList()); + doNothing().when(transactionalDeleteProcessor).deleteExpenses(anyList()); + doNothing().when(transactionalDeleteProcessor).deleteImages(anyList()); // when listener.deleteTrip(event); @@ -107,11 +93,12 @@ void deleteTrip() { // then verify(customDayLogRepository, times(1)).findDayLogIdsByTripId(event.getTripId()); verify(customItemRepository, times(1)).findItemIdsByDayLogIds(anyList()); - verify(placeRepository, times(1)).deleteByIds(anyList()); - verify(expenseRepository, times(1)).deleteByIds(anyList()); - verify(imageRepository, times(1)).deleteByItemIds(anyList()); - verify(itemRepository, times(1)).deleteByIds(anyList()); - verify(dayLogRepository, times(1)).deleteByIds(anyList()); - verify(tripCityRepository, times(1)).deleteAllByTripId(anyLong()); + + verify(customItemRepository, times(1)).findItemIdsByDayLogIds(anyList()); + verify(transactionalDeleteProcessor, times(1)).deletePlaces(anyList()); + verify(transactionalDeleteProcessor, times(1)).deleteExpenses(anyList()); + verify(transactionalDeleteProcessor, times(1)).deleteItems(anyList()); + verify(transactionalDeleteProcessor, times(1)).deleteImages(anyList()); + verify(transactionalDeleteProcessor, times(1)).deleteDayLogs(anyList()); } }