[JPA] 다양한 연관관계 매핑

1

JPA

목록 보기
7/16
post-thumbnail

저번 글에 설명한 내용을 다시 정리해 보자
엔티티의 연관관계를 매핑할 때는 다음 3가지를 고려해야한다.

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

다중성

연관관계에는 다음과 같은 다중성이 있다.

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

보통 다대일과 일대다 관계를 가장 많이 사용하며, 다대다 관계는 거의 사용하지 않는다.


단방향, 양방향

테이블은 외래 키 하나로 조인을 사용해서 양방향으로 쿼리가 가능하다. 하지만 객체는 참조용 필드를 가지고 있는 객체만 연관된 객체를 조회할 수 있다. 객체 관계에서 한 쪽만 참조하는 것을 단방향 관계라고 한다. 그리고 객체에서 단방향 관계 2개가 있으면 양방향 관계라고 한다.


연관관계의 주인

데이터베이스는 외래 키 하나로 두 테이블이 연관관계를 맺는다. 따라서 테이블의 연관관계를 관리하는 것은 외래키 하나이다. 반면에 엔티티를 양방향 매핑하면 A → B, B → A 두 곳에서 서로를 참조한다. 따라서 객체에서는 관리하는 것이 2군데이다.

JPA는 두 객체 연관관계 중 하나를 정해서 데이터베이스 외래 키를 관리하는데 이것을 연관관계의 주인이라고 한다. 외래 키를 가진 테이블과 매핑한 엔티티가 외래키를 관리하는게 효율적이므로 보통 이곳을 연관관계의 주인으로 선택한다.


다중성과 단방향, 양방향등을 고려한 가능한 모든 연관관계를 하나씩 알아보자

  • 다대일 - 단방향, 양방향
  • 일대다 - 단방향, 양방향
  • 일대일 - 주 테이블 단방향, 양방향
  • 일대일 - 대상 테이블 단방향, 양방향
  • 다대다 - 단방향, 양방향

다대일

데이터베이스 테이블의 일(1), 다(N) 관계에서 외래 키는 항상 다쪽에 있다. 따라서 객체 양방향 관계에서 연관관계의 주인은 항상 다쪽이다.

구성원 엔티티, 팀 엔티티 코드를 통해 다대일 단방향 연관관계를 알아보자

구성원 엔티티

@Getter
@Setter
@Entity
public class Member{

	@Id
    @Column(name = "MEMBER_ID")
    private Long id;
    
    private String username;
    
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;

팀 엔티티

@Getter
@Setter
@Entity
public class Member{

	@Id
    @Column(name = "TEAM_ID")
    private Long id;
    
    private String name;

구성원은 Member.team으로 조회할 수 있지만 팀에서는 구성원을 참조하는 필드가 없기에 조회할 수 없다. 따라서 구성원과 팀은 다대일 단방향 연관관계이다.


다대일 양방향 [N:1], [1:N]

구성원 엔티티

@Getter
@Setter
@Entity
public class Member{

    @Id
    @Column(name = "MEMBER_ID")
    private Long id;
    
    private String username;
    
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
    
