그 때 당시에는 Spring은 물론 Java에 대한 개념도 매우 약할 때라
지금도 약하지만레퍼런스의 코드를 가져다 사용하기에 급급했다.
당시의 나는 해당 개념에 대한 정확한 이해보다는 기능 구현이 우선이 되었기에, 내가 코드에 적용했던 다른 코드나 기술들에 대해 정확히 왜 사용하고, 어떤 기능인지에 대한 것들은 모두 미뤄뒀었다.
그래서 지금부터 내가 잘 모르고 작성했던 코드들에 대한 공부를 차근히 진행하고자 한다.
나는 아직도 너무 무지하고, 개념을 확실히 알고 넘어 가야 한다는 습관이 완벽히 자리 잡지 않았기에
이 시리즈는 한동안 쭉 작성하지 않을까 싶다.
이 시리즈의 첫번째 글의 주제는 연관 관계 편의 메서드를 왜 사용했을까라는 주제이다.
나는 JPA를 사용하여서 엔티티간 ManyToOne 양방향 관계를 맺을 때 연관 관계 편의 메서드를 사용해야만 한다고만 이해를 하고 있었다.
정확히 왜 연관 관계 편의 메서드를 작성해야 하는지에 대해서는 이해하지 못하고 있었다.
이제부터 왜 연관 관계 편의 메서드를 작성 해야 하는지 알아보자.
설명은 내가 프로젝트 했던 실제 코드들을 바탕으로 해나갈 것이다.
먼저 Post(게시물)과 User(유저)는 N:1, @ManyToOne 관계를 가지고 있다.
Post의 코드이다.
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED) // 생성자를 통해서 값 변경 목적으로 접근하는 메시지들 차단
@EntityListeners(AuditingEntityListener.class)
@Entity
public class Post extends Date {
@Id
@Column(name = "post_id") /
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
private String hashTag;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User{
-==
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id")
private Long id;
private String password;
private String email;
private String name;
private LocalDate birth;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.REMOVE)
private List<Post> postList = new ArrayList<>();
}
user.getPostList().add(user1); //무시
user.getPostList().add(user2); //무시
post1.setUser(user1); //연관관계 설정(연관관계의 주인)
post2.setUser(user2); //연관관계 설정(연관관계의 주인)
답은 아니다. 위와 같이 주인에서만 post1.setUser(user1);
코드와 같이 주인에서만 값을 저장하면 객체 지향적이지 않고, 안전하지 않다. → ORM은 반드시 객체와 데이터베이드 둘 다 모두를 함께 고려해야한다.
양쪽 모두 값을 입력하지 않으면, JPA를 사용하지 않는 순수한 객체 상태에서 심각한 문제가 생길 수 있다.
아래는 JPA를 사용하지 않고, 순순한 객체 상태의 엔티티에 대한 테스트 코드이다.
void test_save(){
User user = new User();
user.setId(1L);
user.setName("케빈");
Post post = new Post();
post.setId(1L);
post.setUser(user);
List<Post> postList = user.getPostList();
System.out.println("postList.size() = " + postList.size());
}
아래는 위 코드를 양방향에 맞게 수정한 코드이다.
void test_save(){
User user = new User();
user.setId(1L);
user.setName("케빈");
Post post = new Post();
post.setId(1L);
post.setUser(user); // post -> user 연관 관계 설정
user.getPostList().add(post); // user -> post 연관 관계 설정
List<Post> postList = user.getPostList();
System.out.println("postList.size() = " + postList.size());
}
객체의 양방향 연관관계는 양쪽 모두 관계를 맺어주자.
post.setUser(user); // post -> user 연관 관계 설정
user.getPostList().add(post); // user -> post 연관 관계 설정
양방향 관계에서 위 코드는 하나인 것 처럼 사용하는 것이 잊지 않고, 안전하다.
이 때 연관 관계 편의 메서드를 사용하는데, 보통 자주 쓰이는 엔티티에 메서드를 정의하는 것이 일반적이다.
보통 Post에서 User를 지정하는 편이 많기에, Post에 연관 관계 편의 메서드를 작성해보자.
public void setUser(User user){
this.user = user;
user.getPostList().add(this);
}
그런데 위 setUser 메서드에는 버그가 존재한다.
만약 2개 이상의 post가 있으면 어떤 경우가 발생할까?
void test_save(){
User user = new User();
user.setId(1L);
user.setName("케빈");
Post post1 = new Post();
post1.setId(1L);
post1.setUser(user); // 양방향 연관 관계
Post post2 = new Post();
post2.setId(2L);
post2.setUser(user); // 양방향 연관 관계
List<Post> postList = user.getPostList();
System.out.println("postList.size() = " + postList.size());
}
먼저 post1.setUser(user);의 코드에서 post1과 user는 양방향 관계를 맺는다.
그다음 post2.setUser(user);의 코드에서 post2와 user가 양방향 관계를 맺을 때 기존 post1과 user의 관계가 남아있다는 문제이다.
그래서 post1과 user의 관계를 먼저 끊어주고, post2와 user의 관계를 추가해야한다.
public void setUser(User user){
// 기존 user와의 관계를 제거
if (this.user != null) {
this.user.getPostList().remove(this);
}
this.user = user;
user.getPostList().add(this);
}
주인의 반대방향은 여전히 읽기만 가능하다.
💡 기존 단방향은 아래 코드만 가능하지만 post.getUser()양방향을 이용하면 아래 코드 또한 가능하다.
user.getPostList();
답은 바로 객체까지 고려해서 주인이 아닌 곳에도 값을 넣어줘야 하기 때문이었다!!