이번 글에서는 <연관관계가 필요한 이유, 단방향 연관관계>에 대해 알아보겠습니다.
이 시리즈 글은 김영한 님의 강의, 책을 보고 적은 것임을 알려드립니다. (강추)
<시나리오>
- 회원과 팀이 있다.
- 회원은 하나의 팀에만 소속될 수 있다.
- 회원과 팀은 다대일 관계다.
아래는 회원과 팀 클래스입니다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@Column(name = "TEAM_ID")
private Long teamId;
...
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
...
}
아래는 팀과 회원을 저장하는 코드입니다.
//팀 저장
Team team = new Team();
team.setName("wooteco");
em.persist(team);
//회원 저장
Member member = new Member();
member.setName("conas");
member.setTeamId(team.getId());
em.persist(member);
이 상황에서 만약 회원의 팀을 찾으려면 어떻게 할까요?
방법은 다음과 같습니다.
Member findMember = em.find(Member.class, member.getId());
Long findTeamId = findMember.getId();
Team findTeam = em.find(Team.class, findTeamId);
연관관계가 없다면 위와 같이, member의 team을 가져오는데 계속 꺼내와야합니다.
member를 꺼내오고, id를 가져와서 다시 팀을 가져오는... 회원의 팀을 가져오기 위해 많은 비용이 듭니다.
그리고 객체 지향스럽지 않은 코드가 됩니다.
객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없습니다.
회원과 팀의 관계를 통해 다대일 단방향 관계를 알아보겠습니다.
테이블 연관관계
+ 회원 테이블은 TEAM_ID 외래 키로 팀 테이블과 연관관계를 맺습니다.
MEMBER JOIN TEAM
과 TEAM JOIN MEMBER
둘 다 가능합니다. SELECT *
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
SELECT *
FROM TEAM T
JOIN MEMBER M ON T.TEAM_ID = M.TEAM_ID
객체 연관관계와 테이블 연관관계의 가장 큰 차이
참조를 통한 연관관계는 언제나 단방향입니다. 객체간에 연관관계를 양방향으로 만드록 싶으면 반대쪽에도 필드를 추가해서 참조를 보관해야 합니다. 이렇게 양쪽에서 서로 참조하는 것을 양방향 연관관계라고 합니다.
하지만 정확히 이야기하면 이것은 양방향 관계가 아니라 서로 다른 단방향 관계 2개입니다. 반면에 테이블은 외래 키 하나로 양방향으로 조인할 수 있습니다.
객체 연관관계 vs 테이블 연관관계 정리
+ 객체는 참조(주소)로 연관관계를 맺습니다.
+ 테이블은 외래 키로 연관관계를 맺습니다.
+ 참조를 사용하는 객체의 연관관계는 단방향입니다.
+ 외래 키를 사용하는 테이블의 연관관계는 양방향입니다. (A JOIN B가 가능하면 반대로 B JOIN A도 가능)
Member, Team class
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
// Getter, Setter ...
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
// Getter, Setter ...
}
연관관계 저장
//팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
//회원 저장
Member member = new Member();
member.setName("conas");
member.setTeam(team); //단방향 연관관계 설정, 참조 저장
em.persist(member);
이전의 코드와 다른점은 member에 teamId를 넣지 않고, team을 넣어준다는 것입니다.
참조로 연관관계 조회 - 객체 그래프 탐색
//조회
Member findMember = em.find(Member.class, member.getId());
//참조를 사용해서 연관관계 조회
Team findTeam = findMember.getTeam();
연관관계 수정
// 새로운 팀B
Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB);
// 회원1에 새로운 팀B 설정
member.setTeam(teamB);
@ManyToOne: 다대일(N:1) 관계라는 매핑 정보입니다. 위에서 봤던 회원과 팀은 다대일 관계입니다. 연관관계를 매핑할 때 이렇게 다중성을 나타내는 어노테이션을 필수로 사용해야 합니다.
속성 | 기능 | 기본값 |
---|---|---|
optional | false로 설정하면 연관된 엔티티가 항상 있어야 한다 | true |
fetch | 글로벌 패치 전략을 설정한다. (자세한 내용은 추후에) | @ManyToOne=FetchType.EAGER, @OneToMany=FetchType.LAZY |
cascade | 영속성 전이 기능을 사용한다.(자세한 내용은 추후에) | |
targetEntity | 연관된 엔티티의 타입 정보를 설정한다. 이 기능은 거의 사용하지 않음 |
@JoinColumn: 외래 키를 매핑할 때 사용합니다.
속성 | 기능 | 기본값 |
---|---|---|
name | 매핑할 외래 키 이름 | "필드명" + "_" + "참조하는 테이블의 기본 키 컬럼명" |
referencedColumnName | 외래 키가 참조하는 대상 테이블의 컬럼명 | 참조하는 테이블의 기본 키 컬럼명 |
foreignKey(DDL) | 외래 키 제약조건을 직접 지정할 수 있다. 이 속성은 테이블을 생성할 때만 사용한다. | |
unique | @Column의 속성과 같다 | |
nullable | @Column의 속성과 같다 | |
insertable | @Column의 속성과 같다 | |
updatable | @Column의 속성과 같다 | |
columnDefinition | @Column의 속성과 같다 | |
table | @Column의 속성과 같다 |
JoinColumn 어노테이션은 생략 가능합니다.
@JoinColumn을 생략하면 외래 키를 찾을 때 기본 전략을 사용합니다.
기본 전략: 필드명 + _ + 참조하는 테이블의 컬럼명
좋은 글 잘보고 갑니다 !!
그리구 오타 하나 있는거 같아요
Long findTeamId = findMember.getId(); 에서 getId() 를 getTeamId() 로 바꾸셔야 할거 같아욤