    public void setTeam(Team team){
    	this.team = team;
    	}
    }
    

회원 엔티티

@Getter
@Setter
@Entity
public class Member{

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

양방향은 외래 키가 있는 쪽이 연관관계의 주인이다.

일대다와 다대일 연관관계는 항상 다(N)에 외래 키가 있다. 여기서는 다(구성원)쪽 테이블이 외래 키를 가지고 있으므로 Member.team이 연관관계의 주인이 된다.

양방향 연관관계는 항상 서로를 참조해야 한다.

양방향 연관관계는 어느 한 쪽만 참조하면 양방향 연관관계가 성립되지 않는다. 항상 서로 참조하게 하려면 연관관계 편의 메소드를 사용하는 것이 좋은데, 구성원의 setTeam( ), 팀의 addMember( ) 메소드가 편의 메소드들이다.


일대다

일대다 관계는 다대일 관계의 반대 방향이다. 일대다 관계는 엔티티를 하나 이상 참조할 수 있으므로 자바 컬렉션인 Collection, List, Set, Map 중에 하나를 사용해야 한다.


일대다 단방향 [1:N]

하나의 팀은 여러 구성원을 참조할 수 있는데 이것을 일대다 관계라고 한다. 그리고 구성원이 팀을 참조하지 못해야 일대다 단방향이 성립한다.

일대다 단방향 관계는 약간 특이한데 이 매핑은 반대쪽 테이블에 있는 외래 키를 관리한다. 그럴 수 밖에 없는 것이 일대다 관계에서 외래 키는 항상 다쪽 테이블에 있기 때문이다.


팀 엔티티

@Getter
@Setter
@Entity
public class Team {

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

구성원 엔티티

@Getter
@Setter
@Entity
public class Member {

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

일대다 단방향 관계를 매핑할 때는 @JoinColumn을 명시해야 한다. 그렇지 않으면 JPA는 연결 테이블을 중간에 두고 연관관계를 관리하는 조인 테이블 전략을 기본으로 사용해서 매핑하기 때문이다.

일대다 단방향은 단점이 있는데 매핑한 객체가 관리하는 외래 키가 다른 테이블에 있다는 점이다.

본인 테이블에 외래 키가 있으면 엔티티의 저장과 연관관계 처리를 INSERT SQL 한 번으로 끝낼 수 있지만, 다른 테이블에 외래 키가 있으면 연관관계 처리를 위한 UPDATE SQL을 추가로 실행해야 하기 때문이다.

그래서 일대다 단방향보다는 다대일 양방향을 사용하는것이 더 좋다.


일대다 양방향 [1:N], [N:1]

일대다 양방향은 사실 없다. 대신 다대일 양방향을 사용해야 한다(일대다 양방향과 다대일 양방향은 서로 같다). 왜냐하면 양방향 매핑에서 @OneToMany는 연관관계의 주인이 될 수 없기 때문인데, 관계형 데이터베이스에서 일대다, 다대일 관계는 항상 "다"쪽에 항상 외래 키가 있기 때문이다. 그래서 @ManyToOne에는 mappedBy 속성이 없는 이유다.


일대일

  • 일대일 관계는 그 반대도 일대일 관계이다.
  • 주 테이블이나 대상 테이블 둘 중 어느 곳이나 외래 키를 가질 수 있다.

주 테이블에 외래 키

객체 지향 개발자들이 선호한다. 이 방법의 장점은 주 테이블이 외래 키를 가지고 있으므로 주 테이블만 확인해도 대상 테이블과 연관관계가 있는지 알 수 있다.

대상 테이블에 외래 키

전통적인 데이터베이스 개발자들은 보통 대상 테이블에 외래 키를 두는 것을 선호하는데 그 이유는 일대일 관계에서 일대다 관계로 변경할 때 테이블 구조를 그대로 유지할 수 있기 때문이다.

JPA는 주 테이블에 외래 키가 있으면 좀 더 편리하게 매핑할 수 있다.

회원은 하나의 사물함만 사용할 수 있고 사물함은 한 명의 회원에 의해서만 사용될 수 있다.


일대일 단방향 [1:1] - 주테이블에 외래 키

회원

@Getter
@Setter
@Entity
public class Member{

	@Id @GeneratedValue
    @Coulmn(name = "MEMBER_ID")
    private Long id;
    
    private String username;
    
    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;

사물함

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

일대일 양방향 [1:1] - 주테이블에 외래 키

양방향이므로 mappedBy속성을 이용해서 연관관계의 주인만 정해주면 된다.

회원

@Getter
@Setter
@Entity
public class Member{

    @Id @GeneratedValue
    @Coulmn(name = "MEMBER_ID")
    private Long id;
    
    private String username;
    
    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;

사물함

@Getter
@Setter
@Entity
public class Locker{
	
    @Id @GeneratedValue
    @Column(name = "LOCKER_ID")
    private Long id;
    
    private String name;
    
    @OneToOne(mappedBy = "locker")
    private Member member;

다대다

관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다. 그래서 보통 다대다 관계를 일대다, 다대일 관계로 풀어내는 연결 테이블을 사용한다.

반면에 객체는 테이블과 다르게 2개의 객체로 다대다 관계를 만들 수 있다. 코드를 통해 알아보자


다대다 단방향 [N:N]

회원


@Getter
@Setter
@Entity
public class Member{
	
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private String id;
    
    private String username;
    
    @ManyToMany
    @JoinTable(name = "MEMBER_PRODUCT", joinColumns = @JoinColumn(name = "MEMBER_ID"), inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID"))
    private List<Product> products = new ArrayList<Product>();

상품

@Getter
@Setter
@Entity
public class Product{
	
    @Id @GeneratedValue
    @Column(name = "PRODUCT_ID")
    private String id;
    
    private String name;

0개의 댓글