Post

(JPA)연관관계 매핑

JPA 연관관계 매핑

객체

  • 멤버(N)

  • 팀(1)

연관관계 매핑 미설정

모델링

  • 객체테이블에 맞추어 모델링

    참조 대신에 외래 키를 그대로 사용

객체

1
2
3
4
5
6
7
8
9
10
@Entity
public class Member {
    @Id
    @GeneratedValue
    private Long id;
    @Column(name = "USERNAME")
    private String username;
    @Column(name = "TEAM_ID")
    private Long teamId;
}
  • 멤버 객체
1
2
3
4
5
6
7
8
@Entity
public class Team {
    @Id
    @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;
}
  • 팀 객체

객체 저장

1
2
3
4
5
6
7
8
9
10
// 팀 저장
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 Table

MEMBER_IDTEAM_IDUSERNAME
11member1

Team Table

TEAM_IDNAME
1TeamA

객체 조회

1
2
3
4
5
6
7
8
// 멤버 조회
Member findMember = em.find(Member.class, member.getId());

// 멤버의 팀 아이디 가져오기
Long findTeamId = findMember.getTeamId();

// 멤버가 해당하는 팀 조회
Team findTeam = em.find(Team.class, findTeamId);
  • 연관관계가 존재하지 않아, 필요할때 마다 JPA를 통해 계속 조회가 이뤄진다.
    • 객체지향스럽지 않다.
  • 테이블은 외래키로 조인을 사용해서 연관된 테이블을 찾고, 객체는 참조를 사용해서 연관된 객체를 찾는다.
    • 테이블과 객체간에는 이런 큰 차이점이 있다.

연관관계 매핑 설정(단방향)

모델링

  • 객체 지향 모델링

객체

1
2
3
4
5
6
7
8
9
10
11
@Entity
public class Member {
    @Id
    @GeneratedValue
    private Long id;
    @Column(name = "USERNAME")
    private String username;
	@ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}
  • Member N 이고, Team이 1 이기 때문에 ManyToOne 연관관계를 설정해준다.
  • Team_ID(FK) 를 매핑해줘야 하기 때문에 JoinColumn 을 사용해서 매핑할 컬럼을 지정해준다.
  • 위와 같이 설정해 주면 매핑이 끝나게 된다.
  • Team 객체 같은 경우는 변경 사항이 없다.

객체 저장

1
2
3
4
5
6
7
8
9
10
// 팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);

// 멤버 저장
Member member = new Member();
member.setName("member1");
member.setTeam(team);
em.persist(member);
  • team의 id 값을 저장하는 것이 아니라 team 객체 자체를 저장

연관관계 매핑 설정(양방향)

  • 양방향으로 객체 탐색이 가능
  • 매핑은 단방향이 좋다.

DB 테이블

  • 방향이 따로 존재 하지 않는다, FK, PK의 조인을 통해 양쪽으로 조회가 가능
  • 테이블 연관관계
    • 회원 <-> 팀의 연관관계 1개(양방향)

객체

  • 객체 연관관계

    • 객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개다.
    • 회원 -> 팀 연관관계 1개(단방향)

    • 팀 -> 회원 연관관계 1개(단방향)
  • 위에 단방향 매핑으로는 member에서는 team을 조회 할 수 있으나, team에서는 member를 조회 할 수 없다.

    • 객체와 DB 테이블의 가장 큰 차이점
1
2
3
4
5
6
7
8
9
10
@Entity
public class Team {
    @Id
    @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;
    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();
}
  • Team 객체에 member를 저장 할 수 있도록 List 생성
  • Member -> Team은 다대일, Team -> Member 일대다 이기 때문에 @OneToMany 매핑 설정을 해준다.
  • 일대다 매핑에서 어떤 객체랑 연결되어 있는지 나타내주기 위해 mappedBy = "team"을 설정
    • Member 객체에 존재하는 TeamFK가 저장되는 필드 명을 mappedBy 로 지정해주면 된다.
  • Member 객체는 변경 사항이 없다.

객체 조회

1
2
3
4
5
6
...
Member findMember = em.find(Member.class, member.getId());
List<Member> members = findMember.getTeam().getMembers();
for (Member m : members) {
    System.out.println("m = " + m.getUsername());
}
  • 멤버를 조회하고, 멤버를 통해 다시 팀을 조회 한 후 해당 팀에 속해 있는 멤버를 조회 할 수 있게되었다.
  • 한쪽 방향에서만 조회가 되는것이 아니라 양방향으로 조회가 가능

연관관계 주인

  • Member 클래스 혹은 Team 클래스에서 서로 참조 하고 있는 두개의 필드 값중 하나로 외래 키를 관리해야 한다.

  • 객체의 두 관계중 하나를 연관관계의 주인으로 지정
  • 연관관계의 주인만이 외래 키를 관리(등록, 수정)

  • 주인이 아닌쪽은 readOnly
  • 주인은 mappedBy 속성을 사용하지 않는다.
  • 주인이 아니면 mappedBy 속성으로주인 지정
  • 누구를 주인으로?
    • 외래키가 있는 객체를 주인으로 설정(영한님 가이드)
    • 위의 예제에서는 Member를 주인으로 설정

양방향 매핑에서 가장 많이 하는 실수

1
2
3
4
5
6
7
8
9
10
11
Team team = new Team();
team.setName("TeamA");
team.getMembers().add(member); // 문제를 일으키는 코드
em.persist(team);

Member member = new Member();
member.setName("member1");

/**/

em.persist(member);
  • 연관관계 주인에 값을 입력하지 않았다.
  • 3번째 라인에 작성되어 있는 코드는 연관관계 주인이 아닌곳에 값을 넣어 주기 때문에 DB에서 변경이 일어나지 않는다.

  • 9번째 라인에 member.setTeam(team); 코드를 추가해서 연관관계 주인에 값을 넣어 줘야 DB에 Insert 된다.
  • 둘다 넣어도 상관 없지만, 주인의 값을 넣어주는 코드는 절때 빠지면 안된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setName("member1");
member.setTeam(team);
em.persist(member);

em.flush();
em.clear();

Team findTeam = em.find(Team.class, team.getId());
List<Member> members = findTeam.getMembers();

for (Member m : members) {
    System.out.println("m = " + m.getUsername());
}
  • JPA 입장에서는 주인에만 값을 넣어줘도 상관 없지만(flush, clear가 이뤄진 경우), 객체 관점에서는 둘다 값을 넣어주는게 좋다.

  • flush와 clear가 제대로 이뤄지지 않으면, 영속성 컨텍스트에서 1차 캐쉬의 값을 조회해 오기 때문에 값이 없다.

  • 분명 Team객체에 존재하는 members에 따로 member를 넣어 주지 않았지만, 조회를 하면 조회가 잘 된다.

    • members의 데이터를 실제로 사용하는 시점에 query를 하나 사용해서 연관되어 있는 member 데이터를 전부 가져옴
    • 양방향 편의 메서드가 없어도 데이터가 잘 나오는데 왜 사용하는 거지? 라는 의문점이 있었는데 한방에 해결되었다.


REFERENCE

자바 ORM 표준 JPA 프로그래밍 - 기본편

자바 ORM 표준 JPA 프로그래밍 - 4장

This post is licensed under CC BY 4.0 by the author.