지난 포스팅에서 테이블 지향적 (DB 테이블) 설계를 하면 어떤 문제가 발생하는지 알아보았다.
지난 포스팅에서 코드를 리팩토링하여 엔티티가 FK 값을 가지고 있는 것이 아닌 , 실제 객체를 가르키고 있도록 해보자.
이번 예시는 Member 와 Team이 존재한다.
한명의 member은 하나의 team에 속할 수 있고 / 하나의 team에는 여러 member가 속할 수 있다.
즉 member와 team은 1:N 관계이다.
그리고 처음 설명한 것 처럼 member가 team의 FK를 들고 있는 대신 실제 Team 엔티티를 필드로 가지고 있도록 해보자.
단방향 매핑
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "team_id")
private Long id;
private String name;
}
member 엔티티의 team 필드 참조값과 실제 테이블 TEAM_ID 컬럼을 매핑해야한다. -> 사실 이렇게만 보면 뭔말인지 잘 모르겠음
쉽게 생각해보자.
1. 물리적 DB에 member 테이블(member_id, name, team_id)과 team 테이블(team_id, name)이 있다.
2. 엔티티의 모든 필드 (@Transient 제외)는 테이블의 컬럼과 1:1 매칭이 되어야 한다.
3. 검색 조건이 홍길동이란 회원의 정보와 그 소속 팀 이름을 조회한다고 가정하자
4. 그럼 원래 SQL은 다음과 같을 것이다
SELECT *
FROM member m JOIN team t ON m.team_id = t.team_id
WHERE m.username = '홍길동';
5. 여기서 테이블을 조인하려면 member 테이블 쪽에 team테이블의 FK(team_id)가 필요하다.
6. 다시 member 엔티티 코드를 보면 @JoinColumn 에노테이션이 있는데, 해당 필드를 team_id를 FK로 갖는 조인 컬럼으로 쓰겠다는 소리다.
7. 최대한 내가 이해하기 쉽게 로직을 정리하면 테이블의 하나의 로우는 하나의 객체이다 + 그 객체를 자바 코드에선 객체로 사용하고, 물리 DB에서는 FK 값으로 처리한다. = 자바 코드 Team 과 DB의 team_id 가 매핑되는 것
8. 추가적으로 두 테이블간의 관계를 @ManyToOne , @OneToMany .. 등등을 통해 JPA에게 알려줘야함.
테스트코드 작성
@Test
public void 회원_저장() throws Exception {
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Team team = new Team();
team.setName("알나스르");
em.persist(team);
Member member = new Member();
member.setUsername("로나우도");
member.setTeam(team);
em.persist(member);
tx.commit();
} catch(Exception e) {
tx.rollback();
} finally {
em.close();
}
}
@Test
public void 회원_조회() throws Exception {
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Team team = new Team();
team.setName("씨우ㅜㅜ");
em.persist(team);
Member member = new Member();
member.setUsername("베일");
member.setTeam(team);
em.persist(member);
Member findMember = em.find(Member.class, member.getId());
Team findTeam = findMember.getTeam();
assertEquals("씨우ㅜㅜ", findTeam.getName());
tx.commit();
} catch(Exception e) {
tx.rollback();
} finally {
em.close();
}
}
첫번째 테스트를 통해, 이제 더이상 team_id를 넘길 필요 없이 team 객체를 넘기기만 하면 자동으로 JPA가 처리해준다.
두번째 테스트를 통해 이제 더이상 em.find로 엔티티를 찾아와 id값을 반환받고 그 id를 기준으로 다시 find 하여 여러번의 쿼리를 날릴 필요 없이 member만 찾아오면 해당 member의 연관 관계를 보고 getTeam() 메서드만으로 쉽게 team 정보를 찾아올 수 있게되었다.
추가적으로 위 작업들은 다 영속성 컨텍스트 안에 들어있는 데이터기 때문에 쓰기지연 최적화까지 가능하다.