Back-End/Spring JPA

[Spring] 연관관계를 갖는 엔티티를 DTO로 저장할 때 문제 (클라이언트에서 foreign key인 ID만 사용해서 저장하는 법)

koh1018 2023. 1. 8. 18:55
반응형

문제의 발단은 이러했다.

 

 

 

<QuestionPosts>

@Getter
@NoArgsConstructor
@Entity(name = "question_posts")
public class QuestionPosts extends BaseTimeEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "question_post_id")
    private Long questionPostId;

    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private Users user;

    @ManyToOne
    @JoinColumn(name = "dept_id", nullable = false)
    private DeptClass deptClass;

    @Column(length = 100, nullable = false)
    private String title;

    @Column(columnDefinition = "TEXT", nullable = false)
    private String content;

    @OneToMany(mappedBy = "questionPost")   // AnswerPosts 와의 양방향 매핑을 위해 추가
    private List<AnswerPosts> answerPostsList = new ArrayList<>();

    @Builder
    public QuestionPosts(Users user, DeptClass deptClass, String title, String content) {
        this.user = user;
        this.deptClass = deptClass;
        this.title = title;
        this.content = content;
    }

    public void update(QuestionPostsUpdateRequestDto requestDto) {
        this.deptClass = requestDto.getDeptClass();
        this.title = requestDto.getTitle();
        this.content = requestDto.getContent();
    }
}

위의 QuestionPosts 엔티티를 저장할 때 발생하는 문제였는데, QuestionPosts 엔티티는 Users 엔티티, DeptClass 엔티티와 각각 다대일 연관관계를 맺고 있었다.

 

이에 QuestionPosts를 저장할 때 사용할 SaveRequestDto로 아래와 같은 코드를 작성하였다.

 

 

 

<QuestionPostsSaveRequestDto>

@Getter
@NoArgsConstructor
public class QuestionPostsSaveRequestDto {
    private Users user;
    private DeptClass deptClass;
    private String title;
    private String content;

    @Builder
    public QuestionPostsSaveRequestDto(Users user, DeptClass deptClass, String title, String content) {
        this.user = user;
        this.deptClass = deptClass;
        this.title = title;
        this.content = content;
    }

    public QuestionPosts toEntity() {
        return QuestionPosts.builder()
                .user(user)
                .deptClass(deptClass)
                .title(title)
                .content(content)
                .build();
    }
}

 

 

하지만 이 경우 문제가 발생했다...

 

위 swagger의 Request body Example Value 캡처와 같이 SaveRequestDto에서 연관관계에 있는 엔티티들의 객체 값을 요구했기에 해당 객체의 전체 값을 클라이언트 단에서 모두 입력할 것을 요구하는 것이었다.

 

 

이에 필자는 클라이언트 단에서 추가로 발생할 수 있는 불필요한 추가 api 호출, 저장할 때 객체 값을 모두 입력하는 비효율성 등을 이유로 전체 객체 값을 모두 받는 것이 아니라 연관관계에 있는 엔티티의 Id 값만 입력해 저장할 수 있도록 api를 수정해보고자 하였다.

 

 

 


 

 

SaveRequestDto 수정

수정에 앞서 몇 가지 사항을 고민했는데, 엔티티에서는 Id가 아닌 연관관계가 있는 객체를 매핑해야하고, Controller에서는 로직을 처리하면 안되기에 Service 단에서 로직을 처리하기로 했다.

 

클라이언트에서 Id 값들을 받고 그 Id 값들로 객체를 찾은 후, 직접 처리해 넣어줄 예정이다.

먼저 SaveRequestDto를 수정하겠다.

 

 

 

<수정한 QuestionPostsSaveRequestDto>

@Getter
@NoArgsConstructor
public class QuestionPostsSaveRequestDto {
    private Long userId;    // 수정 : Users user -> Long userId
    private Long deptClassId;   // 수정 : DeptClass deptClass -> Long deptClassId
    private String title;
    private String content;

    @Builder
    public QuestionPostsSaveRequestDto(Long userId, Long deptClassId, String title, String content) {
        this.userId = userId;
        this.deptClassId = deptClassId;
        this.title = title;
        this.content = content;
    }

    public QuestionPosts toEntity(Users user, DeptClass deptClass) {    // 수정 : Entity를 만들 때 연관관계가 있는 객체들을 파라매터로 받도록 변경
        return QuestionPosts.builder()
                .user(user)
                .deptClass(deptClass)
                .title(title)
                .content(content)
                .build();
    }
}

위와 같이 dto에서 연관관계에 있는 엔티티들의 Id를 받는 것으로 수정하고, 엔티티로 바꾸는 메서드인 toEntity는 직접 객체를 파라매터로 받는 방식으로 수정하였다.

 

 

 

<QuestionPostsService>

@RequiredArgsConstructor
@Service
public class QuestionPostsService {
    private final QuestionPostsRepository questionPostsRepository;
    
    private final UsersRepository usersRepository;  // 추가
    
    private final DeptClassRepository deptClassRepository;  // 추가

    @Transactional
    public Long save(QuestionPostsSaveRequestDto requestDto) {
        Users user = usersRepository.findById(requestDto.getUserId())
                .orElseThrow(() -> new IllegalArgumentException("해당 유저가 존재하지 않습니다. id=" + requestDto.getUserId()));

        DeptClass deptClass = deptClassRepository.findById(requestDto.getDeptClassId())
                .orElseThrow(() -> new IllegalArgumentException("해당 전공분류가 존재하지 않습니다. id=" + requestDto.getDeptClassId()));
        
        return questionPostsRepository.save(requestDto.toEntity(user, deptClass)).getQuestionPostId();
    }
}

Service의 코드는 위와 같이 작성하였다.

Users와 DeptClass 객체를 직접 찾아 넣어주는 방식으로 구현하였다.

 

 

이제 main 메서드를 실행하고 Swagger를 다시 한번 보겠다.

아까와 다르게 Id만으로 무척 간단하게 Request body를 구성할 수 있게 되었다!

 

 

 


 

 

여러 방법을 찾아보고 고민한 끝에 구현한 방식이지만 조금의 아쉬움이 있다.

왜냐면 Service 단에서 연관관계가 있는 엔티티들의 객체를 찾기위해 추가적인 SELECT문을 날려야 하기 때문이다.

 

추가적으로 공부해 나가면서 더 나은 방법을 배우게 된다면 코드를 리팩토링하며 관련 내용을 추가 포스팅하도록 하겠다.

 

 

(더 좋은 방법을 알고 계시다면 댓글로 남겨주시면 감사하겠습니다 🙇‍♂️)

 

반응형