Web/JPA

EP5. 연관관계 매핑

예제로 연관관계 매핑 해보기

 

단방향 연관관계


 

시나리오

  • 회원과 팀이 있다.
  • 회원은 하나의 팀에만 소속될 수 있다.
  • 회원과 팀은 다대일 관계다.

객체지향 모델링

Member class 의 teamId는 멤버가 어느 팀에 속해있는지 알려주는 변수이다.

teamId는 테이블에서 FK에 해당된다.

그리고 Table 에서는 Member 와 TEAM 이 다대일 관계라는 것을 표현한다.

 

 

모델링코드

package hellojpa;

import javax.persistence.*;
import java.math.BigDecimal;
import java.util.Date;

@Entity
public class Member {

    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;

//    @Column(name = "TEAM_ID")
//    private Long teamId;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "TEAM_ID")
    private Team team;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Team getTeam() {
        return team;
    }

    public void setTeam(Team team) {
        this.team = team;
    }
}

Member class 에서

@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;

테이블 연관관계가 아닌 객체 연관관계를 표현해야 하기 때문에

private int teamId;

가 아닌

private Team team;

으로 객체 연관관계를 표현한다.

 

 

또한, @ManyToOne 어노테이션으로 Member 객체와 Team 객체가 다대일 관계라는 것을 표현해준다.

 

@JoinColumn(name = "TEAM_ID")로 Team 객체에 연관관계를 매핑한다.

 

 

그리고 실제 Hibernate 에서 join SQL이 나가는 것을 볼 수 있다.

 

 

 

 

양방향 연관관계


객체와 테이블의 양방향 연관관계 차이

테이블의 연관관계에서는 FK 하나로 된다.

하지만 객체에서는 참조이기때문에 값을 넣어줘야 한다.

 

  • 객체 : 양방향이 아니라 단방향 2개이다.
  • 테이블 : FK 하나로 양방향 연관관계를 표현한다.

mappedBy

  • 뜻 : (by : ~에 의해 mapped : 매핑됐다) -> 연관관계의 주인을 표현 
  • 딜레마 : Member 의 team 과 Team 의 members 중 어느것으로 Member 테이블의 TEAM_ID를 업데이트 해야하나?
  • 결론 : 둘중 하나로 외래키를 관리해야 한다. (룰) 연관관계의 주인은 FK가 있는 쪽이다.

 

주의점

  • 주인이 아닌쪽은 읽기만 가능.
  • 주인은 mappedBy 사용 불가

 

누구를 주인으로 ? : 외래 키가 있는 곳을 주인으로.

? : 비즈니스적인 이유가 있는것이 아니라 단지 Table에서 FK가 있는 곳을 따라가는 것이다. 그래야 모든것이 깔끔하게 해결됨

 

매핑코드

@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<>();

...

 

 

 

 

가장 많이하는 실수

 

1. 연관관계의 주인에 값을 입력하자.

team 에서 Members는 mappedBy 가 되어있다. 주인이 아니므로읽기만 가능하다.

Team team = new Team();
team.setName("TeamA");
em.persist(team);

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

//역방향(주인이 아닌 방향)만 연관관계 설정
team.getMembers().add(member);

em.persist(member);

결과

읽기만 가능하므로 member테이블에 team_id가 들어가있지 않다.

 

Team team = new Team();
team.setName("TeamA");
em.persist(team);

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

team.getMembers().add(member);
//연관관계의 주인에 값 설정
member.setTeam(team); //**

em.persist(member);

 

결과

 

 

2. 객체지향 설계를 위해 항상 양쪽에 값을 설정하자! 연관관계 편의 메소드를 구현하자! 둘 중 한곳에만!

 

주인이 아닌곳에 mappedBy를 걸면 테이블에 적용이 안된다고는 하지만, 사실 영속성컨텍스트에는 등록되어있기 때문에 자바코드상에서는 적용이 되어있다.

 

그래서 객체지향적인 설계를 위해서 양방향 메소드에서는 연관관계 편의 메소드를 구현하자!

 

addMember 메소드 구현

@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<>();

    public void addMember(Member member) {
        member.setTeam(this);
        members.add(member);
    }
    
    ...

 

 

3. 무한루프를 조심하자

(toString(), lombok, JSON 생성 라이브러리) -> 서로 계속 부름

 

Member

...
@Override
    public String toString() {
        return "Member{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", team=" + team +
                '}';
    }
...

 

Team

...
@Override
    public String toString() {
        return "Team{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", members=" + members +
                '}';
    }
...

 

Main

...
            Team team = new Team();
            team.setName("TeamA");
            em.persist(team);

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

            em.persist(member);

            team.addMember(member);

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

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

            System.out.println("==============");
            System.out.println("findTeam = " + findTeam);
            System.out.println("==============");
...

 

 

실행결과

무한루프를 돌아서 StackOverFlowError가 난다.

 

 

 

 

양방향 매핑 설계 꿀팁

  • 처음 설계할 때 단방향 매핑으로만 연관관계 매핑을 끝내라
  • 양방향 매핑은 개발 하다가 필요할 때 추가해라