스프링부트 DTO 변경 질문
Entity에서 DTO로 변환하는데 며칠을 쓰고도 해결 못해서 질문 올립니다..
- 점프 투 스프링부트가 DTO를 사용하지 않고 Entity만 사용되도록 작성되어서 간단한 DTO를 추가하려고 했습니다.
- DTO클래스와 Entity클래스 내부에 각각 toEntity, toDTO 메소드를 작성하였지만, 무한 순환 참조가 일어나 Stack Overflow가 발생하였습니다.
- ModelMapper, MapStruct를 사용해보아도 answerList의 타입이 List
와 List 여서 그런지 스택오버플로우는 해결되었지만, mapping 된 값에 null이 들어가는 다른 문제가 생겼습니다. - 그래서 다시 수동 매핑을 하려고 다른 방법을 생각해보았고, 아래와 같은 코드를 작성하게 되었는데, 결국은 무한 순환 참조를 피하는 코드는 작성할 수 없었습니다.
- DTO와 Entity간 무한 순환 참조를 피하도록 구현하려면 어떻게 해야할까요?
쓸모 없는 코드는 다 제거하고 dtoToEntity, entityToDTO와 관련된 코드만 첨부하겠습니다.
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class QuestionDTO {
private Integer id;
private String subject;
private String content;
private LocalDateTime createDateTime;
private LocalDateTime modifyDateTime;
private List<AnswerDTO> answerList;
private SiteUserDTO author;
}
@Getter
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class QuestionEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(length = 300)
private String subject;
@Column(columnDefinition = "TEXT")
private String content;
private LocalDateTime createDateTime;
private LocalDateTime modifyDateTime;
@OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE)
private List<AnswerEntity> answerList;
@ManyToOne
private SiteUserEntity author;
}
@Service
@RequiredArgsConstructor
public class QuestionServiceImpl implements QuestionService{
private final QuestionRepository questionRepository;
public QuestionDTO getQuestion(Integer id){
QuestionEntity questionEntity = this.questionRepository.findById(id).orElseThrow(() -> new DataNotFoundException("question not found"));
QuestionDTO questionDTO = this.toDTO(questionEntity);
return questionDTO;
}
public QuestionEntity toEntity(QuestionDTO questionDTO){
return QuestionEntity.builder()
.id(questionDTO.getId())
.subject(questionDTO.getSubject())
.content(questionDTO.getContent())
.createDateTime(questionDTO.getCreateDateTime())
.modifyDateTime(questionDTO.getModifyDateTime())
.answerList(convertAnswerDTOListToAnswerEntityList(questionDTO.getAnswerList()))
.author(questionDTO.getAuthor().toEntity())
.build();
}
private List<AnswerEntity> convertAnswerDTOListToAnswerEntityList(List<AnswerDTO> answerDTOList) {
List<AnswerEntity> answerEntityList = new ArrayList<>();
for (AnswerDTO answerDTO : answerDTOList) {
AnswerEntity answerEntity = AnswerEntity.builder()
.id(answerDTO.getId())
.content(answerDTO.getContent())
.createDateTime(answerDTO.getCreateDateTime())
.modifyDateTime(answerDTO.getModifyDateTime())
.question(this.getQuestionEntity(answerDTO.getId()))
.build();
answerEntityList.add(answerEntity);
}
return answerEntityList;
}
private QuestionEntity getQuestionEntity(Integer id){
QuestionEntity questionEntity = this.questionRepository.findById(id).orElseThrow(() -> new DataNotFoundException("question not found"));
return questionEntity;
}
public QuestionDTO toDTO(QuestionEntity questionEntity){
QuestionDTO questionDTO = QuestionDTO.builder()
.id(questionEntity.getId())
.subject(questionEntity.getSubject())
.content(questionEntity.getContent())
.createDateTime(questionEntity.getCreateDateTime())
.modifyDateTime(questionEntity.getModifyDateTime())
.answerList(convertAnswerEntityListToAnswerDTOList(questionEntity.getAnswerList()))
.author(questionEntity.getAuthor().toDTO())
.build();
System.out.println("questionDTO = " + questionDTO);
return questionDTO;
}
private List<AnswerDTO> convertAnswerEntityListToAnswerDTOList(List<AnswerEntity> answerEntityList) {
List<AnswerDTO> answerDTOList = new ArrayList<>();
for (AnswerEntity answerEntity : answerEntityList) {
AnswerDTO answerDTO = AnswerDTO.builder()
.id(answerEntity.getId())
.content(answerEntity.getContent())
.createDateTime(answerEntity.getCreateDateTime())
.modifyDateTime(answerEntity.getModifyDateTime())
.question(this.getQuestionByAnswer(answerEntity.getId()))
.build();
answerDTOList.add(answerDTO);
}
return answerDTOList;
}
public QuestionDTO getQuestionByAnswer(Integer id){
QuestionEntity questionEntity = this.questionRepository.findQuestionWithAnswersById(id);
QuestionDTO questionDTO = this.toDTO(questionEntity);
return questionDTO;
}
}
public interface QuestionRepository extends JpaRepository<QuestionEntity, Integer> {
@Query("SELECT DISTINCT q FROM QuestionEntity q JOIN FETCH q.answerList a WHERE a.id = :answerId")
QuestionEntity findQuestionWithAnswersById(@Param("answerId") Integer answerId);
}
jumpToSpringBoot 님 97
M 2023년 5월 18일 11:01 오전
순환참조 오류가 발생하는 오류내역을 보여주세요.
-
박응용님,
2023년 5월 18일 8:34 오전
추천
,
대댓글

