지금까지 스프링 부트를 사용할 때 JPA가 아닌 MyBatis를 사용했었다.
JPA에 관해서는 4-1학기 소프트웨어시스템개발이라는 과목에서 배우긴 했지만 실제로 써보지는 못했다.
그래서 이번 프로젝트에서는 MyBatis가 아닌 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를 조회하게 되고, 이게 계속 반복되는 것이다.
이를 해결하기 위한 방법은 여러 개가 있는데 그중에 내가 사용해본 방법은 두 가지이다.
// 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;
}
하지만 MemberDto 객체를 만들어서 반환한다면 필요한 정보만 가져올 수 있게 된다.
자세한 코드는 아래 GITHUB에 들어가서 확인하면 된다!🙌
https://github.com/MinchaeKwon/SpringPractice/tree/main/practice