연관관계에서 중요한 부분은 객체와 테이블의 연관관계 매핑의 방식이 다르다는 것을 인식해야하는 것이다. 어떻게 다른지 간단하게 알아보자.
위와 같은 객체와 테이블의 연관관계 매핑의 특성을 더 잘 이해하기 위해 아래 사항들을 인지해야한다.
연관 관계의 중요도 순으로 차근차근 알아가보자.
N:1이라고도 하며 연관 관계를 이해할 때 가장 먼저 이해가 필요한 부분이다.
위 그림을 보면서 이해해보자.
필드로 보유
하여 연관관계를 맺음.member.getTeam()
등의 메소드로 Member와 연관된 Team을 알 수 있지만 Team과 연관된 Member에 대해서는 알 수 없음.외래 키
를 통해 테이블 간의 연관 관계를 맺음.JOIN
을 이용하여 어느 방향이든 서로 연관된 테이블의 정보를 조회할 수 있음.객체와 테이블의 연관 관계의 가장 큰 차이는 참조를 통한 연관 관계는 항상 단방향이라는 점이다. 즉, 객체 입장에서의 양방향은 서로 단방향으로 연관 관계를 맺는 것을 의미한다.
위 그림에서도 볼 수 있듯이 Member → Team으로 화살표가 표시되어있고, 반대로 참조할 경우 Member ← Team 방향으로 화살표가 표시될 것이다. 즉 하나의 선에 화살표가 2개가 표시되는 것이 아닌 각각의 선에 방향이 표시되는 형태라고 이해하면 좋을 것 같다. 코드를 보면서 이해해보자.
// Member -> Team 단방향
class Member {
private Long id;
private String memberName;
private int age;
private Team team; // 참조를 보관
}
// Team -> Member 단방향
class Team {
private Long id;
private String teamName;
private List<Member> members; // Team에 연관된 회원들의 참조를 보관
}
위 코드에서 서로 단방향 연관 관계를 맺었다. 이 상태가 위에서 설명한 2개의 단방향 관계이며, 객체 그래프 탐색을 통해 서로를 호출하거나 참조할 수 있기 때문에 이런 형태를 양방향 관계라고 부르는 것이다. 정확하고 엄밀히 따지면 단방향 2개가 맞다.
여기까지 객체의 단방향 연관관계에 대해 알아보았으니 실제 엔티티와 테이블을 어떻게 매핑을 하는지 알아보자.
// Member 엔티티
@Entity
public class Member{
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
@Column(name = "name")
private String memberName;
// 연관관계 매핑(다대일, N:1)
@ManyToOne // 다중성 지정
@JoinColumn(name = "team_id") // 연관관계 컬럼 지정
private Team team;
// getter, setter
...
}
// Team 엔티티
@Entity
public class Team{
@Id @GeneratedValue
@Column(name = "team_id")
private Long id;
private String name;
// getter, setter
...
}
위처럼 코드를 작성해주면 Member → Team의 단방향 연관 관계 매핑이 완료된다. 연관관계 매핑에서 사용된 어노테이션에 대해 확인하고 넘어가자.
외래 키를 매핑할 때 사용한다.
주요 속성은 다음과 같다.
속성 | 기능 | 기본값 |
---|---|---|
name | 매핑할 외래 키 이름 | 필드명_기본키컬럼명 |
referencedColumnName | 외래 키가 참조하는 대상 테이블의 컬럼명 | 참조하는 테이블의 기본키 컬럼명 |
foreignKey | 외래 키 제약 조건 |
| unique
nullable
insertable
updatable
columnDefinition
table | @Column 어노테이션의 속성과 동일 | |
아래와 같이 @JounColumn
어노테이션을 생략하면 기본 전략을 사용한다.
// 기본전략 : 필드명_참조하는 컬럼명
@ManyToOne
private Team team;
연관 관계의 다중성을 표현 할 때 사용한다. 생략할 수 없다.
주요 속성은 다음과 같다.
속성 | 기능 | 기본값 |
---|---|---|
optional | false로 설정 시 연관된 엔티티가 항상 있어야한다. | true |
fetch | 글로벌 패치 전략을 사용 | FetchType.EAGER |
FetchType.LAZY | ||
cascade | 영속성 전이 기능 사용 | - |
fetch
와 cascade
는 여기서 다룰 내용은 아니지만 간략히 설명하면 다음과 같다.
위에서 다룬 내용을 토대로 연관 관계 사용에 대해서 알아보자.
... 생략
public void save(){
// 팀 생성
Team team = new Team();
team.setId("팀1");
// 멤버들 생성
Member member1 = new Member();
Member member2 = new Member();
member1.setId("멤버1");
member2.setId("멤버2");
// 연관 관계 설정
member1.setTeam(team);
member2.setTeam(team);
// 영속화
entityManager.persist(member1);
entityManager.persist(member2);
}
1개의 팀과 2명에 멤버를 연관 관계 설정을 하였다. 이후 영속성 컨텍스트에 영속화하여 저장시키면 자동으로 쿼리가 생성되는데 쿼리는 어떤 모습인지 알아보자.
# 팀 생성
INSERT INTO TEAM (TEAM_ID, NAME) VALUES ('팀1', 생략)
# 멤버 생성
INSERT INTO MEMBER (MEMBER_ID, NAME, TEAM_ID) VALUES ('멤버1', 생략, '팀1')
INSERT INTO MEMBER (MEMBER_ID, NAME, TEAM_ID) VALUES ('멤버2', 생략, '팀1')
멤버를 등록 할 때 JPA가 자동으로 팀 객체의 식별자 값을 입력해준다. 연관 관계를 설정해주고 관계의 주인을 영속화하면 해당 객체의 정보도 같이 영속화가 되는 것이다.
연과 관계를 매핑한 엔티티를 조회하는 방법은 크게 두가지가 있다.
객체 그래프 탐색
// 연관된 객체를 조회
String id = "멤버1";
Member member = entityManager.find(Member.class, id);
Team findTeam = member.getTeam();
먼저 연관 관계의 주인이 되는 객체를 데이터베이스에서 조회하고, 해당 객체와 연관된 객체를 그래프 탐색을 통해 조회 할 수 있다.
객체 지향 쿼리언어(JPQL) 사용
// JPQL 정의
String jpql = "select m from Member m join m.team t where t.id = :teamId";
List<Member> result = entityManager.createQuery(jpql, Member.class)
.setParameter("teamId", "팀1")
.getResultList();
위 JPQL에서 Member m join [m.team](http://m.team)
부분이 관계를 가진 필드를 이용하여 조인을 하는 부분이다. 팀 id가 ‘팀1’인 팀의 회원들을 조회한 쿼리문이며 :teamId
이 부분은 쿼리문에 사용될 파라미터를 바인딩하는 문법이다.
수정은 이전에도 한번 보았겠지만 영속화된 엔티티의 필드를 변경해줌으로써 영속성 컨텍스트가 변경 감지를 통하여 변경사항을 데이터베이스에 반영하도록 코드를 작성하면된다.
...생략
Team team2 = new Team();
team.setId("팀2");
// 새로운 팀으로 관계 변경
Member member = entityManager.find(Member.class, "멤버1");
member.setTeam(team2); // 수정
member는 기존에 데이터베이스에 ‘팀1’을 연관 관계로 매핑했던 엔티티이다. 해당 엔티티를 조회하여 필드의 참조를 수정하면 영속성 컨텍스트가 변경을 감지하여 데이터베이스에 자동으로 반영한다.
...생략
Member member1 = entityManager.find(Member.class, "멤버1");
member1.setTeam(null); // 연관관계 제거
연관 관계를 제거하는 것은 객체가 참조하는 연관 관계를 없애버리면 된다.
추가적으로 연관된 엔티티를 삭제하려면 기존에 있던 연관관계를 먼저 제거하고 삭제해야한다. 외래 키 제약조건에 의해 삭제되지 않는 상황을 해결하기 위해서이다.
테이블과 객체의 연관관계에 대한 특징을 알아 볼 수 있었다. 책을 읽기 전에는 제대로 구분할 수 없었고, 어떤 영향을 미치는지 정확히 알 수 없었던 부분들이 이해가 되기 시작했다. 설계 단계부터 객체 그래프가 가능 할 수 있도록 고려하는 것이 중요하고, 중간중간 엔티티가 변경될 수 있지만 큰 틀이 벗어나는 것은 연관관계 매핑에 불편한 상황을 만드는 것이라는 것을 항상 생각해야한다.
다음 포스팅에서는 더욱 다양한 연관관계에 대해서 알아보자.
그럼 이만.