Spring

[Spring + Jpa] 테이블 Join & 문제해결

lyndaa 2023. 5. 15. 01:24

구현

CommentDto에 해당하는 UserDto를 가져오고자 G_Comment 테이블과 G_User 테이블을 조인

 

CommentDto Class

package com.gld.model.dto;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;

import com.fasterxml.jackson.annotation.JsonIgnore;

@Entity(name="G_COMMENT")
@IdClass(CommentId.class)
public class CommentDto {
	@Id
	@Column(name="SEQ")
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long seq;
	
	@Id
	@Column(name="ID")
	private Integer id;
	
	@Id
	@Column(name="COMMENT_DATE")
	private LocalDate commentDate;
	
	@Column(name="COMMENT")
	private String comment;
	
	@Column(name="ISDONE")
	private String isDone;
	
	// table join (G_USER 일대다 G_COMMENT)
	@ManyToOne
	@JoinColumn(name = "ID", insertable = false, updatable = false)
	private UserDto userDto;
	
	// getter&setter&constructor
}

UserDto Class

package com.gld.model.dto;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.gld.model.repository.RegisteredMemberRepository;

@Entity
@Table(name = "G_USER")
public class UserDto {

	@Id
	@Column(name = "ID",nullable = false, unique = true)
    private Long id;

	// table join (G_USER 일대다 REGISTERED_MEMBER)
	
	@OneToMany(mappedBy = "userDto", fetch = FetchType.LAZY)
	private List<RegisteredMemberDto> list = new ArrayList<>();
	
	//table join 
	
	@OneToMany(mappedBy = "userDto")
	private List<CommentDto> list2 = new ArrayList<>();

	@Column(name = "USER_ID", nullable = false, unique = true)
	private String userId;

	@Column(name = "USER_PW", nullable = false)
	private String userPw;

	@Column(name = "USER_NAME", nullable = false)
	private String userName;

	@Column(name = "COMPLETED_CHALLENGE", nullable = false)
	private int completedChallenge;

	@Column(name = "ONOFF_NOTY", nullable = false)
	private String onOffNoty;

	@Column(name = "USER_LOGINTYPE", nullable = false)
	private String userLoginType;

	@Column(name = "USER_PHONE", nullable = false)
	private String userPhone;

	@Column(name = "USER_BIRTH", nullable = false)
	@DateTimeFormat(pattern = "yyyy-MM-dd")
	private Date userBirth;

	// getter&setter&constructor
}

Challenge Controller

package com.gld.model.controller;

//import 생략

@Controller
@RequestMapping("/challenge")
public class ChallengeController {

	@Autowired
	private LoginBiz loginBiz;

	@Autowired
	private ChallengeBiz challengeBiz;

	@Autowired
	private RegisteredBiz registeredBiz;
	
	@Autowired
	private CommentBiz commentBiz;

	
	 @PostMapping("/ajaxComment")
	 @ResponseBody
	 public Map<String, Object> commentDate(@RequestBody CommentId commentid) {
		 
		   System.out.println(commentid.getSeq()+" " +commentid.getId()+" "+commentid.getCommentDate());
		   Map<String, Object> res = new HashMap<>();
		   
		   
		  CommentDto comment = commentBiz.selectComment(commentid.getSeq(), commentid.getId(), commentid.getCommentDate());
		  List<CommentDto> list = commentBiz.selectComments(commentid.getSeq(), commentid.getCommentDate());
		  
		  int i=0;
		   List<String> users = new ArrayList<>();
		   for(CommentDto rmDto : list) {
			   users.add(rmDto.getUserDto().getUserId());
			   System.out.println(users.get(i));
			   i++;
		   }
		   
		   Map<String, CommentDto> map = new HashMap<>();
		   
		   if(comment != null) {
			   map.put("comment", comment);
		   }else {
			   map.put("comment", null);
		   }
		   res.put("comment", map);
		   
		   if(list.size() != 0) {
			   res.put("list", list);
		   }else {
			   res.put("list", null);
		   }
		   if(users.size() != 0) {
			   res.put("users", users);
		   }else {
			   res.put("users", null);
		   }
		   		   
		   System.out.println(res);
		   return res;
	   }		
}

문제 상황

Cannot call sendError() after the response has been committed

해당 오류는 JAVA에 Json 타입 변환 과정 중 일어난 오류이다. 이는 테이블과 테이블이 연관 관계에 있으며 데이터를 Front로 보낼때 Json으로 변환 과정중에 무한으로 참조가 순환되어 발생된 오류이다.


문제 해결

JPA 순환 반복이 CommentDto에서 일어난 듯 하다. CommentDto에는 하위 연관 관계가 당연히 추가로 있으며 이 때문에 참조에 참조를 반복하다가 JSON이 오류가 난 듯 싶다. Spring은 Front로 데이터를 보낼 때 Json으로 보내야하는 상황이면 Jackson을 통해 Json 형태로 변환하는데 순환구조일 경우 에러가 떠버린다.

// table join (G_USER 일대다 G_COMMENT)
	@JsonIgnore
	@ManyToOne
	@JoinColumn(name = "ID", insertable = false, updatable = false)
	private UserDto userDto;

@JsonIgnore을 사용하면 해당 필드는 직렬화 또는 역직렬화 대상에서 제외되므로, 해당 필드에 저장된 정보는 JSON 데이터로 변환되지 않는다. 즉, 위의 코드처럼 해당 연관관계 매핑 부분에 @JsonIgnore을 붙여주면 순환 참조 관계를 막을 수 있다.


@JsonIgnore은 Jackson 라이브러리에서 사용되는 어노테이션으로, JSON 직렬화 또는 역직렬화 시에 특정 필드를 제외하고 처리하고자 할 때 사용되므로 해당 어노테이션으로 문제를 해결하였다.