[Spring Boot] JPA - 순환 참조 해결하기

민채·2021년 8월 11일
3

SpringBoot

목록 보기
2/2

지금까지 스프링 부트를 사용할 때 JPA가 아닌 MyBatis를 사용했었다.
JPA에 관해서는 4-1학기 소프트웨어시스템개발이라는 과목에서 배우긴 했지만 실제로 써보지는 못했다.
그래서 이번 프로젝트에서는 MyBatis가 아닌 JPA를 사용했는데, 처음 사용하는 것이다 보니 조금 어려움이 있었다. 그중에 하나가 JPA 순환 참조이다.

JPA 순환 참조

JPA 순환 참조는 1:N , N:1, 양방향 관계에서 일어날 수 있다.

순환 참조를 설명하기 위해 Member(회원), Post(게시글) Entity를 만들었다.

// Member Entiry
public class Member implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(updatable = false, nullable = false)
    private Long id;

    @Column(name="email")
    private String email;

    @Column(name="password")
    private String password;
	
    @Column(name = "auth")
    private String auth;
	
    @Column(name = "type")
    private String type;
	
    @OneToMany(cascade=CascadeType.ALL, mappedBy="member", orphanRemoval=true)
    private List<Post> posts;
}
// Post Entity
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(updatable = false, nullable = false)
    private Long id;
	
    @Column(name="title")
    private String title;
	
    @Column(name="content")
    private String content;
	
    @Column(name="createAt")
    @CreationTimestamp
    private LocalDateTime createAt;
	
    @Column(name="updateAt")
    @UpdateTimestamp
    private LocalDateTime updateAt;
	
    @ManyToOne
    @JoinColumn(name="member_id")
    private Member member;
}

이때 회원을 통해서 게시글을 조회할 수도 있고, 게시글을 통해서도 회원을 조회할 수 있기 때문에 양방향 관계로 정의를 했다.

이렇게 관계 설정을 하고 Controller에서 아래 코드와 같이 특정 회원이 작성한 게시글 목록을 가져올 때 Entity를 그대로 반환하는 경우

@GetMapping("/mypost")
public ResponseEntity<?> getMyPost(@AuthenticationPrincipal Member member) {
    List<Post> postList = postService.getMyPost(member);
		
    return ResponseEntity.ok().body(new CommonListResponse<List<Post>>(postList.size(), postList));
}

아래 사진과 같이 순환 참조가 발생할 수 있다.

Post Entity를 그대로 반환하게 되면 Post가 참조하고 있는 Member Entity도 조회하게 되고, 이게 다시 Post를 참조해서 다시 Post Entity를 조회하게 되고, 이게 계속 반복되는 것이다.

이를 해결하기 위한 방법은 여러 개가 있는데 그중에 내가 사용해본 방법은 두 가지이다.

  1. @JsonManagedReference와 @JsonBackReference를 사용하는 것
  • @JsonManagedReference를 Member(부모 클래스, 연관 관계 주인의 반대편), @JsonBackReference를 Post(자식 클래스, 연관 관계의 주인 - 외래 키가 있는 곳)에 붙이면 된다.
// Member Entiry
public class Member implements UserDetails {
    ...

    @JsonManagedReference
    @OneToMany(cascade=CascadeType.ALL, mappedBy="member", orphanRemoval=true)
    private List<Post> posts;
}
// Post Entity
public class Post {
    ...

    @JsonBackReference
    @ManyToOne
    @JoinColumn(name="member_id")
    private Member member;
}
  • @JsonManagedReference와 @JsonBackReference를 사용한 후의 결과
  1. DTO 객체를 만들어서 반환하는 것 -> 추천!
  • 나는 처음에 첫 번째 방법을 사용하다가 DTO 객체를 사용하는 방법으로 갈아탔는데, 이 이유는 Entity를 그대로 반환하게 되니까 필요 없는 정보까지 다 나오게 되는 경우가 있었다.
  • 예를 들어 Member Entity를 그대로 반환하는 경우 아래와 같이 Member의 모든 정보가 나오게 된다. 만약 Post 개수가 더 많아지거나 Comment Entity도 추가하는 경우에는 굳이 가져오지 않아도 되는 정보를 너무 많이 가져오게 된다.

하지만 MemberDto 객체를 만들어서 반환한다면 필요한 정보만 가져올 수 있게 된다.

자세한 코드는 아래 GITHUB에 들어가서 확인하면 된다!🙌

GITHUB

https://github.com/MinchaeKwon/SpringPractice/tree/main/practice

profile
코딩계의 떠오르는 태양☀️

0개의 댓글