테이블에 맞춰서 외래키를 가져와서 조회하는 방식이 아니라, 다이렉트로 객체를 가져올 수 있는 연관관계를 맺어서 객체지향스럽게 설계하는 방법
// 주문한 멤버 찾기, 객체지향스럽지 않은 방식
Order order = em.find(Order.class, 1L);
Long memberId = order.getMemberId();
Member member = em.find(Member.class, memberId);
// 식별자를 가져와서 객체를 가져오는 것이 아니라, 바로 객체를 가져올 수 있어야한다.
Order order = em.find(Order.class, 1L);
Member member = order.getMember();
객체와 테이블 연관관계의 차이를 이해해야 하고, 이해를 바탕으로 객체의 '참조'와 테이블의 '외래키'를 매핑하는 방법을 알아야 한다.
용어 이해
연관관계가 없는 경우)
Meber와 Team은 N:1 관계
@Entity
@Getter @Setter
public class Member {
...
// 외래키
@Column(name = "TEAM_ID")
public Long teamId;
...
}
객체 참조가 아닌, '외래키'를 그대로 사용한다.
// 저장
Team team = new Team();
team.setName("Arsenal");
em.persist(team);
Member member = new Member();
member.setUsername("사카");
member.setTeamId(team.getId()); // 외래키를 직접 다룬다, 객체지향적 x
em.persist(member);
// 조회
Member findMember = em.find(Member.class, member.getId());
// 객체를 바로 꺼낼 수 있는 것이 아니라, 외래키를 사용해서 꺼내야함
Long findTeamId = findMember.getTeamId();
Team findTeam = em.find(Team.class, findTeamId);
tx.commit();
Member에 Team을 넣으려면 외래키를 직접 다뤄야 한다.
이는 객체지향적이 아닌 테이블 중심이다.
(테이블은 외래 키로 조인을 사용해 연관된 테이블을 찾고, 객체는 참조를 사용해 연관된 객체를 찾는다)
테이블 중심으로 모델링하면 협력 관계를 만들 수 없다.
연관관계 사용한 경우)
@Entity
@Getter @Setter
public class Member {
...
// 외래키
// @Column(name = "TEAM_ID")
// public Long teamId;
// ORM 매핑
@ManyToOne // 연관관계 설정
@JoinColumn(name = "TEAM_ID") // 외래키와 매핑
private Team team;
...
}
'외래키'를 그대로 사용하지 않고 '객체 참조'를 사용한다.
연관관계 매핑을 통해 객체 참조와 외래키를 매핑한다.
// 저장
Team team = new Team();
team.setName("Arsenal");
em.persist(team);
Member member = new Member();
member.setUsername("사카");
member.setTeam(team); // JPA가 알아서 PK값을 FK로 등록해준다. 단방향 연관관계 설정
em.persist(member);
// 조회
Member findMember = em.find(Member.class, member.getId());
// 객체를 바로 꺼낼 수 있다.
Team findTeam = findMember.getTeam();
System.out.println("findTeam.getName() = " + findTeam.getName());
tx.commit();
외래키를 직접 다루지 않고, 객체 참조로 값을 넣다 뺐다할 수 있다.
양방향 연관관계)
Member)
@Entity
@Getter @Setter
public class Member {
// ORM 매핑
@ManyToOne // 연관관계 설정
@JoinColumn(name = "TEAM_ID") // 외래키와 매핑
private Team team;
...
}
Team)
@Entity
@Getter @Setter
public class Team {
@OneToMany(mappedBy = "team") // 무엇과 매핑되어 있는지 (Member의 team 필드와 매핑)
private List<Member> members = new ArrayList<>();
...
}
mappedBy
옵션을 달아주었다.양방향으로 매핑하면 서로 참조하기 때문에, 반대 방향으로 탐색이 가능하다.
테이블은 외래키 하나로 두 테이블의 연관관계를 관리한다.
그럼 객체는 어떻게 해야할까?
객체는 서로 참조하는 2개의 단방향 연관관계로 인해 생긴 2개의 필드 중, 어느 값을 바꿔야 테이블의 외래키에 반영이 되는지를 고려해야한다.
(테이블의 외래키를 관리할 곳을 지정해야 한다.)
그래서 객체에서 2개의 단방향 연관관계중에서 하나를 연관관계의 주인으로 지정해야 한다. 주인은 외래 키를 관리(@JoinColumn)하고, 주인이 아닌쪽은 읽기(mappedby)만 가능하다.
주인은 테이블에서 '외래키가 있는 곳'을 주인으로 정한다.
Member와 Team은 다대일 관계로, Member에 외래키가 담겨있기 때문에, Member 객체의 Team객체가 연관관계의 주인이 되는 것이다.
순수 객체 상태를 고려해서 양쪽에 값을 세팅해주어야 한다.
연관관계 편의 메소드를 통해 양쪽에 값을 세팅해주자
양방향 매핑시 무한 루프를 조심하자
ex) JSON 생성 라이브러리, toString(), lombok등
연관관계 편의 메소드)
@Entity
@Getter @Setter
public class Member {
...
// 연관관계 편의 메소드, 양방향 관계일 때 사용
public void changeTeam(Team team) {
// 양쪽에 값을 세팅하기 위함
// 하나의 값을 변경할 때, 나머지 하나의 값도 같이 바꿔줌
this.team = team;
team.getMembers().add(this);
}
}
처음 설계 시, 단방향 매핑으로 설계를 끝내야 한다
처음부터 양방향 매핑을 할 필요는 없다.
양방향 매핑을 하게 되면 신경쓸 게 많아진다. (연관관계 편의 메소드 생성, 연관관계 주인 설정등)
객체와 테이블 매핑은 단방향 매핑으로 끝난 것이며, 양방향 매핑은 반대 방향으로 조회하는 기능이 추가된 것 뿐이다.
단방향 매핑을 잘 해놓고 양방향 매핑은 필요할 때 추가하면 된다.
(JPQL로 역방향으로 탐색할 일이 많다.)
연관관계의 주인은 외래 키의 위치를 기준으로 정해야 한다.
(비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안된다.)
'다'쪽이 연관관계 주인인 경우
'1'쪽이 연관관계 주인인 경우
1:1이기 때문에 외래키를 어디에 넣을지 선택할 수 있다
주 테이블에 넣던지, 대상 테이블에 넣던지
주 테이블 외래키 단방향)
대상 테이블 외래키 단방향)
주 테이블 외래키 양방향)
대상 테이블 외래키 양방향)
관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다.
연결 테이블을 추가해서, 다대일 관계로 풀어내야 한다.
-> 연결 테이블용 엔티티를 추가한다.