[JPA] 연관관계 매핑

Harry park·2022년 6월 21일
0

JPA

목록 보기
5/8
post-thumbnail

연관관계 매핑


연관관계 매핑 기초

객체는 참조를 사용하여 연관관계를 맺고 테이블은 외래 키를 사용하여 연관관계를 맺는다.
관계가 있는 다른 데이터를 참조한다는 점에서는 동일하지만 방향연관관계의 주인이라는 특징에서 참조외래 키는 다른 특징을 갖는다.

📖 연관관계가 필요한 이유


위과 같은 객체 모델링이 있을 때, 연관관계가 없는 경우의 코드는 다음과 같다.

Member Entity

@Entity
@Getter @Setter
public class Member {
	
    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    
    @Column(name = "user_name") 
  	private String name;
    
    @Column(name = "team_id)
    private Long teamId;
    
}

Team Entity

@Entity 
@Getter @Setter
public class Team {
	@Id @GeneratedValue
    @Column(name = "team_id")
    private Long id;
	    
    private String name;

}

생성매서드

// 생략

//==생성 메서드==//
public void createMember(Member member) {
	Team team = new Team();
    team.setName(member.getName());
    em.persist(team);
    
    Member member = new Member();
    member.setName(member.getName());
    member.setTeamId(team.getId());
    em.persist(member);
}

이 상황에서 해당 입력된 구성원의 소속 팀을 찾기 위해서는 Member객체의 member를 찾고
해당 Team 객체에서 id를 조회하여야 하는데, 이는 객체지향스럽지 않은 자원이 상대적으로 많이 사용되는 코드이다.

Long findTeamID = em.find(Member.class, member.getId()).getId();
Team findTeam = em.find(Team.class, findTeamId);

연관관계 정의 규칙

방향

방향에는 단방향양방향이 있다.
테이블에는 외래 키(FK)를 통한 조인(join)으로 양방향 쿼리가 가능하여, DB에는 방향의 개념이 없다.

SELECT * FROM MEMBER M JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID;
SELECT * FROM TEAM T JOIN MEMBER M ON M.TEAM_ID = T.TEAM_ID;

하지만 객체의 경우에는 참조용 필드를 가지고 있는 객체만 연관된 객체를 조회할 수 있기에, 방향이라는 개념이 존재한다.

  • 단방향 : 2개의 객체 중 한 쪽만 참조하는 관계
    👉 [회원 → 팀] 또는 [팀 → 회원]
  • 양방향 : 2개의 객체가 서로를 참조하는 관게
    👉 [회원 → 팀] 그리고 [팀 → 회원]

연관관계의 주인

2개의 객체가 양방향관계를 맺을 때는, 연관관계의 주인을 지정해야 한다.
보통, 외래 키를 가진 테이블과 매핑되는 엔티티가 외래 키를 가지는 것이 효율적이므로, 보통은 외래키를 가진 엔티티를 연관관계의 주인으로 지정한다.

🚨 외래 키를 관리하는 연관관계의 주인만이 외래 키를 변경할 수 있으며, 주인이 아닌 곳은 읽기만 가능다.

다중성

  • 다대일 (N:1)
  • 일대다 (1:N)
  • 일대일 (1:1)
  • 다대다 (N:M)

연관관계(다중성, 방향, 연관관계)

대일[N:1] : @ManyToOne

  1. 다대일 - 단방향
@Entity
public class Member {
	@Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;
    ...
}

@Entity
public class Team {
	@Id @GeneratedValue
    @Column(name = "team_id");
    private Long id;
}
  1. 다대일 - 양방향
@Entity
public class Member {
	@Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;
    ...
}

@Entity
public class Team {
	@Id @GeneratedValue
    @Column(name = "team_id");
    private Long id;
    
    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();
}

@OneToMany(mappedBy = "team")를 추가하여 연관관계의 주인이 Member.team임을 지정해준다.

대다[N:1] : @OneToMany

  1. 일대다 - 단방향
@Entity
public class Member {
	@Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    
    ...
}

@Entity
public class Team {
	@Id @GeneratedValue
    @Column(name = "team_id");
    private Long id;
    
    @OneToMany
    @JoinColumn(name = "team_id")
    private List<Member> members = new ArrayList<>();
}

🚨 문제 !!
일대다 단방향 매핑은 매핑한 객체가 관리하는 외래 키가 다른 테이블에 있다.
때문에, INSERT SQL 외에 UPDATE SQL이 추가로 발생하게 된다.
즉, MemberMember에 매핑되어 문제가 되지 않으나 TeamFK를 저장할 방법이 없기 때문에 조인 및 UPDATE SQL을 날려야한다.

  • Member를 수정했는데 Team이 수정이 됨.
    따라서, 일대다(1:N) 단방향 연관 관계 매핑이 필요한 경우는 그냥 다대일(N:1) 양방향 연관 관계를 매핑해버리는게 추후에 유지보수에 훨씬 수월하기 때문에 이 방식을 채택하는 것이 좋다.
  1. 일대다 - 양방향
    일대다 양방향 매핑은 존재하지 않는다.(🚨 실무 사용 금지 ❌)
    @JoinColumn(updatable = false, insertable = false) 키워드만 추가 된다.
@Entity
public class Member {
	@Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    
    @ManyToOne(@JoinColumn(updatable = false, insertable = false)
    ...
}

🚨 일대다 단방향과 동일한 문제를 가지므로 다대일 양방향 매핑을 사용하는 것을 권장함.

대일[1:1] : @OneToOne

  1. 일대일 단방향(주테이블에 FK)
@Entity
@Getter
public class Post {
    @Id @GeneratedValue
    @Column(name = "post_id")
    private Long id;

    @Column(name = "title")
    private String title;
    @OneToOne
    @JoinColumn(name = "attach_id")
    private Attach attach;
}
@Entity
@Getter
public class Attach {
    @Id @GeneratedValue
    @Column(name = "attach_id")
    private Long id;
    private String name;
}
  1. 일대일 단방향(대상 테이블에 FK)
    지원 안함 ❌

  2. 일대일 양방향(대상 테이블에 FK)
    일대일 양방향은 주 테이블과 대상 테이블의 위치만 바뀐 것으로 위와 동일하게 처리하면 된다.

📢 여기서 잠깐! "외래 키를 어디서 관리하는게 좋을까는 고민이 필요!!"

테이블은 한 번 생성되면 변경되기 어렵지만, 비즈니스는 쉽게 바뀔 수 있다.
위의 예에서 1개의 글에는 1개의 첨부만 허용 했으나 1개의 글에 복수의 첨부파일을 허용하는 경우
이 때는 Attach에 외래 키를 관리하는 것이 변경에 유연해진다.
하지만, 주 테이블(Post)에 외래 키를 관리하는 경우에는 성능상 이점이 생긴다.

대다[M:N] : @ManyToMany

🚨 실무에서 사용하지 않는 것을 권장함.(실무 사용 금지 )
사용해야 한다면, @OneToMany - @ManyToOne으로 풀어서 사용할 것!!

  • 중간 테이블이 숨겨져 있기 때문에 자기도 모르는 복잡한 조인의 쿼리(Query)가 발생할 수 있다.
  • 유지보수성 면에서도 떨어진다.

📢 @OneToMany - @ManyToOne으로 풀어서 사용할 것


참고 사이트

tstory - 정아마추어 코딩블로그
tstory - ShinD님 블로그


개인적으로 공부하며 기록한 내용으로, 틀린 내용이 있는 경우 덧글을 달아주시면 감사하겠습니다. 😍

profile
Jr. Backend Engineer

0개의 댓글