BackEnd/JPA

[JPA] 양방향 연관관계 & 연관관계의 주인 (2)

JJangGu 2022. 5. 14. 14:47

이번에는 양방향 연관관계에서의 저장과 편의메소드를 다뤄보겠습니다. 

 

왜 이 주제를 따로 다뤄야 하는지는 저번에 이야기 했었던 연관관계의 주인과 연관이 있습니다. 연관관계의 주인이 아닌 쪽은 읽기만 할 수 있다고 적었었는데요, 그 말은 연관관계의 주인 쪽에서 저장을 할 수 있다는 말과 같습니다. 

 

public void testSaveNonOwner() {
    Member memebr1 = new Member("member1", "회원 1");
    em.persist(member1);
    
    Member memebr2 = new Member("member2", "회원 2");
    em.persist(member2);
    
    Team team1 = new Team("team1", "팀 1");
    team1.getMembers().add(member1);
    team1.getMembers().add(member2);
    
    em.persist();
}

 

위의 코드의 예상결과는 어떤가요? 위에서 이미 주인쪽에서만 저장이 가능하다고 언급을 했습니다만, 위의 코드를 실행한 후에 DB를 조회해보면 Member 테이블에 team_id 는 모두 null 입니다. 

 

연관관계의 주인이 아닌 Team의 members 에만 값을 저장했기 때문입니다. 

 

하지만 생각을 해보면 persist()를 하고, 트랜잭션이 커밋되는 순간. 즉, 플러시가 일어나기 전까지 우리는 저 Team 의 members 에 값이 저장되어있기를 희망할 수 있습니다. 단순히 DB 의 상태만 고려하는 것이 아니라 객체간의 관계까지 고려해야된다는 것이죠. 

 

public void test() {
    Team team1 = new Team("team1", "팀 1");
    Member member1 = new Member("member1", "회원 1");
    Member member2 = new Member("member2", "회원 2");
    
    
    member1.setTeam(team1);
    member2.setTeam(team1);
    
    List<Member> members = team1.getMembers();
    System.out.println(members.size());
}

 

위와 같은 코드가 있다고 가정해보면, 아까의 예와는 다르게 이번에는 연관관계의 주인쪽에서 값을 설정했습니다. 그러면 DB에는 원하는대로 Member 테이블의 team_id 에 값이 들어가 있을 겁니다. 하지만 System.out 으로 찍은 members.size 는 0이 나옵니다. 저 값은 DB에서 저장한 것이 아닌 객체간의 관계에서만 조회된 값인거죠. 

 

이러한 상황을 해결해주려면 위의 두가지를 다 해주면 됩니다. 주인이 아닌쪽에서도 객체에 값을 저장해주고, 주인쪽도 값을 설정하는 것이죠.

 

이렇게 양쪽을 모두 신경쓰다보면, setTeam() 과 members.add(team) 두가지를 모두 만드느라 실수를 할 확률이 높아집니다. 따라서 이 두 코드를 마치 하나처럼 쓰는 것이 필요한데, 이것이 연관관계 편의 메소드 입니다.

 

public class Member {

    private Team team;
    
    public void setTeam(Team team) {
    	if(this.team != null) {
            this.team.getMembers().remove(this);
        }
    	this.team = team;
        team.getMembers().add(this);
    }
    ...
}

 

이젠 어떤가요? Member에 Team 을 추가할때 team 쪽에도 값을 세팅해주는 겁니다. 이렇게 하나의 코드처럼 동작하게 만드는 것을 연관관계 편의 메소드라고 합니다. 

 

하지만 꼭 이렇게 작성할 필요는 없다고 생각합니다. 결국 목적은 사용할 객체와 DB 모두 안전하게 값이 들어가있다는 것을 보장해야 된다는 것인데, 저의 경우에는 Team이 갖는 List<Member> members 필드를 일급컬렉션으로 만드는 것을 선호합니다. 

 

public class Team {
    @OneToMany(mappedBy="team")
    List<Member> members = new ArrayList<>();
    
    ...
}

 

원래 이렇게 생겼던 모양을 아래와 같이 바꾸는 것이죠.

 

@Embeddable
public class Members {
    @OneToMany(mappedBy="team")
    private List<Member> members = new ArrayList<>();
    
    public void add(Member member) {
    	// 여러 제약조건 추가
    	this.members.add(member);
    }
    
    public int size() {
    	return this.members.size();
    }
}

 

이렇게 일급컬렉션으로 분리하는 것 입니다. 그러면 일단 Members 객체에 여러역할을 위임할 수 있습니다. 아까 System.out으로 size()를 찍은것도 getter 를 통해서 필드에 접근한 후 출력할 수 있었는데, 지금은 Members 객체에 물어보면 되는 것이죠.

 

이를 객체에 메시지를 보낸다고 표현하는데, 이를 적절하게 활용하면 OOP를 구현하는데 효과적입니다. 

 

JPA 를 복습하면서 클린코드나 리팩토링의 영역까지는 다룰 생각이 없었지만, 생각이 나서 적어보았습니다. 😀

 

그러면 여기까지 연관관계의 기초(?)를 마무리하고 이후로는 고급매핑을 보겠습니다. 다대일, 일대일 등의 관계는 크게 다루지 않으려고 합니다. 다양한 연관관계의 매핑일 뿐이고 여기까지의 개념이 충실하게 있다면 활용가능한 영역이라고 생각을 합니다.