[JPA] 순환참조 문제

hynnch2·2022년 3월 6일
0
post-thumbnail
post-custom-banner

JPA를 사용하던 중 예상치 못한 에러를 발견했습니다..

결과를 보자마자 두통이.. 😢

pretty한 결과 값으로 확인해본 결과 순환참조에 의해서 계속 깊어지다가 오류를 뱉는 것이었습니다.


문제 발생.

  1. User와 Challenge Domain이 두 개 존재.
  2. User와 Challenge는 다대다(N:N) 관계를 가짐.
  3. JPA에서 ManyToMany를 사용하는 대신 추가 Domain 작성 함.
  4. 추가 Domain과 각 도메인에서 양방향 참조를 하다보니 계속 깊어짐.

User -> Challenge -> User 로 양방

(User)

@Entity
public class User extends BaseEntity {
	@Id
	@Column(name="ID")
	private Integer Id;

	@NotNull
	@Column(name="USER_ID")
	private String userId;

    ...

(Challenge)

@Entity(name = "challenge")
public class Challenge extends BaseEntity {

	@Id
	@GeneratedValue
	@Column(name = "CHALLENGE_ID")
	private Long id;

	@Column(name = "NAME")
	private String name;
    
    
    // 문제 발생
    @OneToMany(mappedBy = "challenge")
	private List<UserChallenge> userChallengeList = new ArrayList<>();

	@ManyToOne(optional = false)
	@JoinColumn(name="LEADER_ID")
	private User leader;
    
    ...

(UserChallenge)

@Entity
public class UserChallenge {
	@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "USER_CHALLENGE_ID")
	private Long id;

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "ID")
	private User user;

	// 문제 발생
	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "CHALLENGE_ID")
	private Challenge challenge;
    
    ...

처음에는 UserChallenge의 ManyToOne에 LAZY를 적용하면 되는 줄 알고 위와 같이 작성했었습니다.

그러나 계속 순환 참조가 발생하는 것을 보고, 정확한 이유를 찾아야 헀습니다.


정보 수집.

순환 참조가 발생하는 이유는 Entity에서 json으로 변환하는 과정에서 발생하는 것이라 했습니다.

java의 jackson 라이브러리를 이용하여 Entity의 값을 toString으로 변환하는 과정에서
1. Challenge: User 정보를 String으로 변환하기 위해 정보 필요
2. UserChallenge: User와 Challenge를 toString하기 위해 정보 필요.
...

이런식으로 무한으로 넘어가기 때문에, LAZY는 해당 문제와 관련이 없었습니다.
(LAZY는 실제 값을 원하지 않으면 query를 호출하지 않는 방식, toString에서 불러오기 때문에 무소용)

저와 같은 순환참조에 관해 인터넷에 많은 정보가 있었고 다음과 같은 해결책이 있었습니다.

  1. toString을 Override함.
  2. @JsonIgnore 사용.
  3. @JsonManagedReference, @JsonBackReferenced 사용.
  4. DTO를 통해 반환.

toString을 override하는 것은 당장 사용 가능하지만, field가 많을 때 toString을 다시 짠다는 것은 효율이 좋지 않을 것 같았습니다.
또한, @JsonIgnore은 Json 변환할 때 참조하지 않는 다는 것인데, 어디서 쓰일지 모르는 Domain에게 사용하는 것은 좋은 해결책이 아닌 것 같았습니다.

(JsonIgnore은 json에서 아예 제외, jsonmana~~는 순환참조 방어만 함.)

그럼 @JsonManagedReference, @JsonBackReferenced를 통해 해결해보기로 했습니다.


문제 해결 🙌

다음과 같이 각각 field에 적용을 해주고 다시 한번 결과를 불러보면,

정상적으로 순환참조 문제가 해결 되는 것을 확인 할 수 있습니다!!


추가로 DTO 방식도 작성해보았습니다.

각자 취향에 맞춰서 해결하면 될 것 같습니다.


+ 추가 할 내용이나 부족한 부분이 있다면, 댓글 작성 부탁드립니다! :)

profile
more than yesterday
post-custom-banner

0개의 댓글