[Spring+JS] 리스트 update 로직 리팩토링

ziwww·2024년 6월 12일

개발

목록 보기
14/14

Intro

최근에 진행한 프로젝트에서 추천 책 리스트 업데이트 로직에서 개선 피드백이 있었다.

내가 만든 update 방식은 그냥 원래 있던 리스트에 있는 책들을 싸그리 삭제하고 새로 저장하는 방식으로 했었는데 대규모로 운영하게 되면 무조건 터진다고 하심

부트캠프 끝난 후에도 그냥 냅두다가 계속 신경쓰여서 드디어 update 방식을 바꾸기로 했다!

일단 바꾸기전에 update 시간을 측정하니 0.25초가 걸렸다.


해결방식

만약 리스트에 1,2,3의 책이 있을때 3,2,4로 바꾼다면
3번과 2번 책은 순서만 바꾸고 1번은 삭제, 4번은 새로 추가하여야한다.

그럼 순서가 중요하므로 order컬럼을 추가하여 데이터베이스 구조를 수정하였다.

이제 삭제할 책과 새로 업데이트 or 추가할 책을 나눠야한다.
이건 js에서 해줘야할 일이다.

나누는 로직은 아래와 같다

const newBookList=[];
const deleteBookList=[];


function drop(event){
  		...

        //드래그 된 요소의 value 값
        const draggedItemId=listNode.getAttribute('value');

 		...

            newBookList.push(parseInt(draggedItemId, 10));

            const deleteBookIndex=deleteBookList.indexOf(parseInt(draggedItemId, 10));
            if(deleteBookIndex!==-1){
                deleteBookList.splice(deleteBookIndex,1);
		...
}

...

function deleteBook(button) {
    const bookItem = button.closest('.book-item');

  	...

        const bookId=parseInt(bookItem.getAttribute('value'),10);
        deleteBookList.push(bookId);


        //만약 삭제한 id가 새로 추가된 리스트에 있을때 id 삭제
        const newBookIndex=newBookList.indexOf(bookId);
        if(newBookIndex!==-1){
            newBookList.splice(newBookIndex,1);
        }

    }
}

이게 무슨 코드냐면
책이 새로 추가될 때, newBookList에 추가
책이 삭제될 때, deleteBookList에 추가하는 코드이다.

const newBookIndex=newBookList.indexOf(bookId);
        if(newBookIndex!==-1){
            newBookList.splice(newBookIndex,1);
        }

이 코드는 만약 사용자가 1번 책을 추가했다가 다시 삭제한다면 newBookList에 삭제하는 코드이다.
만약 이 로직이 없다면 newBookList= 1번, 2번 / deleteBookList= 1번 이 들어가게되서 생성도하고 삭제도 하는 대참사가 나게된다.
생성도 마찬가지로 삭제한 책을 다시 생성한다면 삭제리스트에서 빼는 로직이 있어야한다.

그럼 여기서 궁금한 점은 기존에 있는 책들은 어떻게 순서를 바꾸냐!인데,
내가 만든 추천리스트 생성 방식은 인스타그램처럼 기존에 있는 사진들의 순서를 바꿀려면 삭제하고 다시 추가하는 방식으로 바꿀 수 있다.

그렇기 때문에 만약 기존에 있는 사진이 1,2,3 일때 1,3,2로 바꿀려면 2,3 삭제 후 3,2 추가를 하면 된다.
즉, deleteBookList에 2,3이 담기고 newBookList에 2,3이 담긴 후 deleteBookList에 2,3이 삭제되는 것이다.
그러면 deleteBookList=[], newBookList=[2,3]의 리스트가 생성되므로 controller에 보내주면된다.

이제 백엔드 로직을 보겠다.

