구현 기능 중에 컬럼 위치 변경 기능이 있어서 어떻게 구현할까? 고민을 많이 했다.
우선 첫번째로 생각난 것은 Entity 내부에 Position 값을 주고 정렬 그 값을 기준으로 정렬하는 것이다.
Position을 어떻게 주고, 변경할 때는 Position을 어떻게 변경해야할까?
Entity 내부에 List For문으로 하나하나 돌면서 업데이트 쿼리 보내기?
효율이 진짜 안좋아질 거 같았다.
그래서 쿼리를 최적화를 하는데 어떻게 할까? 하다가 연산하는 쿼리를 한 번에 날리게끔 생각했다.
우선 포지션 값은 2의 제곱으로 사용하기로 정했는데,
이유는 1,2,3,4,5 와 2,4,8,16,32 를 했을 때, 하나만 순서 변경하는 데에는 문제가 없지만
여러개를 한 꺼번에 옮기라는 요구가 있으면, 1,2,3,4,5 는 쿼리를 여러번 날려야 되는 반면, 2,4,8,16,32 로 하면 1,2,3,4,5 대비 쿼리를 적게 날릴 수 있다고 한다.
상관 없었다, 내가 구현하는 로직은 삽입 정렬이고, 위의 같은 경우에는 가중치 계산 하는 로직을 내가 잘못 이해했던 것, 그래서 글 내용을 그냥 12345로 봐도 무방하다.
가정
여기서 2를 세번째 위치로 바꾸고 싶다면
이렇게 되는데, 여기서 4와 8을 2로 나눠주면
이렇게 된다.
그러면 이제 2만 바꾸면 된다.
2는 바꾸고 싶은 위치를 Dto로 받은 후에
2의 (바꾸고 싶은 위치) 제곱 을 하면된다 위의 예시로 설명하자면
2의 3제곱 = 8 이니까 딱 맞는 거다
그러면 그 사이의 값들을 어떻게 집을까?
나는 SQL의 Between을 사용했다
위의 예시 같은 경우는
(2(바꾸기 전 현재 위치) + 1(현재 위치 값보다 큰 값들만 선택하기 위함)) 과와 8 (바꿀 위치)까지의 모든 값들을 /2 하면 된다
그러면 4와 8이 선택되어 2와 4가 될 것이다.
반대로
이 예시에서 32를 세번째 자리로 옮기고 싶다면
바꿀위치 부터 현재위치 - 1 까지의 값들을 2로 곱해준 다음, 32를 8로 바꾸기
코드
서비스
// 카탈로그 생성 시 포지션 값 지정
@Override
public CatalogResponseDto createCatalog(Long boardId, Long memberId,
CatalogRequestDto requestDto) {
Board board = findBoard(boardId);
Optional<MemberBoard> memberBoard = memberBoardRepository.findByMemberIdAndBoardId(
memberId, boardId);
if (!memberBoard.isPresent()) {
throw new BisException(YOUR_NOT_INVITED_BOARD);
}
if (memberBoard.get().getRole().equals(MemberBoardRoleEnum.NOT_INVITED_MEMBER)) {
throw new BisException(YOUR_NOT_INVITED_BOARD);
}
Long position = (long) Math.pow(2, board.getCatalogList().size() + 1); // 이 부분
Catalog catalog = Catalog.createCatalog(board, requestDto, position);
Catalog savedCatalog = catalogRepository.save(catalog);
return Catalog.createCatalogResponseDto(savedCatalog);
}
// 카탈로그 위치 변경 메서드
@Override
public void changePosCatalog(Long boardId, Long memberId, Long catalogId,
CatalogPositionRequestDto requestDto) {
existsBoard(boardId);
Optional<MemberBoard> memberBoard = memberBoardRepository.findByMemberIdAndBoardId(
memberId, boardId);
if (!memberBoard.isPresent()) {
throw new BisException(YOUR_NOT_INVITED_BOARD);
}
if (memberBoard.get().getRole().equals(MemberBoardRoleEnum.NOT_INVITED_MEMBER)) {
throw new BisException(YOUR_NOT_INVITED_BOARD);
}
Catalog catalog = findCatalog(catalogId);
Long currentPos = catalog.getPosition();
Long changedPos = (long) Math.pow(2, requestDto.pos()); // 2의 옮기려는 위치 제곱
if (changedPos > currentPos) {
catalogRepository.decreasePositionBeforeUpdate(boardId, currentPos,
changedPos); // 바꿀 위치가 현재 위치보다 크다면 ? between current+1 ~ changed 까지의 모든 것들 /2
} else {
catalogRepository.increasePositionBeforeUpdate(boardId, currentPos,
changedPos); // 바꿀 위치가 현재 위치보다 작다면 ? between current-1 ~ changed
}
catalogRepository.updateCatalogPosition(catalogId, changedPos);
}
Dsl Repository
@Override
public void updateCatalogPosition(Long catalogId, Long changePos) {
queryFactory.update(catalog)
.set(catalog.position, changePos)
.where(catalog.id.eq(catalogId))
.execute();
em.flush();
em.clear();
}
@Override
public void decreasePositionBeforeUpdate(Long boardId, Long currentPos, Long changedPos) {
queryFactory.update(catalog)
.where(catalog.board.id.eq(boardId)
.and(catalog.position.between(currentPos + 1, changedPos)))
.set(catalog.position, catalog.position.divide(2))
.execute();
em.flush();
em.clear();
}
@Override
public void increasePositionBeforeUpdate(Long boardId, Long currentPos, Long changedPos) {
queryFactory.update(catalog)
.where(catalog.board.id.eq(boardId)
.and(catalog.position.between(changedPos, currentPos - 1)))
.set(catalog.position, catalog.position.multiply(2))
.execute();
em.flush();
em.clear();
}
코드로 보면 이렇다!
생각보다 간단한 위치 변경 로직이었다
문제점이 하나 있긴한데, 2의 제곱으로 계속 늘어나니까 언젠가는 범위를 벗어난 값이 들어갈 수도 있다.
그래서 카탈로그에다가 생성 제한을 걸어두면 된다!
아직 저 코드에는 위치 변경하는 거에대한 제한 예외 처리가 안되어 있으니 참고로만 보길 바란다..
'TIL' 카테고리의 다른 글
TIL 2023-12-29 CI/CD 흐름 (0) | 2023.12.30 |
---|---|
TIL 2023-12-28 스프링 단위 테스트랑 통합 환경 테스트 차이가 뭘까? (0) | 2023.12.29 |
TIL 2023-12-26 @ColumnDefault 과 Nullable (0) | 2023.12.26 |
TIL 2023-12-22 의존성에 대한... (0) | 2023.12.22 |
TIL 2023-12-21 @Modifying QueryDsl에도 적용이 될까? (0) | 2023.12.21 |