📌 단방향 연관관계
회원과 팀의 관계를 통해서 객체와 테이블의 차이를 보면 다음과 같다.
- 객체 : 단방향 관계이다. 회원은 Team 필드를 통해서 팀을 알 수 있지만, 반대로 팀은 알 수 없다.
- 테이블 : 양방향 관계이다. 외래 키를 통해서 서로 JOIN 할 수 있다.
참조를 통한 연관관계는 항상 단방향이다.
양방향으로 만들고 싶으면 반대쪽에서도 필드를 추가해 참조를 보관해야 한다.
하지만 이건 서로 다른 단방향 관계 2개이다. 테이블과 다르게 외래 키 하나로 양방향으로 JOIN 할 수 없다.
1️⃣ 순수한 객체 연관관계
참조를 통해서 연관관계를 탐색하는 것을 객체 그래프 탐색이라고 한다.
2️⃣ 테이블 연관관계
데이터베이스의 외래 키를 사용해서 연관관계를 탐색하는 것을 조인이라고 한다.
3️⃣ 객체 관계 매핑
@Entity
public class Member {
...
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team;
...
}
@Entity
public class Team {
@Id
@Column(name="TEAM_ID")
private String id;
...
}
다대일 관계를 나타내는 🌀ManyToOne과 외래 키 매핑을 위한 🌀JoinColumn을 사용해서 두 엔티티를 매핑했다.
4️⃣@JoinColumn
외래 키를 매핑할 때 사용한다.
- 🌀JoinColumn을 생략하면 기본 전략을 사용해서 외래 키를 찾는다.
- 기본 전략 : 필드명 + "_" + 참조하는 테이블의 칼럼명
5️⃣ @ManyToOne
다대일 관계를 매핑할 때 사용한다. 중요한 속성으로 fetch와 cascade가 있다.
📌 연관관계 사용
1️⃣ 저장
public void testSave() {
Team team1 = new Team("team1", "팀1");
em.persist(team1);
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1); // 연관관계 설정 member1 -> team1
em.persist(member1);
...
}
회원 엔티티가 팀 엔티티를 참조하고 저장했다. 이때, JPA는 팀의 ID를 외래 키로 사용해서 저장한다.
2️⃣ 조회
연관관계가 있는 엔티티를 조회하는 방법은 크게 2가지이다.
- 객체 그래프 탐색 (객체 연관관계를 사용한 조회)
- 객체지향 쿼리 사용 (JPQL)
🧷 객체 그래프 탐색
Member member = em.find(Member.class, "member1");
Team team = member.getTeam();
System.out.println("팀 이름 = " + team.getName());
다음과 같이 객체를 통해 연관관계를 조회하는 것을 객체 그래프 탐색이라고 한다.
🧷 객체지향 쿼리 사용
private static void queryLogicJoin(EntityManager em) {
String jpql = "select m from member m join m.team t where " + "t.name=:teamName";
List<Member> resultList = em.createQuery(jpql, Member.class)
.setParameter("teamName", "팀1")
.getResultList();
for (Member member : resultList) {
System.out.println("member.username=" + member.getUsername());
}
}
JPQL을 보면 회원이 팀과 관계를 가지고 있는 필드를 통해서 Member와 Team을 조인했다.
3️⃣ 수정
private static void updateRelation(EntityMananger em) {
Team team2 = new Team("team2", "팀2");
em.persist(team2);
Member member = em.find(Member.class, "member1");
member.setTeam(team2);
}
단순히 엔티티 값만 변경해두면 트랜잭션을 커밋할 때 플러시가 일어나면서 변경 감지 기능이 작동한다.
4️⃣ 제거
private static void deleteRealation(EntityManager em) {
Member member1 = em.find(Member.class, "member1");
member1.setTeam(null);
}
5️⃣ 연관된 엔티티 삭제
연관된 엔티티를 삭제하려면 기존에 있던 연관관계를 먼저 제거하고 삭제해야 한다.
외래 키 제약조건으로 데이터베이스에 오류가 발생할 수 있기 때문이다.
member1.setTeam(null);
em.remove(team);
📌 양방향 연관관계
회원과 팀은 다대일 관계이다. 반대로 팀에서 회원은 일대다 관계이다.
일대다 관계에서 여러 건과 연관관계를 맺을 수 있으므로 컬렉션을 사용해야 한다.
테이블 관계는 외래 키 하나로 양방향으로 조회할 수 있다. 데이터베이스에선 추가할 내용이 없다.
🧐 JPA는 List, Collection, Set, Map 등을 지원한다!
1️⃣ 양방향 연관관계 매핑
@Entitiy
public class Memeber {
//...
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team;
//Getter, Setter...
}
@Entity
public class Team {
//...
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<Member>();
//Getter, Setter...
}
mappedBy 속성은 양방향 매핑일 때 사용한다. 반대쪽 매핑 필드의 이름을 값으로 준다.
2️⃣ 일대다 컬렉션 조회
public void biDirection() {
Team team = em.find(Team.class, "team1");
List<Member> members = team.getMembers();
for (Member member : members) {
System.out.println("member.username = " + member.getUsername());
}
}
📌 연관관계의 주인
객체에서 구성된 양방향 연관관계는 참조는 둘인데 외래 키는 하나라서 둘 사이에 차이가 발생한다.
이런 차이 때문에 두 연관관계 중 하나를 정해서 외래 키를 관리하는 것을 연관관계의 주인이라고 한다.
1️⃣ 양방향 매핑의 규칙 : 연관관계의 주인
연관관계의 주인만이 데이터베이스 연관관계와 매핑되고 외래 키를 관리할 수 있다.
주인이 아닌 반대쪽은 읽기만 가능하다.
- 주인은 mappedBy 속성을 사용하지 않는다.
- 주인이 아니면 mappedBy 속성을 사용해서 속성의 값으로 주인을 지정해야 한다.
2️⃣ 연관관계의 주인은 외래 키가 있는 곳
회원 테이블이 외래 키를 가지고 있으므로 Member.team이 주인이 된다.
🧐 @ManyToOne은 항상 연관관계의 주인이라서 mappedBy 속성이 없다!
📌 양방향 연관관계 저장
public void testSave() {
Team team1 = new Team("team1", "팀1");
em.persist(team1);
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1); // 연관관계 설정 member1 -> team1
em.persist(member1);
...
}
연관관계의 주인이 아닌 곳에서 입력된 값은 무시된다.
📌 양방향 연관관계의 주의점
가장 많이 발생하는 실수는 주인이 아닌 곳에만 값을 입력하는 것이다.
public void testSaveNonOwner() {
Member member1 = new Member("member1", "회원1");
em.persist(member1);
Member member2 = new Member("member2", "회원2");
em.persist(member2);
Team team1 = new Team("team1", "팀1");
// 주인이 아닌 곳이기 때문에 무시된다!
team1.getMembers().add(member1);
team1.getMembers().add(member2);
em.persist(team1);
}
1️⃣ 순수한 객체까지 고려한 양방향 연관관계
사실 객체 관점에서 양쪽 방향에 모두 값을 입력하는 것이 가장 안전하다.
JPA를 사용하지 않을 때는 다음과 같이 양방향에서 입력해줘야 team1.getMembers()를 조회할 수 있다.
public void test순수한객체_양방향() {
Team team1 = new Team("team1", "팀1");
Member member1 = new Member("member1", "회원1");
Member member2 = new Member("member2", "회원2");
member1.setTeam(team1);
team1.getMembers().add(member1);
member2.setTeam(team1);
team1.getMembers().add(member2);
}
반면에 JPA를 사용하면 다음과 같다.
public void testORM_양방향() {
Team team1 = new Team("team1", "팀1");
em.persist(team1);
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1);
team1.getMembers().add(member1); // 무시된다.
em.persist(member1);
Member member2 = new Member("member2", "회원2");
member2.setTeam(team1);
team1.getMembers().add(member2); // 무시된다.
em.persist(member2);
}
'개인 공부 > JPA (자바 ORM 표준 JPA 프로그래밍)' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 고급 매핑 (7장) (0) | 2022.12.12 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 다양한 연관관계 매핑 (6장) (0) | 2022.12.02 |
자바 ORM 표준 JPA 프로그래밍 : 엔티티 매핑 (4장) (1) | 2022.10.06 |
자바 ORM 표준 JPA 프로그래밍 : 영속성 관리 (3장) (0) | 2022.09.26 |
자바 ORM 표준 JPA 프로그래밍 : JPA 시작 (2장) (0) | 2022.09.15 |
댓글