Skip to content

Commit

Permalink
Member 탈퇴에 따른 엔티티 Soft Delete 처리 성능 개선 (#704)
Browse files Browse the repository at this point in the history
* feat: memberId로 tripId 리스트 반환하는 기능 추가

* feat: tripId 리스트로 dayLogId 리스트 반환하는 기능 추가

* feat: dayLogId 리스트로 itemId 리스트 반환하는 기능 추가

* refactor: itemId, placeId, expenseId를 가진 DTO 리스트 반환하도록 변경

* refactor: memberId에 따른 엔티티 status 변경 및 refreshToken 삭제 성능 개선

* refactor: repository 구현체 패키지 변경

* refactor: SharedTrip, PublishedTrip도 함께 업데이트되도록 변경

* refactor: event 적용

* refactor: 트랜잭션 분리

* refactor: `@Async`로 비동기 처리

* refactor: 메서드 분리

* test: AuthServiceIntegrationTest 수정

* test: MemberDeleteEventListener Mock Test 작성

* refactor: indent 변경

* refactor: final 추가

* refactor: event 관련 클래스 패키지 위치 변경

* refactor: SharedStatusType 위치 변경

* refactor: WriterResponse 위치 변경

* refactor: PublishedStatusType 위치 변경
  • Loading branch information
jjongwa authored Oct 12, 2023
1 parent decf270 commit 5ab4694
Show file tree
Hide file tree
Showing 41 changed files with 463 additions and 41 deletions.
2 changes: 2 additions & 0 deletions backend/src/main/java/hanglog/HangLogApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableAsync
@EnableJpaAuditing
@EnableScheduling
@SpringBootApplication
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@
import hanglog.auth.domain.RefreshToken;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface RefreshTokenRepository extends JpaRepository<RefreshToken, String> {

Optional<RefreshToken> findByToken(final String token);

boolean existsByToken(final String token);

void deleteByMemberId(final Long memberId);
@Modifying
@Query("""
DELETE FROM RefreshToken refreshToken
WHERE refreshToken.memberId = :memberId
""")
void deleteByMemberId(@Param("memberId") final Long memberId);
}
22 changes: 16 additions & 6 deletions backend/src/main/java/hanglog/auth/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@
import hanglog.auth.domain.repository.RefreshTokenRepository;
import hanglog.global.exception.AuthException;
import hanglog.member.domain.Member;
import hanglog.member.domain.MemberDeleteEvent;
import hanglog.member.domain.repository.MemberRepository;
import hanglog.trip.domain.repository.TripRepository;
import hanglog.share.domain.repository.SharedTripRepository;
import hanglog.trip.domain.repository.CustomTripRepository;
import hanglog.trip.domain.repository.PublishedTripRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -28,12 +33,15 @@ public class AuthService {
private static final int MAX_TRY_COUNT = 5;
private static final int FOUR_DIGIT_RANGE = 10000;

private final RefreshTokenRepository refreshTokenRepository;
private final PublishedTripRepository publishedTripRepository;
private final MemberRepository memberRepository;
private final CustomTripRepository customTripRepository;
private final SharedTripRepository sharedTripRepository;
private final OauthProviders oauthProviders;
private final RefreshTokenRepository refreshTokenRepository;
private final TripRepository tripRepository;
private final JwtProvider jwtProvider;
private final BearerAuthorizationExtractor bearerExtractor;
private final ApplicationEventPublisher publisher;

public MemberTokens login(final String providerName, final String code) {
final OauthProvider provider = oauthProviders.mapping(providerName);
Expand Down Expand Up @@ -89,8 +97,10 @@ public void removeRefreshToken(final String refreshToken) {
}

public void deleteAccount(final Long memberId) {
refreshTokenRepository.deleteByMemberId(memberId);
tripRepository.deleteAllByMemberId(memberId);
memberRepository.deleteById(memberId);
final List<Long> tripIds = customTripRepository.findTripIdsByMemberId(memberId);
publishedTripRepository.deleteByTripIds(tripIds);
sharedTripRepository.deleteByTripIds(tripIds);
memberRepository.deleteByMemberId(memberId);
publisher.publishEvent(new MemberDeleteEvent(tripIds, memberId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import hanglog.city.domain.City;
import hanglog.city.dto.response.CityResponse;
import hanglog.share.dto.response.WriterResponse;
import hanglog.member.dto.response.WriterResponse;
import hanglog.trip.domain.Trip;
import java.time.LocalDate;
import java.util.List;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package hanglog.community.service;

import static hanglog.community.domain.recommendstrategy.RecommendType.LIKE;
import static hanglog.community.domain.type.PublishedStatusType.PUBLISHED;
import static hanglog.global.exception.ExceptionCode.NOT_FOUND_TRIP_ID;
import static hanglog.trip.domain.type.PublishedStatusType.PUBLISHED;

import hanglog.auth.domain.Accessor;
import hanglog.city.domain.City;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
package hanglog.expense.domain.repository;

import hanglog.expense.domain.Expense;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface ExpenseRepository extends JpaRepository<Expense, Long> {

@Modifying
@Query("""
UPDATE Expense expense
SET expense.status = 'DELETED'
WHERE expense.id IN :expenseIds
""")
void deleteByIds(@Param("expenseIds") final List<Long> expenseIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public TripExpenseResponse getAllExpenses(final Long tripId) {

final List<CategoryExpense> categoryExpenses = categoryAmounts.entrySet().stream()
.map(entry -> new CategoryExpense(entry.getKey(), entry.getValue(), totalAmount))
.sorted((o1,o2) -> o2.getAmount().compareTo(o1.getAmount()))
.sorted((o1, o2) -> o2.getAmount().compareTo(o1.getAmount()))
.toList();

final List<DayLogExpense> dayLogExpenses = dayLogAmounts.entrySet().stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
package hanglog.image.domain.repository;

import hanglog.image.domain.Image;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface ImageRepository extends JpaRepository<Image, Long> {

@Modifying
@Query("""
UPDATE Image image
SET image.status = 'DELETED'
WHERE image.item.id IN :itemIds
""")
void deleteByItemIds(@Param("itemIds") final List<Long> itemIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package hanglog.listener;

import hanglog.auth.domain.repository.RefreshTokenRepository;
import hanglog.expense.domain.repository.ExpenseRepository;
import hanglog.image.domain.repository.ImageRepository;
import hanglog.member.domain.MemberDeleteEvent;
import hanglog.trip.domain.repository.CustomDayLogRepository;
import hanglog.trip.domain.repository.CustomItemRepository;
import hanglog.trip.domain.repository.DayLogRepository;
import hanglog.trip.domain.repository.ItemRepository;
import hanglog.trip.domain.repository.PlaceRepository;
import hanglog.trip.domain.repository.TripRepository;
import hanglog.trip.dto.ItemElement;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;

@Component
@RequiredArgsConstructor
public class MemberDeleteEventListener {

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 TripRepository tripRepository;
private final RefreshTokenRepository refreshTokenRepository;

@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
@TransactionalEventListener(fallbackExecution = true)
public void delete(final MemberDeleteEvent event) {
final List<Long> dayLogIds = customDayLogRepository.findDayLogIdsByTripIds(event.getTripIds());
final List<ItemElement> itemElements = customItemRepository.findItemIdsByDayLogIds(dayLogIds);

deletePlaces(itemElements);
deleteExpenses(itemElements);
deleteImageAndItems(itemElements);

dayLogRepository.deleteByIds(dayLogIds);
tripRepository.deleteByMemberId(event.getMemberId());
refreshTokenRepository.deleteByMemberId(event.getMemberId());
}

private void deletePlaces(final List<ItemElement> itemElements) {
final List<Long> placeIds = itemElements.stream()
.map(ItemElement::getPlaceId)
.toList();

placeRepository.deleteByIds(placeIds);
}

private void deleteExpenses(final List<ItemElement> itemElements) {
final List<Long> expenseIds = itemElements.stream()
.map(ItemElement::getExpenseId)
.toList();

expenseRepository.deleteByIds(expenseIds);
}

private void deleteImageAndItems(final List<ItemElement> itemElements) {
final List<Long> itemIds = itemElements.stream()
.map(ItemElement::getItemId)
.toList();

imageRepository.deleteByItemIds(itemIds);
itemRepository.deleteByIds(itemIds);
}
}
13 changes: 13 additions & 0 deletions backend/src/main/java/hanglog/member/domain/MemberDeleteEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package hanglog.member.domain;

import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class MemberDeleteEvent {

private final List<Long> tripIds;
private final Long memberId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,21 @@
import hanglog.member.domain.Member;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface MemberRepository extends JpaRepository<Member, Long> {

Optional<Member> findBySocialLoginId(String socialLoginId);

boolean existsByNickname(String nickname);

@Modifying
@Query("""
UPDATE Member member
SET member.status = 'DELETED'
WHERE member.id = :memberId
""")
void deleteByMemberId(@Param("memberId") final Long memberId);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package hanglog.share.dto.response;
package hanglog.member.dto.response;

import static lombok.AccessLevel.PRIVATE;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
package hanglog.share.domain.repository;

import hanglog.share.domain.SharedTrip;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface SharedTripRepository extends JpaRepository<SharedTrip, Long> {

Optional<SharedTrip> findBySharedCode(final String sharedCode);

@Modifying
@Query("""
UPDATE SharedTrip sharedTrip
SET sharedTrip.status = 'DELETED'
WHERE sharedTrip.trip.id IN :tripIds
""")
void deleteByTripIds(@Param("tripIds") final List<Long> tripIds);
}
4 changes: 2 additions & 2 deletions backend/src/main/java/hanglog/trip/domain/Trip.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
import static jakarta.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PROTECTED;

import hanglog.community.domain.type.PublishedStatusType;
import hanglog.global.BaseEntity;
import hanglog.member.domain.Member;
import hanglog.share.domain.SharedTrip;
import hanglog.share.domain.type.SharedStatusType;
import hanglog.trip.domain.type.PublishedStatusType;
import hanglog.trip.domain.type.SharedStatusType;
import hanglog.trip.dto.request.TripUpdateRequest;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@
public interface CustomDayLogRepository {

void saveAll(final List<DayLog> dayLogs);

List<Long> findDayLogIdsByTripIds(final List<Long> tripIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package hanglog.trip.domain.repository;

import hanglog.trip.dto.ItemElement;
import java.util.List;

public interface CustomItemRepository {

List<ItemElement> findItemIdsByDayLogIds(final List<Long> dayLogIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package hanglog.trip.domain.repository;

import java.util.List;

public interface CustomTripRepository {

List<Long> findTripIdsByMemberId(final Long memberId);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
package hanglog.trip.domain.repository;

import hanglog.trip.domain.DayLog;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface DayLogRepository extends JpaRepository<DayLog, Long> {

@Modifying
@Query("""
UPDATE DayLog dayLog
SET dayLog.status = 'DELETED'
WHERE dayLog.id IN :dayLogIds
""")
void deleteByIds(@Param("dayLogIds") final List<Long> dayLogIds);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
package hanglog.trip.domain.repository;

import hanglog.trip.domain.Item;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface ItemRepository extends JpaRepository<Item, Long> {

@Modifying
@Query("""
UPDATE Item item
SET item.status = 'DELETED'
WHERE item.id IN :itemIds
""")
void deleteByIds(@Param("itemIds") final List<Long> itemIds);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
package hanglog.trip.domain.repository;

import hanglog.trip.domain.Place;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface PlaceRepository extends JpaRepository<Place, Long> {

@Modifying
@Query("""
UPDATE Place place
SET place.status = 'DELETED'
WHERE place.id IN :placeIds
""")
void deleteByIds(@Param("placeIds") final List<Long> placeIds);
}
Loading

0 comments on commit 5ab4694

Please sign in to comment.