@박응용님 안녕하세요. 오류 로그는 StackOverflow라고 뜹니다..
getQuestion() -> toDTO() -> convertAnswerEntityListToAnswerDTOList() -> getQuestionByAnswer() -> toDTO() -> 앞의 과정 반복
디버거 찍어보니 이런식으로 진행되어서 무한 순환참조가 이루어집니다.
-
jumpToSpringBoot님,
M 2023년 5월 18일 11:05 오전
추천
,
대댓글
+2
@jumpToSpringBoot님 이미 질문을 알고 있는데, 답변을 통해서 getQuestionByAnswer를 다시 호출할 필요는 없어 보입니다. 이 부분이 순환참조의 주범으로 보이네요.
-
박응용님,
2023년 5월 18일 11:45 오전
추천
,
대댓글

@박응용님 덕분에 아래처럼 구현해서 해결했습니다! 감사합니다!
-
jumpToSpringBoot님,
M 2023년 5월 19일 7:31 오전
추천
,
대댓글
1개의 답변이 있습니다. 1 / 1 Page
QuestionDTO
@Getter
@Setter
@AllArgsConstructor
@Builder
public class QuestionDTO {
private Integer id;
private String subject;
private String content;
private LocalDateTime createDateTime;
private LocalDateTime modifyDateTime;
private List<Answer> answerList;
private SiteUserDTO author;
@Getter
@Setter
public static class Answer {
private Integer id;
private String content;
private LocalDateTime createDateTime;
private LocalDateTime modifyDateTime;
private SiteUserDTO author;
}
public QuestionDTO() {
this.answerList = new ArrayList<>();
}
}
QuestionServiceImpl
@Service
@RequiredArgsConstructor
public class QuestionServiceImpl implements QuestionService {
private final QuestionRepository questionRepository;
public List<QuestionEntity> getList() {
return this.questionRepository.findAll();
}
public QuestionDTO getQuestion(Integer id) {
QuestionEntity questionEntity = this.questionRepository.findById(id).orElseThrow(() -> new DataNotFoundException("question not found"));
QuestionDTO questionDTO = this.toDTO(questionEntity);
return questionDTO;
}
public void create(String subject, String content, SiteUserDTO siteUserDTO) {
QuestionDTO questionDTO = new QuestionDTO();
questionDTO.setSubject(subject);
questionDTO.setContent(content);
questionDTO.setCreateDateTime(LocalDateTime.now());
questionDTO.setModifyDateTime(LocalDateTime.now());
questionDTO.setAuthor(siteUserDTO);
this.questionRepository.save(this.toEntity(questionDTO));
}
public void modify(QuestionDTO questionDTO, String subject, String content) {
questionDTO.setSubject(subject);
questionDTO.setContent(content);
questionDTO.setModifyDateTime(LocalDateTime.now());
this.questionRepository.save(this.toEntity(questionDTO));
}
public void delete(QuestionDTO questionDTO) {
this.questionRepository.delete(this.toEntity(questionDTO));
}
public Page<QuestionEntity> getList(int page) {
List<Sort.Order> sort = new ArrayList<>();
sort.add(Sort.Order.desc("createDateTime"));
Pageable pageable = PageRequest.of(page, 15, Sort.by(sort));
return this.questionRepository.findAll(pageable);
}
public QuestionEntity toEntity(QuestionDTO questionDTO) {
List<AnswerEntity> answerList = new ArrayList<>();
for (QuestionDTO.Answer answer : questionDTO.getAnswerList()) {
AnswerEntity answerEntity = AnswerEntity.builder()
.id(answer.getId())
.content(answer.getContent())
.createDateTime(answer.getCreateDateTime())
.modifyDateTime(answer.getModifyDateTime())
.author(answer.getAuthor().toEntity())
.build();
answerList.add(answerEntity);
}
return QuestionEntity.builder()
.id(questionDTO.getId())
.subject(questionDTO.getSubject())
.content(questionDTO.getContent())
.createDateTime(questionDTO.getCreateDateTime())
.modifyDateTime(questionDTO.getModifyDateTime())
.answerList(answerList)
.author(questionDTO.getAuthor().toEntity())
.build();
}
public QuestionDTO toDTO(QuestionEntity questionEntity) {
List<QuestionDTO.Answer> answerList = new ArrayList<>();
for (AnswerEntity answerEntity : questionEntity.getAnswerList()) {
QuestionDTO.Answer answer = new QuestionDTO.Answer();
answer.setId(answerEntity.getId());
answer.setAuthor(answerEntity.getAuthor().toDTO());
answer.setContent(answerEntity.getContent());
answer.setCreateDateTime(answerEntity.getCreateDateTime());
answer.setModifyDateTime(answerEntity.getModifyDateTime());
answerList.add(answer);
}
return QuestionDTO.builder()
.id(questionEntity.getId())
.subject(questionEntity.getSubject())
.content(questionEntity.getContent())
.createDateTime(questionEntity.getCreateDateTime())
.modifyDateTime(questionEntity.getModifyDateTime())
.answerList(answerList)
.author(questionEntity.getAuthor().toDTO())
.build();
}
}
2023년 5월 19일 7:30 오전