JPA 4강 - 연관관계 매핑
'객체지향 설계의 목표는 자율적인 객체들의 협력 공동체를 만드는 것이다.' - 조영호(객체지향의 사실과 오해)
객체를 테이블에 맞추어 설계(연관관계가 없는 객체)
객체에서는 값만 그대로 세팅해놨기 때문에 Team과의 연관관계가 없다.
객체를 테이블에 맞추어 모델링(참조(Team객체참조) 대신에 외래 키를 그대로 사용)
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
private int age;
@Column(name = "TEAM_ID")
private Long teamId;
...
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
...
}
Main.java
//팀 저장, 아이디는 자동으로 삽입된다.(AUTO속성으로 설정했기 때문)
Team team = new Team();
team.setName("TeamA");
em.persist(team);
//회원저장
Member member = new Member();
member.setName("member1");
member.setTeamId(team.getId());
em.persist(member);
위와 같이 Member 클래스와 Team 클래스, Main 클래스를 작성하자. 그리고 실행하자. H2 콘솔을 통해 확인하니 MEMBER 테이블의 TEAM_ID 컬럼에 1, TEAM 테이블의 ID컬럼에 1이 들어간 것을 확인할 수 있다.
하지만 위와 같은 방법으로 조회를 한다고 해보자.
객체를 테이블에 맞추어 모델링(식별자로 다시 조회, 객체 지향적인 방법은 아니다.)
//조회
Member findMember = em.find(Member.class, member.getId());
Long teamId = findMember.getTeamId();
//연관관계가 없음
Team findTeam = em.find(Team.class, teamId);
연관된 객체가 없으므로 식별자로 데이터베이스에서 다시 조회해야 한다. 객체지향적인 방법은 아니다. 데이터지향적인 것이다.(데이터가 있고 데이터 부은 것을 가져오는)
객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.
- 테이블은 외래 키로 조인을 해서 사용해서 연관된 테이블을 찾는다.
- 객체는 참조를 사용해서 연관된 객체를 찾는다.
- 테이블과 객체 사이에는 이런 큰 간격이 있다.
연관관계 매핑이론
단방향 매핑
객체 지향 모델링(객체 연관관계 사용)
이번에는 Member 클래스의 teamId를 Team 객체로 바꾸는 것이다. 아래 그림을 보면 Member 클래스에서 Team 정보를 가져올 수 있지만 Team 클래스에서는 Member 정보를 못 가져온다. 바로 단방향 매핑이다. 하지만 테이블은 MEMBER 테이블에서 TEAM 테이블 정보를 가져올 수 있고 그 반대도 가능하다.
- @Entity
- public class Member {
- @Id @GeneratedValue
- private Long id;
- @Column(name = "USERNAME")
- private String name;
- private int age;
- @ManyToOne
- @JoinColumn(name = "TEAM_ID")
- private Team team;
- ...
- }
라인 11 @ManyToOne 어노테이션이 있다. Member 클래스와 Team 클래스의 관계는 Member 클래스 입장에서 보면 다 대 1이고 Team 클래스 입장에서 보면 1 대 다 이다. 그래서 Member 클래스에는 @ManyToOne 어노테이션을 필수로 넣어줘야 한다. 라인 12 @JoinColumn(name = "TEAM_ID") 어노테이션은 조인 컬럼을 지정하는 것이다. @JoinColumn 어노테이션은 필수이고 괄호안에는 안 적어줘도 디폴트로 생성되지만 웬만하면 넣어주는게 좋다.
객체 지향 모델링(ORM 매핑)
위 그림을 보면 Member 클래스의 Team 객체가 MEMBER 테이블에 TEAM_ID 컬럼과 매핑이 된다. 이 것을 연관관계 매핑이라고 한다. 이렇게 연관관계 매핑을 해주면 그 다음부터는 JPA가 알아서 해준다.
객체 지향 모델링(연관관계 저장)
- //팀 저장
- Team team = new Team();
- team.setName("TeamA");
- em.persist(team);
- //회원 저장
- Member member = new Member();
- member.setName("member1");
- member.setTeam(team); //단방향 연관관계 설정, 참조 저장
- em.persist(member);
라인 9에서 member 객체에 team 객체를 세팅하고 있다. 객체간 연관관계가 없을 때는 team 객체에서 teamId를 꺼내와서 세팅을 해줬지만 지금은 바로 team 객체를 세팅하면 된다.
객체 지향 모델링(참조로 연관관계 조회 - 객체 그래프 탐색)
//조회
Member findMember = em.find(Member.class, member.getId());
//참조를 이용해서 연관관계 조회
Team findTeam = findMember.getTeam();
실습
Member 클래스를 아래와 같이 변경해보자.
- @Entity
- public class Member {
- @Id @GeneratedValue
- private Long id;
- @Column(name = "USERNAME")
- private String name;
- private int age;
- @ManyToOne
- @JoinColumn(name = "TEAM_ID")
- private Team team;
- ...
- }
Main 클래스를 아래와 같이 수정하고 실행해보자.
- try {
- Team team = new Team();
- team.setName("teamA");
- em.persist(team);
- Member member = new Member();
- member.setName("hello");
- member.setTeam(team);
- em.persist(member);
- Member findMember = em.find(Member.class, member.getId());
- Team findTeam = findMember.getTeam();
- tx.commit();
- } catch (Exception e) {
H2 콘솔을 보면 데이터가 정상적으로 들어간 것을 확인할 수 있다.
정리하자면 먼저 위와 같이 연관관계 설정을 하기 위해서는 객체 간의 연관관계가 있어야 한다.(Member 클래스에 Team 객체를 필드로 참조하는 것) 반면 테이블은 변경된 것이 없다. 그리고 연관관계를 맺는 객체의 참조와 데이터베이스의 외래키를 매핑해주는 것이다. 이게 바로 연관관계 매핑이다.
사실 Main 클래스 라인 2에서 team을 조회해도 SELECT 쿼리가 안나간다.(이클립스 콘솔창에서 쿼리를 확인할 수가 없다.) 이유는 내부적으로 캐싱을 해서 그렇다.
Main.java
- Member findMember = em.find(Member.class, member.getId());
- Team findTeam = findMember.getTeam();
해결방안은 라인 1, 2와 같이 코드를 추가한다. 그리고 다시 실행해보자.
- em.flush();//DB에 쿼리를 다 보낸다.
- em.clear();//캐시를 다 비운다.
- Member findMember = em.find(Member.class, member.getId());
- Team findTeam = findMember.getTeam();
아래와 같이 한방쿼리로 TEAM 테이블을 조회했다.
위와 같이 조인하는게 싫고 member만 가져오고 싶다면 아래 Member 클래스 라인 1과 같이 @ManyToOne 어노테이션에 FetchType.LAZY라고 설정해야한다. 실행해보자.
Member.java
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "TEAM_ID")
- private Team team;
아래와 같이 MEMBER 테이블만 조회한 것을 볼 수 있다.
Team 객체는 실제 team 객체에서 정보를 실제 사용하는 시점에 쿼리를 날린다. 아래 코드와 같이 라인 4 findTeam.getName() 코드를 Main 클래스에 추가하고 실행해보자.
- Member findMember = em.find(Member.class, member.getId());
- Team findTeam = findMember.getTeam();
- findTeam.getName();
- tx.commit();
아래 결과와 같이 team 조회 쿼리를 보내는 것을 확인할 수 있다.
이것을 바로 지연로딩이라고 한다. 기본 값이 @ManyToOne(fetch = FetchType.EAGER)이다.(조인쿼리) 권장사항은 LAZY이다.
객체 지향 모델링(연관관계 수정)
//새로운 팀B
Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB);
//회원1에 새로운 팀B 설정
member.setTeam(teamB);
위와 같이 팀을 변경하면 MEMBER 테이블에 TEAM_ID(FK) 값이 바뀐다.
양방향 매핑
간단하게 말하자면 Member에서도 Team 가고 싶고 Team에서도 Member를 가는 것이다. Team에서 Member를 가져오고 싶으면 1 대 다 매핑이기 때문에 List 타입으로 선언해서 가져오면 된다. 그런데 DB는 변경되는 게 없다. 왜냐하면 DB는 다 가져올 수 있으니 말이다. DB는 방향이 없다. 그런데 객체는 member.getTeam()도 있고 team.getMembers() 2개 다 있다. 이게 바로 DB와 객체의 가장 큰 차이다.
Member 엔티티는 단방향과 동일하다.
Member.java
- @Entity
- public class Member {
- @Id @GeneratedValue
- private Long id;
- @Column(name = "USERNAME")
- private String name;
- private int age;
- @ManyToOne
- @JoinColumn(name = "TEAM_ID")
- private Team team;
- ...
- }
단방향으로 개발하다 양방향으로 바꾸고 싶다면 아래와 같이 추가하면 된다.
Team 엔티티는 컬렉션을 추가한다.
- @Entity
- public class Team {
- @Id @GeneratedValue
- private Long id;
- private String name;
- @OneToMany(mappedBy = "team")
- List<Member> members = new ArrayList<Member>();
- ...
- }
라인 9 ~ 10 처럼 코드를 추가하면 된다. 그리고 getter, setter를 넣자.
Main 클래스에도 코드를 추가하자. 라인 4 ~ 6을 추가하고 실행하자.
- findTeam.getName();
- List<Member> members = findTeam.getMembers();
- for(Member member1 : members) {
- System.out.println("member1 = " + member1);
- }
- tx.commit();
아래와 같이 member 객체가 출력되는 것을 볼 수 있다.
member 필드 정보를 출력하기 위해 아래와 toString() 메서드를 오버라이드 해보자. 그리고 다시 실행해보자.
@Override
public String toString() {
return "Member [id=" + id + ", name=" + name + ", age=" + age + ", team=" + team + "]";
}
아래와 같이 필드정보가 출력되는 것을 확인할 수 있다. 역방향조회이다.
반대 방향으로 객체그래프 탐색
//조회
Team findTeam = em.find(Team.class, team.getId());
int memberSize = findTeam.getMembers().size(); //역방향 조회
위 코드와 같이 teamd객체에서 member 객체를 조회해도 된다.
'IT공부 > JPA' 카테고리의 다른 글
JPA 6강 - JPA 내부구조 (0) | 2020.05.22 |
---|---|
JPA 5강 - 양방향 매핑 (0) | 2020.05.21 |
JPA 3강 - 필드와 컬럼 매핑 (0) | 2020.05.21 |
JPA 2강 - JPA기초와 매핑 (0) | 2020.05.20 |
JPA 1강 (0) | 2020.05.19 |