5장 - 연관관계 매핑 기초
- 방향 : 단방향과 양방향
- 다중성 : 일대일, 일대다, 다대일, 다대다
- 연관관계의 주인 : 양방향 연관관계 시 연관관계의 주인을 정해야 함
1. 단방향 연관관계
객체 연관관계와 테이블 연관관계의 차이
- 객체는 참조(주소)로 연관관계를 맺는다
- 객체의 연관관계는 단방향이다.
- 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
- 테이블의 외래 키로 연관관계를 맺는다.
- 테이블의 연관관계는 양방향이다.
1.1 순수한 객체 연관관계
- 객체는 참조를 사용해서 연관관계 탐색 - 객체 그래프 탐색
public class Member {
private String id;
private String username;
private Team team; // 팀의 참조를 보관
public void setTeam(Team team) {
this.team = team;
}
// Getter, Setter
}
public class Team {
private String id;
private String name;
// Getter, Setter
}
public static void main(String[]args){
Member member1 = new Member("member1","회원1");
Member member2 = new Member("member2","회원2");
Team team1 = new Team("team1","팀1");
member1.setTeam(team1);
member2.setTeam(team1);
Team findTeam = member1.getTeam(); //팀1 조회
}
1.2 테이블 연관관계
- 데이터베이스는 외래 키를 사용해서 연관관계 탐색 - 조인
SELECT T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.ID
WHERE M.MEMBER_ID = 'member1' // member1이 속한 팀 조회
1.2 객체 관계 매핑
회원 객체의 Member.team 필드와 회원 테이블의 MEMBER.TEAM_ID 외래 키 컬럼를 매핑
@Entity
public class Member {
@Id
@Column(name= "MEMBER_ID")
private String id;
private String username;
// 연관 관계 매핑
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
// Getter, Setter
}
@Entity
public class Team {
@Id
@Column(name = "TEAM_ID")
private String id;
private String name;
// Getter, Setter
}
@ManyToOne
- 다대일(N:1) 관계라는 매핑 정보 / 연관관계 매핑 시 다중성을 나타내는 어노테이션은 필수
optional
: false로 설정하면 연관된 엔티티가 항상 있어야 함fetch
: 글로벌 페치 전략 설정cascade
: 영속성 전이 기능 사용targetEntity
: 연관된 엔티티의 타입 정보를 설정
@JoinColumn
- 외래 키를 매핑할 때 사용 / 생략 가능
name
: 매핑할 외래 키 이름 지정referencedColumnName
: 외래키가 참조하는 대상 테이블의 컬럼명foreignKey
: 외래 키 제약조건 직접 설정 (테이블 생성 시에만 사용)- 이 외에 @Column 의 속성도 사용 가능
- 생략 시 기본 전략 사용 : 필드명 + _ + 참조하는 테이블의 컬럼명 (ex.
team_TEAM_ID
)
2. 연관관계 사용
2.1 저장
- JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태여야 한다.
public void testSave(){
Team team1 = new Team("team1","팀1");
em.persist(team1);
Member member1 = new Member("member1","회원");
member1.setTeam(team1); // 팀 참조
em.persist(member1); // 저장
Member member2 = new member("member2","회원2");
member2.setTeam(team1);
em.persist(member2);
}
- 회원 엔티티가 팀 엔티티를 참조하고 저장하고, JPA는 참조한 팀의 식별자(Team.id)를 외래 키로 사용해서 적절한 등록 쿼리를 생성한다.
2.2 조회
연관관계가 있는 엔티티를 조회하는 방법은 크게 2가지이다.
객체 그래프 탐색
Member member = em.find(Member.class, "member1");
Team team = member.getTeam();
객체지향 쿼리 사용(JPQL)
- 팀 1에 소속된 회원들만 조회
- JPQL에서
:
로 시작하는 것은 파라미터를 바인딩받는 문법
String jpql = "select m from Member m join m.team where" + "t.name=:teamName";
List<Member> resultList = em.createQuery(jpql, Member.class)
.setParameter("teamName", "팀1")
.getResultList();
2.3 수정
- 단순히 불러온 엔티티의 값만 변경 → 변경 감지 기능으로 DB에 자동 반영
private static void updateRelation(EntityManager em){
Team team2 = new Team("team2","팀2");
em.persist(team2);
Member member = em.find(Member.class,"member1");
member.setTeam(team2); // 수정
}
2.4 연관관계 제거
- 연관관계를 null로 설정
private static void deleteRelation(EntityManager em){
Member member1 = em.find(Member.class,"member1");
member1.setTeam(null);
}
2.5 연관된 엔티티 삭제
- 연관관계를 삭제하려면 기존에 있던 연관관계를 먼저 제거하고 삭제해야 한다.
- 그렇지 않으면 외래 키 제약 조건으로 인해 DB에서 오류가 발생한다
member1.setTeam(null);
member2.setTeam(null);
em.remove(team);
3. 양방향 연관관계
- 객체 연관관계
- 회원 → 팀 : 다대일 관계
- 팀 → 회원 : 컬렉션 사용, 일대다 관계
- 테이블은 외래 키 하나로 양방향 조회가 가능하므로 DB에 추가해야 할 내용은 없다.
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private String id;
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
// Getter, Setter
}
@Entity
public class Team {
@Id
@Column(name = "TEAM_ID")
private String id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
// Getter, Setter
}
4. 연관관계의 주인
테이블은 외래 키 하나로 두 테이블의 연관관계를 관리한다. 엔티티를 양방향으로 매핑하면 회원 → 팀 / 팀 → 회원 두 곳에서 서로를 참조한다. 즉, 엔티티를 양방향 연관관계로 설정하면 객체의 참조는 둘인데, 외래키는 하나인 차이가 발생한다.
따라서 두 객체 연관관계 중 하나를 연관관계의 주인으로 정해서 테이블의 외래 키를 관리해야 한다.
1. 양방향 매핑의 규칙 : 연관관계의 주인
- 연관관계의 주인 만이 데이터베이스 연관관계와 매핑되고 외래 키를 관리(등록, 수정, 삭제)할 수 있고, 주인이 아닌 쪽은 읽기만 가능하다.
- 주인은
mappedBy
속성을 사용하지 않는다. - 주인이 아니면
mappedBy
속성을 사용해서 연관관계의 주인을 지정해야 한다.
2. 연관관계의 주인은 외래키가 있는 곳
- 연관관계의 주인은 테이블에 외래키가 있는 곳으로 정해야 한다.
- 일대다, 다대일 관계에서는 항상 ‘다’ 쪽이 외래키를 가진다. 그러므로
@ManyToOne
은 항상 연관관계의 주인이 된다.
5. 양방향 연관관계 저장
public void testSave(){
Team team1 = new Team("team1","팀1");
em.persist(team1);
Member member1 = new Member("member1","회원");
member1.setTeam(team1);
em.persist(member1);
Member member2 = new member("member2","회원2");
member2.setTeam(team1);
em.persist(member2);
}
- 단방향 연관관계 저장 코드와 완전히 같다.
- 주인이 아닌 곳에 입력된 값은 외래키에 영향을 주지 않는다.
team1.getMembers().add(member1)
코드에서Team.members
는 연관관계의 주인이 아니므로 DB에 저장할 때 해당 코드는 무시된다.
6. 양방향 연관관계의 주의점
- 가장 흔히 하는 실수는 주인이 아닌 곳에 값을 입력하는 것이다.
- 이렇게 되면 DB 저장 시 해당 코드는 무시되므로 외래 키의 값도 null이 저장된다.
6.1. 순수한 객체까지 고려한 양방향 연관관계
- 사실 객체 관점에서는 양쪽 방향에 모두 값을 입력해 주는 것이 가장 안전하다.
- 연관관계의 주인에게만 값을 입력하면 JPA를 사용하지 않는 순수한 객체 상태에서 심각한 문제가 발생할 수 있다.
결론 : 객체의 양방향 연관관계는 양쪽 모두 관계를 맺어주자
public void test순수한객체_양방향(){
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(team2);
team1.getMembers().add(member2);
em.persist(member2);
}
6.2 연관관계 편의 메소드
양 쪽 모두 관계를 맺는 코드를 하나의 메소드로 묶어서 사용하면 실수할 일이 줄어든다.
public class Member {
private Team team;
public void setTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
}
member1.setTeam(teamA);
를 설정하고 teamA를 teamB로 변경하고 싶어
member1.setTeam(teamB);
를 설정하면 teamA -> member1
관계를 제거되지 않은 상태에서 teamB와 연결되게 된다. 따라서 연관관계를 변경할 때, 기존 팀이 있으면 삭제하는 코드를 추가해야 한다.
public void setTeam(Team team){
if(this.team != null){
this.team.getMembers().remove(this);
}
this.team = team;
team.getMembers().add(this);
}
'자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 7장 - 고급 매핑 (0) | 2023.06.12 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 6장 - 다양한 연관관계 매핑 (0) | 2023.05.15 |
[자바 ORM 표준 JPA 프로그래밍] 4장 - 엔티티 매핑 (0) | 2023.05.01 |
[자바 ORM 표준 JPA 프로그래밍] 3장 - 영속성 관리 (0) | 2023.04.24 |
[자바 ORM 표준 JPA 프로그래밍] 1장 - JPA 소개 (0) | 2023.04.24 |