해결 방안 이 글 하나로 끝 ㅠ orElse()에 대한 무지함에서 비롯된 5시간의 싸움이었다.

Untitled

Untitled

위 JSON 데이터를 보면 알 수 있다 시피, Data를 알맞게 보냈음에도 불구하고 수정 로직을 수행한 후에 의도치 않은 null이 담긴 객체가 하나씩 더 추가되는 문제가 발생했다.

문제 발생 시, 서비스 로직을 아래와 같이 구성했었는데, dto에 담긴 ID로 조회를 하고, dto에 ID가 없는 경우에는, Fields 객체를 새로 만들어서 post만 할당해준 후에 수정 로직을 수행하도록 했다. ← 이 부분이 문제의 발단이 됨.

public PostResponse updatePost(Long id, UpdatePostRequest postDto) {
        Post post = postRepository.findById(id).orElseThrow(() -> new RuntimeException("없는 아이디"));

        post.updatePost(postDto);

        List<UpdateFieldsRequest> fieldsDtoList = postDto.getFieldsList();
        for (UpdateFieldsRequest fieldDto : fieldsDtoList) {
            log.info("filedDto : {}", fieldDto.getFieldsName());
            Fields fields = fieldsRepository.findById(fieldDto.getId() != null ? fieldDto.getId() : -1L)  // id가 없는 경우, -1L로 찾으므로써 null 유도.
                    .orElse(Fields.builder()  // post만 할당된 Fields를 넘겨준다.
                            .post(post)  // 왠지 이 로직에서 null이 담긴 객체가 들어가면서 같이 영속화 ㄱㄱ 한듯?
                            .build());

            fields.updateFields(fieldDto);

            log.info("fieldsName : {}", fields.getFieldsName());

            List<UpdateTechStackRequest> stacks = fieldDto.getStacks();
            for (UpdateTechStackRequest stackDto : stacks) {
                log.info("stackDto : {}", stackDto.getStackName());
                TechStack techStack = techStackRepository.findById(stackDto.getId() != null ? stackDto.getId() : -1L)
                        .orElse(TechStack.builder()
                                .fields(fields)
                                .build());

                techStack.updateTechStack(stackDto);
                log.info("techStackName : {}", techStack.getStackName());
            }
        }

        postRepository.save(post);  // 새로운 필드나 스택이 추가될 수 있기 때문에 저장

        return new PostResponse(post);
    }

해당 로직에는 겉 보기에는 문제가 없었다. 왜냐면 빈 객체 생성 후, 수정 로직을 같이 수행해주면 될 것이라고 생각했기 때문이다.

하지만, Entity 내부에 구성했던 로직이 문제를 일으켰던 것이다. 둘은 서로 다대일 매핑으로 관계가 맺어져 있다. (Fields : Post = N : 1)

Fields.builder()  // post만 할당된 Fields를 넘겨준다.
        .post(post)  // 왠지 이 로직에서 null이 담긴 객체가 들어가면서 같이 영속화 ㄱㄱ 한듯?
        .build()

해당 로직의 수행 경로를 쭉 따라가보면 알 수 있다.

값이 Post Entity 외에는 아무것도 할당되지 않은 Fields Entity이다. Field Entity에 Post를 할당해줄 경우 아래와 같이 편의 로직이 함께 수행된다. (글만 저장되면 모두 저장되도록 구성함. 영속화 전이 이용. 수정 및 삭제도 함께 되어야 하기 때문에 ALL 사용)

public Fields(String fieldsName, Integer recruitCount, Integer totalAbility, Post post) {
    this.fieldsName = fieldsName; // null
    this.recruitCount = recruitCount; // null
    this.totalAbility = totalAbility; // null
    this.post = post;

    //이거 되나
    this.post.addFields(this);  // 이 때 post에 한번 넘어감 (모두 null인게)
}

편의 로직은 위와 같이 구성했다. 모집 분야는 글이 생성될 때 생성되고, 삭제될 때 삭제되며, 모집 분야가 다른 글로 설정될 일은 없기 때문에, 생성과 동시에 post를 할당 해주면, 자동으로 Post Entity 내부의 List<Fields> 에 해당 Fields가 추가되도록 구성했다.

바로 이 부분이 문제의 핵심이다. null이 담겨있던 Fields가 Post의 List에 추가되어 버린 것이다.

그저 편의성 때문에 이렇게 구성했던 게 문제가 될 줄은 몰랐지만, 이제라도 조심해야되는 것을 알게되어 다행이다.

처음에는, 어차피 Fields를 생성하더라도 수정 로직에서 값을 입혀줄 거라서 빈 객체를 생성해서 줘버렸는데 ,이게 문제가 되어버렸다. 일단은 이 하나로 해결될 지 모르지만, 아래와 같이 생성과 동시에 값을 입혀줘버리기로 했다.

Fields fields = fieldsRepository.findById(fieldDto.getId() != null ? fieldDto.getId() : -1L)  // id가 없는 경우, -1L로 찾으므로써 null 유도.
        .orElse(Fields.builder()  // post만 할당된 Fields를 넘겨준다.
                .post(post)  // 왠지 이 로직에서 null이 담긴 객체가 들어가면서 같이 영속화 ㄱㄱ 한듯?
                .fieldsName(fieldDto.getFieldsName())
                .totalAbility(fieldDto.getTotalAbility())
                .recruitCount(fieldDto.getRecruitCount())
                .build());

역시 이 하나로 해결되진 않았다.

음, 정확히는 null은 해결됐지만, null 대신 중복 저장이 발생되어버린다. 이는 아무래도 orElse()절에서 생성을 하면서 post에 할당이 되지만, 동시에 Fields에는 따로 TechStack이 할당되지 않은 채로 post에 먼저 저장이 되어버리는 문제가 발생했다.