본문 바로가기
프로젝트/WEPIK

질문 API를 수정하면서 겪었던 문제

by 미네구스 2024. 6. 5.

현재 진행중인 프로젝트에서 질문 생성 API를 날릴 때 이런식으로 보내준다.

{
        "title": "질문을 뭐라고 적지?",
        "type": "DATE",
        "selectQuestions": [
            {
                "title": "original"
            },
            {
                "title": "original2"
            }
        ],
        "storedName": null
}

 

Question과 SelectQuestion의 관계는 OneToMany로, 부모 -> 자식 관계에 있다. CascadeType.ALL을 통해서 질문이 삭제될 때 같이 삭제되도록 설정했다.

@OneToMany(mappedBy = "question", cascade = CascadeType.ALL)
private List<SelectQuestion> selectQuestions = new ArrayList<>();

 

 

프론트에서 질문 수정 API가 필요하다는 요청이 들어왔고, 나는 곧바로 작업에 들어갔다. 그런데, 생각보다 selectQuestions값을 어떻게 할것인지 많이 헷갈렸다. 그래서 이 부분을 적어보고자 한다.

 

clear 메서드를 통해서 초기화 하기

public void update(String title, AnswerType type, List<SelectQuestion> selectQuestions, File file) {
    this.title = title;
    this.type = type;
    this.selectQuestions.addAll(selectQuestions);
    this.file = file;
}

 

findQuestion.getSelectQuestions().clear();

 

서비스 레이어에서 기존의 Question의 selectedQuestions를 clear해줘서 기존 값들을 삭제하려고 시도했다. 

 

결과는?

수정값:

{
        "selectQuestions": [
            {
                "title": "changed"
            },
            {
                "title": "changed2"
            }
        ],
}

 

 

수정 후

"selectQuestions": [
        {
            "id": 1,
            "title": "original"
        },
        {
            "id": 2,
            "title": "original2"
        },
        {
            "id": 3,
            "title": "changed"
        },
        {
            "id": 4,
            "title": "changed2"
        }
    ]

 

 

기존의 필드에 수정 값이 더해지게 되었다😓

 

왜 그런가 구글링을 통해서 살펴보니

@OneToMany(mappedBy = "question", cascade = CascadeType.ALL, orphanRemoval = true)
private List<SelectQuestion> selectQuestions = new ArrayList<>();

 

orphanRemoval 조건이  누락되어서 값이 추가가 되는 것이였다.

추가하고 나면, 이런식으로 db에서 기존 값들이 삭제되고, 새로운 값들이 save가 된다.

 

나는 항상 CascadeType.ALL을 사용하면 db상에서 고아 객체까지 삭제가 되는줄 알았지만, 실제 DB에선 삭제가 되지 않는다고 한다. 그래서 orphanRemoval = true 조건을 붙여야 DB에 비로소 삭제가 된다.

 

레포단에서 직접 삭제하기

selectQuestionRepository.deleteAll(selectQuestions); // 데이터베이스에서 삭제

 

이런식으로 직접 삭제를 하려고 시도했지만, 앞선 실패 케이스와 같이 수정 값이 추가되었다.

 

for (SelectQuestion selectQuestion : selectQuestions) {
    selectQuestion.setQuestion(null);
}

 

이렇게 연관 관계 메서드를 nuill로 끊어줘야, 비로소 삭제가 되었다.

 

 

 

 

하지만, Entity에 Setter 사용은 절대 금물이다! Setter를 사용하게 된다면 일관성이 유지가 되지 않아서 외부에서 값을 변경할 수 있다.

 

그렇기 때문에, setter 사용은 지양해야 한다.

 

연관 관계 메서드 사용

public void deleteSelectedQuestion(Question question) {
    this.question = null;
}

 

findQuestion.getSelectQuestions().forEach(selectQuestion -> 
	selectQuestion.deleteSelectedQuestion(findQuestion));

 

SelectQuestion 엔티티에 연관관계 메서드를 만들어서 초기화를 해주고, 리스트를 돌면서 연관 관계를 끊어주면 정상적으로 수정값으로 업데이트가 되었다!

{
    "id": 1,
    "title": "수정수정수정?",
    "type": "BAR",
    "imageURL": null,
    "selectQuestions": [
        {
            "id": 3,
            "title": "changed"
        },
        {
            "id": 4,
            "title": "changed2"
        }
    ]
}

 

 

db 결과

 

 

그런데, 맨 처음의 clear를 사용한 방법을 보면 알겠지만 db내에서 기존 필드를 아예 삭제시켜 주었다.

기존 필드가 삭제됨

 

하지만, 연관 관계를 사용한 방법은, 기존에 있던 필드는 그대로 db상에 남아 있지만, question_id가 null로 연관 관계가 끊어진 채로 남아있게 된다.

 

 

 

결론

현재 진행중인 프로젝트에선, 한번 SelectQuestion이 삭제된다면 값이 남아있어야 할 이유가 없기 때문에 연관 관계 메서드를 사용한 방법 보다는 clear 메서드를 통해서 db 상에서 데이터를 완전히 삭제해 주었다.

 

배운 것

  • orphan 설정과 clear()를 통해서 db값 삭제
  • 연관 관계 메서드에서 null로 연관 관계를 끊어버리는 방법