뭐라도 끄적이는 BLOG

JPA 06.01 - 다양한 연관관계 매핑 [ N : 1, 1 : N ] 본문

Java/JPA

JPA 06.01 - 다양한 연관관계 매핑 [ N : 1, 1 : N ]

Drawhale 2021. 4. 17. 15:49

연관관계 매핑시 고려사항 3가지

  • 다중성
  • 단방향 양방향
  • 연관관계의 주인

다중성

  • 다대일 (@ManyToOne)
  • 일대다 (@OneToMany)
  • 일대일 (@OneToOne)
  • 다대다 (@ManyToMany)

JPA 어노테이션은 DB와 매핑하기 위해 있습니다. 그래서 데이터베이스의 관점으로 기준을 고민하면 됩니다. 다중성을 판단하기 어려울 때는 반대방향에선 어떤 다중성을 가지는지 생각해 보면 됩니다.

대부분 다대일을 많이 사용하게 되며 일대다 일대일이 나오기도 합니다. 하지만 다대다는 정말 나오면 안되는 다중성입니다. 가능한 다른 방법으로 대체해 주는것이 좋습니다.

 

이번단원은 이전단원에서 이미 다루었던 내용이 나옵니다. 이전장에서 사용했던 연관관계를 정리하는 단원이라고 생각하면 됩니다.

다대일

데이터베이스 테이블의 [ 1 : N ]관계에서 외래키는 항상 다쪽에 있습니다. 따라서 객체 양방향 관계에서 연관관계의 주인은 항상 다쪽입니다.

단방향

DB 입장에서 TEAM이 1이고 MEMBER가 N입니다.

@Entity
@Getter
@Setter
@Table(name ="MEMBER")
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;
    
    private String username;
    
    @ManyToOne
    @JoinColumn(name = "TEAM_ID)
    private Team team;
}
@Entity
@Getter
@Setter
@Table(name ="TEAM")
public class Team {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "TEAM_ID")
    private Long id;
    
    private String name;
}

회원은 Member.team으로 팀 엔티티를 참조할 수 있지만 반대로 팀에는 회원을 참조하는 필드가 없습니다. 따라서 단방향 연관관계가 됩니다.

@JoinColum(name = "TEAM_ID")를 사용해서 Member.team 필드를 TEAM_ID외래키와 매핑했습니다. 따라서 Member.team 필드로 회원 테이블의 TEAM_ID외래키를 관리합니다.

양방향

@Entity
@Getter
@Setter
@Table(name ="MEMBER")
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;
    
    private String username;
    
    @ManyToOne
    @JoinColumn(name = "TEAM_ID)
    private Team team;
}
@Entity
@Getter
@Setter
@Table(name ="TEAM")
public class Team {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "TEAM_ID")
    private Long id;
    
    private String name;
    
    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();
}

단방향과 비교하자면 Team class에서 members가 추가되었습니다. 추가된 부분은 테이블을 변화시키지 않고 단순히 읽기만 되기 때문에 추가만 하면 문제가 없습니다. mappedBy는 연관관계의 주인인 Member의 team필드를 가리켜 주어야 합니다. team이라는 것으로 매핑이 되어진 것이다 라는 의미로 해석하면 좋습니다.

 

다대일 단방향은 가장 많이 사용하는 연관관계입니다. 그리고 다대일 관계의 반대 방향은 항상 일대다 관계이고 일대다 관계의 반대 방향은 항상 다대일관계입니다.

 

일대다

다대일에서는 연관관계의 주인이 다였습니다. 대부분의 경우 다(N)쪽이 연관관계의 주인이 되지만 가끔 일이 연관관계의 주인이 되는 경우가 있습니다. 하지만 이 모델은 권장하지 않습니다.

단방향

DB에는 무조건 다쪽에 외래키가 들어가야 합니다.(만일 TEAM에 외래키가 있게된다면 중복된TEAM을 만들수 밖에 없습니다.) 그래서 Team.members를 업데이트 해주면 TEAM과 다른 MEMBER의 외래키를 관리하는 특이한 구조가 생성됩니다. 이때 @JoinColumn을 반드시 사용해야 조인 테이블 방식을 사용합니다. (사용하지 않으면 JoinTable정책을 사용하여 중간에 테이블을 하나 추가함)

@Entity
@Getter
@Setter
@Table(name ="MEMBER")
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;
    
    private String username;
}
@Entity
@Getter
@Setter
@Table(name ="TEAM")
public class Team {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "TEAM_ID")
    private Long id;
    
    private String name;
    
    @OneToMany
    @JoinColumn(name = "TEAM_ID")
    private List<Member> members = new ArrayList<>();
}

코드가 조금 어색하지만 이렇게하면 원하는 일대다 연관관계가 된것입니다.

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

em.persist(member);

Team team = new Team();
team.setName("teamA");
team.getMembers().add(member);

실행을 해보면 team.getMembers().add(member);에서 Member의 update쿼리가 생성됩니다. 일대다관계의 문제는 다대일 관계에서보다 update쿼리가 한번더 나간다는 것도 있지만 정말 심각한 문제는 나중에 코드를 리딩하는데서 문제가 발생합니다. 코드상에서 team을 변경하여 TEAM 테이블에만 영향이 갈줄 알았는데 생각지도 못하게 MEMBER테이블을 건드려 버리게 된것입니다. 이런 상황은 복잡성때문에 운영이 힘들어질 수밖에 없습니다. 이런부분에서 그냥 다대일 연관관계에서 양방향을 해버리는게 더 알기 쉽습니다. 다대일은 Member에서 team필드를 만들수 밖에 없지만 일대다보다는 유지보수 하기 쉬울것입니다.

양방향

일대다의 양방향은 JPA에서 공식적으로 지원해 주는것이 아니라 비슷하게 만들어 보는 것입니다.

@Entity
@Getter
@Setter
@Table(name ="MEMBER")
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;
    
    private String username;
    
    @ManyToOne
    @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
    private Team team;
}
@Entity
@Getter
@Setter
@Table(name ="TEAM")
public class Team {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "TEAM_ID")
    private Long id;
    
    private String name;
    
    @OneToMany
    @JoinColumn(name = "TEAM_ID")
    private List<Member> members = new ArrayList<>();
}

일대다에서 양방향을 하기위해서 Member에도 @JoinColumn을 주고 insetable과 updatable을 모두 false로 두어야 합니다. 이렇게 두면 Member는 단지 읽기만 가능하게 됩니다. 결과적으로 Team의 members가 연관관계의 주인으로 남아있고 Member에서 읽기만 가능하도록 Team에 접근할 수 있습니다.

사실 이렇게 복잡하게 만드는것보다 다대일 양방향을 사용하는것이 가장 간단하고 알기 쉽습니다.


※ 참고 자료

 

 

자바 ORM 표준 JPA 프로그래밍 - 교보문고

자바 ORM 표준 JPA는 SQL 작성 없이 객체를 데이터베이스에 직접 저장할 수 있게 도와주고, 객체와 관계형 데이터베이스의 차이도 중간에서 해결해준다. 이 책은 JPA 기초 이론과 핵심 원리, 그리고

www.kyobobook.co.kr

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 본 강의는 자바 백엔

www.inflearn.com

 

Documentation - 5.4 - Hibernate ORM

Idiomatic persistence for Java and relational databases.

hibernate.org

 

반응형