프로젝트 시작
Trello를 참고하여 프로젝트 협업 도구를 만드는 프로젝트를 진행하게 되었다.
내가 맡은 부분은 카드들을 담고 있는 Deck 관련된 기능이었다.
구현해야 하는 기능
내가 구현해야 하는 기능은 다음과 같았다.
✔ Deck 생성
아무 위치에서 Deck 생성이 가능해야 한다.( 현재는 오른쪽 끝에서 생성하는 로직으로 변경하였다. )
✔ Deck 단일 조회
Deck을 조회했을 때 Deck의 id와 name, 속해 있는 Card를 조회할 수 있어야 한다.
✔ Deck 위치 수정
Deck의 위치를 수정할 수 있어야 한다.
✔ Deck 이름 변경
Deck의 이름을 변경해야 한다.
✔ Deck 삭제(논리적 삭제)
Deck의 Boolean 타입 필드 isDeleted 값을 true로 바꿔준다.
✔ 스케쥴러 기능
스케쥴러가 실행이 되면서 isDeleted 필드가 true이고, 수정시간(삭제된 시간)을 기준으로 일정시간이 지나면 삭제되어야 한다.
위치 수정은 어떻게 할까?
여기서 내가 먼저 생각해야 하는 구현 로직은 Deck의 생성이었다. 어떻게 Deck의 위치를 지정해줄 것이냐. 이걸 먼저 생각해야 Entity의 구조를 생각해서 나머지 로직도 구현할 수 있을 것 같았다. 구글링을 해서 다음 자료를 보게 되었다. 이 자료에서는 4가지 방법을 소개하고 있다.
- 순서가 바뀐 객체를 포함해, 뒤에 있는 모든 객체들의 순서를 업데이트한다.(List에서 삽입처럼!)
- 위치 필드 position을 주고 position간의 간격을 크게 띄운다.
- 위치 필드 position을 소수로 표현한다.
- parent_id를 이용해서 앞에 올 아이템이 무엇인지 저장한다.(Linked List처럼!)
나는 저번 프로젝트에서 대댓글을 parent_id를 줘서 구현한 경험이 있었으므로 4번 방법을 이용해서 구현해보기로 했다.
첫 번째 시도
Deck1과 Deck2가 연결 되어 있을 때, 가운데 Deck3을 넣으려고 하는 로직을 다음과 같이 구현하였다.
public DeckResponseDto createDeck(Long boardId, DeckRequestDto requestDto) {
Board board = findBoard(boardId);
String name = requestDto.getName();
Deck deck = new Deck(name, board);
List<Deck> decks = deckRepository.findAll();
if(decks.size()==0){
deckRepository.save(deck);
return new DeckResponseDto(deck);
}
if (requestDto.getParentId() == 0) {
Deck firstDeck = deckRepository.findByParentNull(); // parent가 null값인 애 찾기
firstDeck.updateParent(deck);
} else {
Deck parentDeck = findDeck(requestDto.getParentId());
// 마지막 거 넣었을 때 null값 들어오는지 확인
Deck afterDeck = deckRepository.findByParent(parentDeck);
deck.updateParent(parentDeck);
if (afterDeck != null) {
afterDeck.updateParent(deck);
}
}
deckRepository.save(deck);
return new DeckResponseDto(deck);
}
그런데 대댓글과 다르게 이번 경우는 일대다의 관계가 아닌 일대일 관계 였으므로 다음과 같이 충돌이 발생했다.
그러면 다음과 같은 예외가 발생한다.(중복 키 오류)
2023-08-08T21:50:23.389+09:00 WARN 17692 --- [nio-8080-exec-3] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1062, SQLState: 23000
2023-08-08T21:50:23.389+09:00 ERROR 17692 --- [nio-8080-exec-3] o.h.engine.jdbc.spi.SqlExceptionHelper : Duplicate entry '1' for key 'deck.UK_5l7v9xfb2sotghor7tiyybbsw'
2023-08-08T21:50:23.391+09:00 ERROR 17692 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.DataIntegrityViolationException: could not execute statement [Duplicate entry '1' for key 'deck.UK_5l7v9xfb2sotghor7tiyybbsw'] [insert into deck (board_id,created_at,is_deleted,modified_at,name,parent_id) values (?,?,?,?,?,?)]; SQL [insert into deck (board_id,created_at,is_deleted,modified_at,name,parent_id) values (?,?,?,?,?,?)]; constraint [deck.UK_5l7v9xfb2sotghor7tiyybbsw]] with root cause
java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '1' for key 'deck.UK_5l7v9xfb2sotghor7tiyybbsw'
두 번째 시도
다음과 같은 로직으로 구현해 보려고 했으나 같은 오류가 나왔다.
세 번째 시도
아무래도 한 메서드에 transaction이 걸려 있는데 쿼리가 순서대로 나가는게 아닌 것 같았다. 저번에 JPA 강의에서 save()메서드는 DB에 영향을 미치는 중요한 메서드라서 먼저 쿼리가 날아간다고 했던 기억이 있어서
Deck3의 부모를 null로 되어 있는 상태에서 save()를 먼저 해 준 다음, Deck2의 부모를 Deck3으로 바꿔주고, Deck3의 부모를 Deck1로 바꿔주었더니 해결했다!!
메서드를 짜다 보니 JPA에 대한 이해가 부족해서 일어난 일 같았다. 프로젝트가 진행되면서 메서드들이 복잡해지는데 그럴 때마다 쿼리에 대한 이해, JPA에 대한 이해에 대한 필요성을 느낀다.
'STUDY > SpringBoot' 카테고리의 다른 글
[TIL] Entity의 선형 구조 / 트리 구조에 따른 구현 방식 비교 - Trouble Shooting (0) | 2023.08.09 |
---|---|
[TIL] 수업 자료 - 카카오 로그인 분석 (1) | 2023.08.02 |
[TIL] SpringBoot dependency 버전 맞추기! (0) | 2023.07.25 |
[TIL] SQLSyntaxError ( 컬럼명 like 문제) (0) | 2023.07.14 |
[TIL] AnnotationException 예외 발생 (mappedBy) (0) | 2023.07.13 |