나를 기록하다
article thumbnail
반응형

연관관계 매핑 기초

  • 객체의 참조와 테이블의 외래 키를 매핑
  • 용어 이해
    • 방향(Direction): 단방향, 양방향
    • 다중성(Multiplicity): 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M)
    • 연관관계의 주인(Owner): 객체 양방향 연관관계는 관리 주인이 필요

1. 연관관계가 필요한 이유

  • 객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.
    • 테블은 외래 키로 조인을 사용해서 연관된 테이블을 찾는다.
    • 객체는 참조를 사용해서 연관된 객체를 찾는다.
    • 테이블과 객체 사이에는 이런 큰 간격이 있다.

연관관계가 필요한 이유

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

            Member member = new Member();
            member.setUsername("member1");
            member.setTeamId(team.getId());
            em.persist(member);

            Member findMember = em.find(Member.class, member.getId());

            Long findTeamId = findMember.getTeamId();
            Team findTeam = em.find(Team.class, findTeamId);

위와 같이 팀에 속한 멤버를 찾기 위해서는 findMember를 통해 멤버 클래스의 멤버의 id를 찾고,

findMember.getTeamId();를 통해 조회한 멤버의 teamId를 조회한다. 마지막으로 팀 클래스에서 이전에 조회한 teamId와 일치하는 팀을 조회한다.

2. 단방향 연관관계

객체 연관관계와 테이블 연관관계

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 // 다대일
    @JoinColumn(name = "TEAM_ID") // 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 입장에서 다대일 관계

 

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

            Member member = new Member();
            member.setUsername("member1");
            member.setTeamId(team.getId());
            em.persist(member);

            Member findMember = em.find(Member.class, member.getId());

            Long findTeamId = findMember.getTeamId();
            Team findTeam = em.find(Team.class, findTeamId);
Team team = new Team();
            team.setName("TeamA");
            em.persist(team);

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

            em.persist(member);

            Member findMember = em.find(Member.class, member.getId());

            Team findTeam = findMember.getTeam();
            System.out.println("findTeam = " + findTeam.getName());

연관관계 매핑 전(왼쪽)과 연관관계 매핑 후(오른쪽)의 차이. 단방향 연관관계를 통해 보다 객체지향적인 코드로 변환할 수 있다.

3. [⭐️⭐️⭐️]양방향 연관관계와 연관관계의 주인

양방향 연관관계의 주인

// 멤버 클래스
        @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

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

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

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
// 팀 클래스
        @Id
    @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>(); //
// JpaMain
                        Team team = new Team();
            team.setName("TeamA");
            em.persist(team);

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

            em.persist(member);

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

            Member findMember = em.find(Member.class, member.getId());

            List<Member> members = findMember.getTeam().getMembers();

            for (Member m : members) {
                System.out.println("m = " + m.getUsername());
            }

            tx.commit();

 

연관관계의 주인과 mappedBy

양방향 연관관계의 주인과 mappedBy

  • 객체와 테이블간에 연관관계를 맺는 차이를 이해해야 한다. (어려움 주의)

 

객체와 테이블이 관계를 맺는 차이

  • 객체 연관관계 = 2개
    • 회원 → 팀 연관관계 1개(단방향)
    • 팀 → 회원 연관관계 1개(단방향)
  • 테이블 연관관계 = 1개
    • 회원 ↔ 팀의 연관관계 1개(양방향)

객체의 양방향 연관관계

  • 객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개
  • 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
class A {
        B b;
}
class B {
        A a;
}
  • A → B(a.getB()), B → A(b.getA())

테이블의 양방향 연관관계

  • 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리
  • MEMBER.TEAM_ID 외래 키 하나로 양방향 연관관계 가짐(양쪽으로 조인할 수 있다)
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
  • 둘 중 하나로 외래 키를 관리해야 함.

테이블의 연관관계

 

연관관계의 주인

양방향 매핑 규칙

  1. 객체의 두 관계 중 하나를 연관관계의 주인으로 지정
  2. 연관관계의 주인만이 외래 키를 관리(등록, 수정)
  3. 주인이 아닌 쪽은 읽기만 가능
  4. 주인은 mappedBy 속성 사용 X
  5. 주인이 아니면 mappedBy 속성으로 주인 지정

누구를 주인으로 할 것인가

  • 외래 키가 있는 곳을 주인으로 정해라
  • 여기서는 Member.team이 연관관계의 주인

진짜 매핑 - 연관관계의 주인

예시)

자동차와 자동차의 바퀴가 있으면 연관관계의 주인은 자동차의 바퀴(다(many) 쪽)로 해야 한다.

 

양방향 연관관계 주의

  • 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자
  • 연관관계 편의 메소드를 생성하자→ member에 team을 세팅하는 시점에 team에도 getMembers().add(Member)로 멤버를 설정해줌으로써 양쪽에 값을 설정하게 됨.
    • setTeam이 아닌 changeTeam인 이유 단순한 getter and setter 관례에 의한 것이 아닌 중요한 기능을 담당하는 것으로 보이기 위함.
  • public void changeTeam(Team team) { this.team = team; team.getMembers().add(this); }
  • 연관관계 편의 메소드는 양쪽에 다 있으면 문제를 일으킬 수 있음 → 혹시나 다른 쪽으로 만들고자 한다면 기존의 연관관계 편의 메소드는 지울 것
public void addMember(Member member) {
        member.setTeam(this);
        members.add(member);
    }
Team team = new Team();
            team.setName("TeamA");
            em.persist(team);

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

            team.addMember(member);
  • 양방향 매핑 시에 무한 루프를 조심하자
    • ex) toString(), lombok, JSON 생성 라이브러리
    • 이유: 양방향에 걸려있으면 무한루프에 빠지게 된다. stackoverflow 또는 장애 발생.
    • 컨트롤러에는 엔티티를 절대 반환하지 마라. → 컨트롤러에서 엔티티 자체를 JSON으로 API 스펙에 반환해버리면 두가지 문제가 발생
      1. 무한 루프 발생
      2. 엔티티는 변경될 수 있는데 엔티티를 API에 반환해버리면 엔티티를 변경하는 순간 그 API의 스펙이 변경되어버림. 컨트롤러에서는 엔티티는 값만 존재하는 DTO로 변환하여 반환하는 것을 추천

양방향 매핑 정리

  1. 단방향 매핑만으로도 이미 연관관계 매핑은 완료
  2. 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
  3. JPQL에서 역방향으로 탐색할 일이 많음
  4. 단방향 매핑을 잘하고 양방향은 필요할 때 추가해도 됨(테이블에 영향을 주지 않음)

연관관계의 주인을 정하는 기준

  1. 비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안됨
  2. 연관관계의 주인은 외래 키의 위치를 기준으로 정해야함
반응형
profile

나를 기록하다

@prao

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...