전체 코드

    @Transactional
    @Override
    public void update(Long listId, RecommendListUpdateRequestDto request) {
        UserRecommendList userRecommendList = userRecommendListRepository.findById(listId)
                .orElseThrow(RecommendListNotFoundException::new);
        List<UserRecommendBook> userRecommendBooks = userRecommendBookRepository.findByUserRecommendListIdOrderByOrder(listId)
                .orElseThrow(BookNotFoundException::new);

        List<Long> existingBookIds= userRecommendBooks.stream().map(UserRecommendBook::getBookId).collect(Collectors.toList());

        //list 만든 사용자 id와 응답으로 받은 사용자 id(현재 로그인된 유저)가 다를 때
        if(request.getUserId()==null || userRecommendList.getUserId()!=request.getUserId()){
            throw new UserValidateException();
        }

        int lastOrder = userRecommendBooks.get(userRecommendBooks.size()-1).getOrder();

        //리스트에 새로 추가할거 추가하고 수정할 거 수정하기
        if(request.getNewBookList()!=null){
            for (int i = 0; i < request.getNewBookList().size(); i++) {
                long bookId = request.getNewBookList().get(i);

                //기존 책인데 순서만 바뀌었다면 순서만 업데이트
                if (existingBookIds.contains(bookId)) {
                    int bookIndex = existingBookIds.indexOf(bookId);
                    UserRecommendBook userRecommendBook = userRecommendBooks.get(bookIndex);
                    userRecommendBook.updateOrder(lastOrder + i + 1);
                } else {
                    UserRecommendBook book = UserRecommendBook.builder()
                            .userRecommendListId(listId)
                            .bookId(bookId)
                            .order(lastOrder + i + 1)
                            .build();

                    userRecommendBookRepository.save(book);
                }
            }
        }

        //삭제될 책들 삭제
        if(request.getNewBookList()!=null){
            for (Long deleteBookId : request.getDeleteBookList()) {
                userRecommendBookRepository.deleteByBookIdAndUserRecommendListId(deleteBookId, listId);
            }
        }

        //제목 수정
        userRecommendList.updateTitle(request.getTitle());
    }




엄청 길어서 잘라서 설명해야될 것 같다..

db에서 리스트에 담긴 목록 가져오기

        UserRecommendList userRecommendList = userRecommendListRepository.findById(listId)
                .orElseThrow(RecommendListNotFoundException::new);
        List<UserRecommendBook> userRecommendBooks = userRecommendBookRepository.findByUserRecommendListIdOrderByOrder(listId)
                .orElseThrow(BookNotFoundException::new);

        List<Long> existingBookIds= userRecommendBooks.stream().map(UserRecommendBook::getBookId).collect(Collectors.toList());
  • existingBookIds를 만들어주는 이유는 현재 db에 담긴 책들과 newBookList안에 담긴 책들을 비교하기 위해서 만들어주었다.

order 정하기

int lastOrder = userRecommendBooks.get(userRecommendBooks.size()-1).getOrder();

lastOrder를 정하는 이유는 order가 1부터 시작하게 된다면 id:1 order:1,id:2 order:2,id:3 order:3의 책이 있을때 1,3으로 바꿀 때 그냥 2번을 삭제할 수가 없기 때문이다. 3번 책의 order를 2로 바꿔야됨..

마지막에 저장된 책의 order + 1부터 새로운 책들의 order를 정해줘야한다. 수정 책도 마찬가지. (userRecommendBooks는 order의 오름차순으로 정렬되어있다.)

newBookList 등록하기

if(request.getNewBookList()!=null){
            for (int i = 0; i < request.getNewBookList().size(); i++) {
                long bookId = request.getNewBookList().get(i);

                //기존 책인데 순서만 바뀌었다면 순서만 업데이트
                if (existingBookIds.contains(bookId)) {
                    int bookIndex = existingBookIds.indexOf(bookId);
                    UserRecommendBook userRecommendBook = userRecommendBooks.get(bookIndex);
                    userRecommendBook.updateOrder(lastOrder + i + 1);
                } else {
                    UserRecommendBook book = UserRecommendBook.builder()
                            .userRecommendListId(listId)
                            .bookId(bookId)
                            .order(lastOrder + i + 1)
                            .build();

                    userRecommendBookRepository.save(book);
                }
            }
        }

프론트에서 보내준 newBookList로 책을 등록 or 수정해주는 로직이다.
만약 newBookList에 있는 책이 db에 저장되어있다면 순서만 수정해주면 된다.
만약 저장되어있지 않다면 db에 새로 저장해줘야한다.

아까 만든 existingBookIds는 여기서 쓰인다. db에 저장되어있는지 아닌지 구별하는 역할

deleteBookList 삭제하기

        //삭제될 책들 삭제
        if(request.getNewBookList()!=null){
            for (Long deleteBookId : request.getDeleteBookList()) {
                userRecommendBookRepository.deleteByBookIdAndUserRecommendListId(deleteBookId, listId);
            }
        }

deleteBookList에 등록된 책들을 db에서 삭제한다.

전체 코드 이해를 위해서 예를 들어보겠다.

  • 현재 DB에는 id:1 order:1, id:2 order:2, id:3 order:3, id:4 order:4로 저장되어있음
  • newBookList: 2, 5 / deleteBookList:4인 상태이다.

이런 상태라면 2, 5 중 2는 db에 있으므로 순서만 수정, 5는 새로 생성
4는 db에서 삭제된다.



이런 로직으로 시간 측정을 해보니 0.071초가 나왔다!
2배 넘게 줄어들었으니 엄청난 성과가 난 것 같다~

profile
반갑습니다. 오늘도 즐거운 하루입니다.

0개의 댓글