Skip to content

Commit

Permalink
[REFACTOR] �분산 락이 되지 않는 문제 해결 및 �서비스에 분산 락 추가 (#687)
Browse files Browse the repository at this point in the history
* refactor: 락 어노테이션에 key 값 필드 제거

* refactor: 분산 락 aop에 userId, 메서드명 리플렉션으로 가져온 후 키로 활용 및 모든 aop 중 가장 우선순위 높도록 설정

* refactor: 락에 트랜잭션 설정

* refactor: 폴더 서비스에 락 설정

* refactor: PickDataHandler 주석 제거

* refactor: LockService 제거

* refactor: 부모 폴더의 리스트를 변경하는 경우 분산 락 설정

* refactor: Tag에도 분산 락 설정
  • Loading branch information
sangwonsheep authored Dec 9, 2024
1 parent e1e088a commit 78a431f
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,5 @@
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUserIdDistributedLock {
String key();

long timeout() default 3000;
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public List<FolderResult> getBasicFolderList(Long userId) {
/**
* 생성하려는 폴더가 미분류폴더, 휴지통이 아닌지 검증합니다.
* */
@LoginUserIdDistributedLock
@Transactional
public FolderResult saveFolder(FolderCommand.Create command) {
Folder parentFolder = folderDataHandler.getFolder(command.parentFolderId());
Expand All @@ -89,6 +90,7 @@ public FolderResult updateFolder(FolderCommand.Update command) {
* 현재 폴더들의 부모가 같은지 검증합니다.
* 이동하려는 폴더가 미분류폴더, 휴지통이 아닌지 검증합니다.
* */
@LoginUserIdDistributedLock
@Transactional
public void moveFolder(FolderCommand.Move command) {
Folder destinationFolder = folderDataHandler.getFolder(command.destinationFolderId());
Expand All @@ -109,8 +111,7 @@ public void moveFolder(FolderCommand.Move command) {
}
}

// TODO: 리팩토링 필요
@LoginUserIdDistributedLock(key = "DELETE_FOLDER")
@LoginUserIdDistributedLock
@Transactional
public void deleteFolder(FolderCommand.Delete command) {
// 먼저 공유 부터 해제
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import techpick.api.annotation.LoginUserIdDistributedLock;
import techpick.api.domain.folder.exception.ApiFolderException;
import techpick.api.domain.pick.dto.PickCommand;
import techpick.api.domain.pick.dto.PickMapper;
Expand Down Expand Up @@ -69,6 +70,7 @@ public List<PickResult.FolderPickList> getFolderListChildPickList(PickCommand.Re
.toList();
}

@LoginUserIdDistributedLock
@Transactional
public PickResult.Pick saveNewPick(PickCommand.Create command) {
validateRootAccess(command.parentFolderId());
Expand All @@ -78,6 +80,7 @@ public PickResult.Pick saveNewPick(PickCommand.Create command) {
return pickMapper.toPickResult(pickDataHandler.savePick(command));
}

@LoginUserIdDistributedLock
@Transactional
public PickResult.Pick updatePick(PickCommand.Update command) {
validatePickAccess(command.userId(), command.id());
Expand All @@ -86,6 +89,7 @@ public PickResult.Pick updatePick(PickCommand.Update command) {
return pickMapper.toPickResult(pickDataHandler.updatePick(command));
}

@LoginUserIdDistributedLock
@Transactional
public void movePick(PickCommand.Move command) {
validateRootAccess(command.destinationFolderId());
Expand All @@ -101,6 +105,7 @@ public void movePick(PickCommand.Move command) {
pickDataHandler.movePickToCurrentFolder(command);
}

@LoginUserIdDistributedLock
@Transactional
public void deletePick(PickCommand.Delete command) {
List<Pick> pickList = pickDataHandler.getPickList(command.idList());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.springframework.transaction.annotation.Transactional;

import lombok.RequiredArgsConstructor;
import techpick.api.annotation.LoginUserIdDistributedLock;
import techpick.api.domain.tag.dto.TagCommand;
import techpick.api.domain.tag.dto.TagMapper;
import techpick.api.domain.tag.dto.TagResult;
Expand Down Expand Up @@ -33,6 +34,7 @@ public List<TagResult> getUserTagList(Long userId) {
.map(tagMapper::toResult).toList();
}

@LoginUserIdDistributedLock
@Transactional
public TagResult saveTag(TagCommand.Create command) {
validateDuplicateTagName(command.userId(), command.name());
Expand All @@ -49,6 +51,7 @@ public TagResult updateTag(TagCommand.Update command) {
return tagMapper.toResult(tagDataHandler.updateTag(command));
}

@LoginUserIdDistributedLock
@Transactional
public void moveUserTag(TagCommand.Move command) {
Tag tag = tagDataHandler.getTag(command.id());
Expand All @@ -58,6 +61,7 @@ public void moveUserTag(TagCommand.Move command) {
tagDataHandler.moveTag(command.userId(), command);
}

@LoginUserIdDistributedLock
@Transactional
public void deleteTag(TagCommand.Delete command) {
Tag tag = tagDataHandler.getTag(command.id());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package techpick.api.infrastructure.lock.util;

import java.lang.reflect.Field;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import lombok.RequiredArgsConstructor;
import techpick.api.annotation.LoginUserIdDistributedLock;
import techpick.api.infrastructure.lock.LockProvider;
import techpick.security.annotation.LoginUserId;

@Order(1)
@Aspect
@Component
@RequiredArgsConstructor
Expand All @@ -19,18 +24,60 @@ public class LoginUserIdDistributedLockAspect {

@Around("@annotation(loginUserIdDistributedLock)")
public Object handleDistributedLock(ProceedingJoinPoint joinPoint,
LoginUserIdDistributedLock loginUserIdDistributedLock, @LoginUserId Long userId) throws Throwable {
String key = loginUserIdDistributedLock.key();
LoginUserIdDistributedLock loginUserIdDistributedLock) throws Throwable {
String key = getMethodName(joinPoint);
long timeout = loginUserIdDistributedLock.timeout();
Long userId = getUserIdFromArgs(joinPoint);

if (!lockProvider.acquireLock(key, timeout, userId)) {
throw new LockException("락 획득 실패, key : " + key);
}
lockProvider.acquireLock(key, timeout, userId);

try {
return joinPoint.proceed();
} finally {
lockProvider.releaseLock(key, userId);
}
}

/**
* @author sangwon
* 리플렉션을 통해 메서드 이름을 가져오는 메서드
*/
private String getMethodName(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
return signature.getMethod().getName();
}

/**
* @author sangwon
* 리플렉션을 통해 메서드 파라미터에 있는 userId를 가져온다.
*/
private Long getUserIdFromArgs(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
String[] parameterNames = methodSignature.getParameterNames();

for (int i = 0; i < parameterNames.length; i++) {
Object arg = args[i];

if ("userId".equals(parameterNames[i]) && arg instanceof Long) {
return (Long)arg;
}

if (arg != null) {
try {
// Reflection으로 "userId" 필드를 추출
Field field = arg.getClass().getDeclaredField("userId");
field.setAccessible(true); // private 필드 접근 허용
Object userId = field.get(arg);
if (userId instanceof Long) {
return (Long)userId;
}
} catch (NoSuchFieldException | IllegalAccessException ignored) {
// 해당 파라미터에 "userId"가 없으면 무시
}
}
}

throw new IllegalArgumentException("userId 파라미터를 찾을 수 없습니다.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public Pick getPick(Long pickId) {
@Transactional(readOnly = true)
public Pick getPickUrl(Long userId, String url) {
return pickRepository.findByUserIdAndLinkUrl(userId, url)
.orElseThrow(ApiPickException::PICK_NOT_FOUND);
.orElseThrow(ApiPickException::PICK_NOT_FOUND);
}

@Transactional(readOnly = true)
Expand Down Expand Up @@ -91,30 +91,30 @@ public boolean existsByUserIdAndLink(Long userId, Link link) {
public Pick savePick(PickCommand.Create command) throws ApiPickException {
User user = userRepository.findById(command.userId()).orElseThrow(ApiUserException::USER_NOT_FOUND);
Folder folder = folderRepository.findById(command.parentFolderId())
.orElseThrow(ApiFolderException::FOLDER_NOT_FOUND);
.orElseThrow(ApiFolderException::FOLDER_NOT_FOUND);
Link link = linkRepository.findByUrl(command.linkInfo().url())
.map(existLink -> {
existLink.updateMetadata(command.linkInfo().title(),
command.linkInfo().description(),
command.linkInfo().imageUrl());
return existLink;
})
.orElseGet(() -> linkRepository.save(linkMapper.of(command.linkInfo())));
.map(existLink -> {
existLink.updateMetadata(command.linkInfo().title(),
command.linkInfo().description(),
command.linkInfo().imageUrl());
return existLink;
})
.orElseGet(() -> linkRepository.save(linkMapper.of(command.linkInfo())));

// 픽 존재 여부 검증
pickRepository.findByUserAndLink(user, link)
.ifPresent((__) -> {
throw ApiPickException.PICK_MUST_BE_UNIQUE_FOR_A_URL();
});
.ifPresent((__) -> {
throw ApiPickException.PICK_MUST_BE_UNIQUE_FOR_A_URL();
});

Pick savedPick = pickRepository.save(pickMapper.toEntity(command, user, folder, link));
Folder parentFolder = savedPick.getParentFolder();
attachPickToParentFolder(savedPick, parentFolder);

List<PickTag> pickTagList = tagRepository.findAllById(command.tagIdOrderedList())
.stream()
.map(tag -> PickTag.of(savedPick, tag))
.toList();
.stream()
.map(tag -> PickTag.of(savedPick, tag))
.toList();
pickTagRepository.saveAll(pickTagList);

return savedPick;
Expand All @@ -132,7 +132,7 @@ public Pick updatePick(PickCommand.Update command) {
if (command.parentFolderId() != null) {
Folder parentFolder = pick.getParentFolder();
Folder destinationFolder = folderRepository.findById(command.parentFolderId())
.orElseThrow(ApiFolderException::FOLDER_NOT_FOUND);
.orElseThrow(ApiFolderException::FOLDER_NOT_FOUND);

detachPickFromParentFolder(pick, parentFolder);
attachPickToParentFolder(pick, destinationFolder);
Expand All @@ -148,15 +148,15 @@ public Pick updatePick(PickCommand.Update command) {
public void movePickToCurrentFolder(PickCommand.Move command) {
List<Long> pickIdList = command.idList();
Folder folder = folderRepository.findById(command.destinationFolderId())
.orElseThrow(ApiFolderException::FOLDER_NOT_FOUND);
.orElseThrow(ApiFolderException::FOLDER_NOT_FOUND);
movePickListToDestinationFolder(pickIdList, folder, command.orderIdx());
}

@Transactional
public void movePickToOtherFolder(PickCommand.Move command) {
List<Long> pickIdList = command.idList();
Folder destinationFolder = folderRepository.findById(command.destinationFolderId())
.orElseThrow(ApiFolderException::FOLDER_NOT_FOUND);
.orElseThrow(ApiFolderException::FOLDER_NOT_FOUND);

List<Pick> pickList = pickRepository.findAllById(pickIdList);
pickList.forEach(pick -> {
Expand Down Expand Up @@ -194,15 +194,15 @@ public void deletePickList(PickCommand.Delete command) {
@Transactional
public void attachTagToPickTag(Pick pick, Long tagId) {
Tag tag = tagRepository.findById(tagId)
.orElseThrow(ApiTagException::TAG_NOT_FOUND);
.orElseThrow(ApiTagException::TAG_NOT_FOUND);
PickTag pickTag = PickTag.of(pick, tag);
pickTagRepository.save(pickTag);
}

@Transactional
public void detachTagFromPickTag(Pick pick, Long tagId) {
pickTagRepository.findByPickAndTagId(pick, tagId)
.ifPresent(pickTagRepository::delete);
.ifPresent(pickTagRepository::delete);
}

// 부모 폴더의 픽 리스트에 추가
Expand All @@ -227,25 +227,16 @@ private void movePickListToDestinationFolder(List<Long> pickIdList, Folder folde

private void updateNewTagIdList(Pick pick, List<Long> newTagOrderList) {
// 1. 기존 태그와 새로운 태그를 비교하여 없어진 태그를 PickTag 테이블에서 제거
log.info("---------- (1) {}", pick.getTagIdOrderedList().toString());
pick.getTagIdOrderedList().stream()
.filter(tagId -> {
return !newTagOrderList.contains(tagId);
})
.filter(tagId -> !newTagOrderList.contains(tagId))
.forEach(tagId -> detachTagFromPickTag(pick, tagId));

// 2. 새로운 태그 중 기존에 없는 태그를 PickTag 테이블에 추가
newTagOrderList.stream()
.filter(tagId -> {
log.info("---------- (2) {}", pick.getTagIdOrderedList().toString());
return !pick.getTagIdOrderedList().contains(tagId);
})
.forEach(tagId -> attachTagToPickTag(pick, tagId));

log.info("---------- (3) {}", pick.getTagIdOrderedList().toString());
.filter(tagId -> !pick.getTagIdOrderedList().contains(tagId))
.forEach(tagId -> attachTagToPickTag(pick, tagId));

pick.updateTagOrderList(newTagOrderList);
log.info("---------- (4) {}", pick.getTagIdOrderedList().toString());
}

}

0 comments on commit 78a431f

Please sign in to comment.