단방향 연관관계
단방향 연관관계는 1 : N(1대다)
인 경우를 말한다. 이 경우는 한 쪽의 엔티티가 관계를 맺은 엔티티 쪽의 여러 객체를 가질 수 있다는 것을 의미한다.
- 팀은 다수의 회원을 포함하고 있다.
- 하나의 주문에는 다수의 주문상품이 포함된다.
- 상위 카테고리는 다수의 하위 카테고리를 가지고 있다.
테이블 연관관계
테이블은 외래 키를 활용하여 연관관계를 맺는다. 그리고 외래 키를 통해서 테이블을 조인할 수 있는데 외래 키가 두 테이블 중 어디에 있든 조인이 가능하다. (양방향 관계)
1
2
3
4
5
6
7
8
9
select
*
from member m
join team t on t.team_id = m.team_id
select
*
from team t
join member m on m.team_id = t.team_id
객체 연관관계
객체의 경우 필드를 이용하여 연관관계를 맺는다. 객체는 테이블과 다르게 연관관계 필드가 있는 객체에서만 접근이 가능하다. (단방향 관계)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Member {
private Long memberId;
private Team team;
private String username;
}
class Team {
private Long teamId;
private String name;
}
Member member = new Member();
member.getTeam();
Member에서는 Team에 접근할 수 있지만 Team은 member의 정보가 없기 때문에 접근할 수 없다.
객체 연관관계 vs 테이블 연관관계
객체
- 객체는 참조(주소)로 연관관계를 맺는다.
- 참조를 사용하는 객체의 연관관계는 단방향이다.
테이블
- 테이블은 외래 키로 연관관계를 맺는다.
- 외래 키를 사용하는 테이블의 연관관계는 양방향이다.
객체 관계 매핑
위에서 객체와 테이블의 연관관계 방법 및 차이점을 알아보았다. 이제 JPA를 활용하여 객체와 테이블을 매핑하는 방법을 알아보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Entity
class Member {
@Id1
private Long memberId;
// 연관관계 매핑
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
private String username;
}
class Team {
private Long teamId;
private String name;
}
@ManyToOne
이름 그대로 다대일(N:1) 관계를 나타내는 매핑 정보이다. 회원과 팀은 다대일 관계에 있다. JPA에서는 연관관계를 표현할 때 이러한 다중성을 나타내는 어노테이션을 필수로 사용해야한다.
@ManyToOne 속성
속성 | 기능 | 기본값 |
---|---|---|
optional | false로 설정하면 연관된 엔티티가 항상 있어야 한다. | true |
fetch | 글로벌 페치 전략을 설정한다. | FetchType.EAGER |
cascade | 영속성 전이 기능을 사용한다. | |
targetEntity | 연관된 엔티티의 타입 정보를 설정한다. 이 기능은 거의 사용하지 않는다. 컬렉션을 사용해도 제네릭으로 타입 정보를 알 수 있다. |
@ManyToOne
과 함께 사용된 어노테이션인 @JoinColumn
은 외래 키를 매핑하기 위해 사용한다. 즉, @JoinColumn(name = "team_id")
에서 name의 값인 team_id
는 외래키를 나타낸다. 해당 어노테이션은 생략이 가능하다.
@JoinColumn 속성
속성 | 기능 | 기본값 |
---|---|---|
name | 매핑할 외래 키 이름 | 필드명 + _ + 참조하는 테이블의 기본 키 컬럼명 |
referencedColumnName | 외래 키가 참조하는 대상 테이블의 컬럼명 | 참조하는 테이블의 기본 키 컬럼명 |
foreignKey(DDL) | 외래 키 제약조건을 직접 지정할 수 있다. 이 속성은 테이블을 생성할 때만 사용한다. | |
unique nullable insertable updatable columnDefinition table | @Column의 속성과 동일 |
@JoinColumn 생략 시
@JoinColumn
을 생략하게 되면 외래 키를 찾을 때 기본 전략을 사용한다.
1
2
@ManyToOne
private Team team;
기본 전략: 필드명(tema) + _ + 참조하는 테이블의 컬럼명(team_id) -> team_team_id
연관관계 사용
저장
연관관계를 매핑한 엔티티를 어떻게 저장하는지 예제로 알아보자.
1
2
3
4
5
6
7
8
9
10
11
public void save() {
// 팀1 저장
Team team1 = new Team("team1", "팀1");
em.persist(team1);
// 회원1 저장
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1); // 연관관계 설정
em.persist(member1);
}
연관관계를 매핑하기 위해 Member
를 생성 후 member가 가지고 있는 setTeam(team1)
메소드로 Team
을 설정하는 것을 볼 수 있다.
Member 엔티티에 Team 필드에 값이 세팅되어 있는 상태로 Member가 저장되면 JPA가 참조하는 팀의 식별자를 외래 키로 사용하여 적절한 등록 쿼리를 생성한다.
조회
연관관계가 있는 엔티티를 조회하는 방법은 크게 2가지가 있다.
- 객체 그래프 탐색 (객체 연관관계를 사용한 조회)
- 객체지향 쿼리(JPQL) 사용
객체 그래프 탐색
1
2
Member member = em.find(Member.class, "member1");
Team team = member.getTeam(); // 객체 그래프 탐색
위 코드와 같이 객체를 통해 연관된 엔티티를 조회하는 것을 객체 그래프 탐색이라 한다.
수정
연관관계를 수정하는 방법을 코드를 통해 알아보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void update() {
// 팀1 저장
Team team1 = new Team("team1", "팀1");
em.persist(team1);
// 회원1 저장
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1); // 연관관계 설정
em.persist(member1);
// 새로운 팀2 생성
Team team2 = new Team("team2", "팀2");
em.persist(team2);
// 회원1 조회
Member find = em.find(Member.class, "member1");
// 회원1에 팀2 설정
member.setTeam(team2);
}
영속상태의 Member를 조회 후 새로운 연관관계인 Team2로 값을 변경하게 되면 이후 트랜잭션을 커밋할 때 플러시가 발생되고 변경감지 기능이 동작하여 연관관계가 변경된다.
제거 (연관관계 제거)
1
2
3
4
public void delete() {
Member member1 = em.find(Member.class, "member1");
member1.setTeam(null);
}
영속상태의 Member를 조회 후 Team 값을 null로 변경하면 연관관계를 제거할 수 있다.
연관된 엔티티 삭제
만약 연관된 엔티티를 삭제하려면 기존에 있던 연관관계를 먼저 제거후 삭제를 해야한다. 그렇지 않으면 외래 키 제약조건으로 인해 데이터베이스 오류가 발생하게 된다. 예제의 경우로 다시 설명하면 Team1을 삭제하기 위해서는 Member1과 Team1의 연관관계를 우선 삭제해야 한다.
1
2
member1.setTeam(null); // member1과 team1의 연관관계 제거
em.remove(team1); // team1 삭제
Comments powered by Disqus.