연관관계
단방향 연관관계
객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.
- 테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾는다.
- 객체는 참조를 사용해서 연관된 객체를 찾는다.
- 테이블과 객체 사이에는 이런 큰 간격이 존재 객체와 테이블의 차이
테이블 연관관계에선 foriegn key를 사용하여 Member 와 Team 테이블을 연결해서 서로의 컬럼을 확인 할 수 있지만 객체는 할 수 없다.
객체는 그림과 같인 단방향 연관관계를 가진다.
@Entity
public class Member{
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@Column(name = "TEAM_ID")
private Long teamId;
...
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
...
}
이후 팀객체를 생성후 멤버객체로 팀객체를 참조하게된다. 이때 멤버객체로 팀객체를 참조할 수 있지만, 팀객체로 멤버객체를 참조하는 것은 불가능하다.
만약, 팀 객체로 멤버 객체를 참조하고 싶을때는 양방향 설정을해 참조 할 수 있다.
Team team= new Team();
team.setName("test");
Member member=new Member();
member.setName("member test");
member.setTeam(team);
em.persiste(member);
Member findMember = em.find(Member.class, member.getId());
Team team=findMember.getTeam(); // 멤버 객체로 참조
단방향 연관관계
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String username;
@Column(name = "TEAM_ID")
private Long teamId;
}
다음과 같이 객체적이지 않은 엔티티 매핑관계를
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
다음과 같이 team객체를 직접적으로 이용하여 객체지향적으로 만들 수 있다.
양방향 연관관계와 연관관계의 주인
@ManyToOne
@OneToMany(mappedBy = "team")
@OneToMany와 @ManyToOne은 단방향 연결설정 @JoinColumn으로 외래키 설정연결 할 수 있다.
mappedBy 설정은 나: One 연결된상대: Many 일때, Many에서 정의된 나
즉, Member와 Team을 예시로 들때 Member안에 정의된 “Team team” 과 매핑된다는 뜻
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
@Entity
public class Team{
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team") //mappedBy 속성을 이용하여 양방향 설정을 맺을 수 있다.
List<Member> members = new ArrayList<Member>();
}
mappedBy
객체와 테이블이 관계를 맺는 차이
객체는 ‘단방향’으로 서로를 참조가능 이는 자칫하면 양방향으로 보일 수 있으나 명백히 ‘단방향’이 2번 이루어진 관계이다.
테이블은 ‘조인’으로 양방향 참조가능
연관관계의 주인
Q. 테이블은 조인으로 관리할수있지만, 테이블은 누가 관리하지?
양방향 매핑 규칙
- 객체의 두 관계중 하나를 연관관계의 주인으로 지정
- 연관관계의 주인만이 외래 키를 관리(등록,수정)
- 주인이 아닌 쪽은 읽기만 가능
- 주인은 mappedBy 속성 사용 X
- 주인이 아니면 mappedBy로 주인 속성 지정’
연관관계 주인 설정해야하는 이유 ※ 매핑할때 외래키가 있는 곳을 주인으로 정해라
비즈니스적 중요성과 상관이없음
양방향 매핑
- 단방향 매핑만으로도 이미 연관관계 매핑은 완료
- 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
- JPQL에서 역방향으로 탐색할 일이 많음
- 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨
양방향 매핑시 주의
다대일 관계에서 연관 관계를 DB에 저장하지 않고 값을 세팅하게되면 다쪽에서만 값이 조회되는 상황이 발생하게 된다.
아래 코드는 teamA 객체를 생성하고 member객체를 생성한후 getMember().add()로 멤버를 추가시켜준다. 이후 아래 findTeam.getMembers()로 연관관계로 설정한 멤버객체를 꺼내오는 코드인데, 이때 null값이 반환되게 된다.
이유는 member와 team이 엔티티 식별자로서가 아닌 객체자체를 참조하기 때문에 영속성 컨텍스트에서와 다른 불일치 문제가 발생 한다. 결국 맨 아래코드에서 멤버를 조회하기 될 경우에 조회되지않는다.
이를 해결하기 위해선 team.getMembers().add(member) 아래 코드에서 em.flush(), em.clear()로 DB로 저장을 한번 한 뒤에 객체를 꺼내와야한다.
때문에 순수 객체 상태를 고려해서 양쪽에 값을 설정하지만 더 편한 방법으론 연관 관계 편의 메소드 생성을 작성한다.
Team team=new Team();
team.setName("TeamA");
em.persist(team);
Member member=new Member();
member.setUsername("member");
member.setTeam(team);
em.persist(member);
team.getMembers().add(member); // 1차 캐시에만 존재하는 상태
// em.flush(); // DB로 저장
// em.clear();
// 만약 em.flush() em.clear() 가 주석처리된다면 팀 객체가 1차캐시에서 멤버 객체를 조회하는 상황으로 null값이 저장되어있을 수 있음
Team findTeam=em.find(Team.class,team.getId());
List<Member>members=findTeam.getMembers(); // null 값
@Entity
public class Member{
...
public void setTeam(Team team){
this.team=team;
team.getMembers().add(this);
}
...
}
출처: 인프런 -JPA 기본편 [김영한]