@Entity 로 클래스와 테이블의 매핑을 진행하였다. 유일하며 불변해야하는 기본키는 @GeneratedValue 로 자동 생성 방식을 사용하였다. 전략으로는 IDENTITY 나 SEQUECE 를 사용 주로 사용한다. 그러면 1:N 과 같은 연관 관계는 클래스에 어떻게 적용할까?
테이블의 연관관계를 클래스에 적용하는 방식에 대해 알아보자.
회원과 팀이 있다고 가정하자. 회원은 하나의 팀에만 소속될 수 있다. 즉 회원과 팀은 1:N 관계이다. 1:N 관계에서는 N 에서 FK 를 갖는다.
테이블 에서는 FK 로 상대 테이블의 PK 를 갖는다. 이 방식 그대로 객체를 모델링 해보자.
Member 는 FK인 teamId 를 필드로 갖게 설계될 것이다.
1번 Member 의 team을 조회하고 싶다고 가정해보자.
Member1 = em.find(Member.class, 1L); // 1번 멤버 조회
Member1_team = em.find(Team.class, Member1.getTeamId); // 1번의 teamId 로 team 조회
Member 객체는 Team 객체가 아닌 FK인 teamId를 필드로 갖는다. 따라서 Member1 을 찾고, 그 teamId로 다시 조회를 team 을 찾아야한다. 쿼리가 두번 필요한 것이다.
그냥 Member 객체가 속한 Team 객체를 필드로 들고 있으면 안될까? 그게 더 객체지향적 설계이다
연관관계를 표현하면 위와 같다. 이때 객체와 테이블의 차이가 존재한다.
즉, Member가 Team 객체를 필드로 갖으면, 객체에서 Member 가 속한 팀은 확인할 수 있지만, Team에 속한 Member들은 확인할 수 없다. 이를 위해서는 Team 객체도 Member 객체들을 가져야한다.
어플리케이션에서는 객체가 객체를 필드로 갖고 있는 것이 자연스러우며, 그것이 객체지향적 설계이다. 우리는 연관관계시 원래대로 객체를 갖도록 설계하자. 그 후 JPA 의 도움으로 어노테이션을 통해 FK 를 지정하면 객체지향 프로그래밍과 DB 설계를 병행할 수 있다.
단방향 매핑이란 연관관계를 갖는 두 객체 중 한쪽만 다른 객체를 필드로 갖고 있는 상황이다. 즉 Member 가 Team 객체를 필드로 갖고, Team 객체는 Member 객체를 필드로 갖지 않는 상태를 단방향 매핑이라고 한다.
단방향 매핑만으로도 연관 관계 매핑과 FK 설정은 완료된다. 하지만 Team 의 필드에 Member 가 없기에 Team 에 속한 Member 들을 조회할 수는 없다.
Member 의 Team 객체에 @ManyToOne 같은 자신 입장에서 연관관계를 어노테이션으로 추가하고, @JoinColumn 에서 매핑할 외래키 이름을 지정해주면 된다. 테이블 자동생성 기능을 사용할 경우, @JoinColumn 에서 지정한 이름으로 외래키가 테이블에 생성된다.
양방향 매핑이란 연관관계를 갖는 양쪽 객체 모두가 서로를 필드로 갖는 상황이다. 단방향 매핑에서는 Team 이 Member 를 필드로 갖지 않는다. 그래서 Team 객체를 통해 해당 Team의 Member들을 조회할 수 없다고 하였다. 그런데 Team의 Member 를 조회할 일이 필요할 경우 Team 의 필드로 Member 객체를 두면 된다.
단방향 매핑으로 이미 FK 생성이 완료되었다. 양방향 매핑쪽에서는 @OneToMany 등 관계를 입력해주고, mappedBy 속성으로 단방향 매핑의 필드명을 입력하면 된다.
양방향 매핑을 통해 Member 도 Team 객체를 갖고, Team 도 MemberList 객체를갖는다.
그런데 Member 의 Team 이 변경되는 상황. 즉 Fk가 변경해야하면 어떻게할까?
Member 내의 Team 객체를 바꿔야할까? 아니면 Team 의 MemberList 객체를 바꿔야할까?
-> 테이블은 FK 존재해도 양방향 접근이 가능하지만, 객체에서는 양방향 접근을 위해 양쪽에 필드로 추가하여서, 이런 딜레마가 생긴 것이다. 즉 외래키와 관련된 양쪽 필드 중 외래키를 관리하는 필드는 누구인가를 결정해야한다. 이 외래키를 결정하는 쪽의 객체가 "연관 관계의 주인" 이다.
양방향 매핑에서 두 객체 중 한쪽의 필드는 FK 를 결정하고, 반대쪽은 FK 결정을 못하며 조회만 가능하도록 해야한다. Team 객체의 Member List 필드는 FK 설정이 완료된 상태에서 조회만을 위해 추가되었다고 하였다. 따라서 여기서는 Member 가 연관관계의 주인이 된다. Member 의 Team 객체를 변경하면 테이블의 FK가 변하지만, Team의 MemberList는 아무리 변경해도 FK에 영향을 주지 않는다. 다만 읽기에 사용할 뿐이다.
양방향 매핑에서는 외래키가 있는 곳을 주인으로 하자. 예를 들어 Team 을 연관관계의 주인으로 하면, Team 의 memberList 를 변경시키면 실제 쿼리는 Member 의 FK를 변경하도록 쿼리가 나가기 때문에 덜 직관적이다.
양방향 매핑에서 FK 를 변경하려면 연관관계 주인의 필드만 변경하면 된다. 즉 Member 객체의 Team 만 바꾸면 된다. 다만 다음과 같은 경우를 보자.
//teamA 생성
Team team = new Team()
team.setName("teamA");
em.persist(team);
//memberA 생성. teamA에 들어가도록함.
Member memberA = Member();
member.setName("kim");
member.setTeam(team);
em.persist(memberA);
//em.flush();
//em.clear();
//teamA 조회후 members 보면 null;
Team teamA = em.find(Team.class, 1L);
List<Member> members = teamA.getMembers(); // member1 기대하나 null.
teamA 를 생성하고, memberA 의 필드에 teamA 를 넣어 FK 를 저장했다. 이제 flush 가 일어나면 DB의 FK에 기록될 것이다.
그 밑에 teamA 를 조회해서 members 를 찍어보았다. 그런데 이 때 영속성 컨텍스트의 1차 캐시에서 teamA 를 가져오기 때문에 members 가 null 인 상태이다. 원하는 결과와 다른 결과가 도출된다.
void setTeam(Team team) {
team.members.add(this); // 주인 아닌놈에도 추가
this.team = team;
}
따라서 양방향 매핑에서 연관관계 주인의 값에 FK 를 set 할때. 주인이 아닌 쪽에도 추가하도록 편의 메서드를 짜서 사용하는 것이 좋다. 실무에서는 setTeam 이 아닌 별도의 이름을 사용하자.
1:N 과 같은 연관관계를 클래스에서는 필드에 다른 객체를 갖도록 하여 구현한다. @JoinColumn 을 통해 FK 의 이름을 정할 수 있다.
한쪽 클래스에서만 객체를 갖는 상태를 단방향매핑, 양쪽 모두 서로의 객체를 갖는 상태를 양방향매핑이라 한다.
양방향 매핑에서 FK 값을 결정하는 클래스가 연관관계의 주인이다. 주인은 FK를 갖는 쪽으로 설정하자. 주인이 아닌쪽은 mappedBy 로 필드명을 입력한다.
연관관계주인의 객체의 필드가 변경되면 FK 값이 변경된다. 다만 영속성 컨텍스트에서 꺼내 사용할 때를 대비해 편의메서드로 양쪽 다 바꿔주자.