객체 지향 프로그래밍에서의 객체와 관계형 데이터베이스의 테이블간의 차이를 해결하기 위한 연관관계 매핑은 JPA에서 정말 중요한 부분이다.

연관관계 매핑에 관한 용어들 먼저 정리하면 다음과 같다.

  • 방향(Direction): 단방향, 양방향
  • 다중성(Multiplicity): 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:N)
  • 연관관계의 주인(Owner): 객체 양방향 연관관계에서의 관리 주인

여기서는 다중성을 중심으로 연관관계 매핑 방법을 설명하려 한다.

 

 

제일 먼저 다대일 연관관계가 있다.

다대일 연관관계는 가장많이 사용하는 연관관계이고 그래서 제일 중요한 연관관계이다.

다대일 단방향 연관관계를 다이어그램으로 표현하면 다음과 같다.

이러한 연관관계를 표현하기 위한 코드는 다음과 같다.

@Entity
public class Member {
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
    
    private String username;
}

@Entity
public class Team{
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    
    private String name;
}

 

여기서 Team 객체를 통해서도 Member와의 관계를 알고 싶으면 양방향 연관관계를 걸어주면 된다.

다대일 양방향 연관관계를 나타내는 다이어그램과 코드는 다음과 같다.

@Entity
public class Member {
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
    
    private String username;
}

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

외래키가 있는 쪽이 연관관계의 주인이므로 Member가 연관관계의 주인이 된다.

코드를 보면 다대일 단방향 연관관계에서 Team에 @OneToMany로 member를 노드로 하는 List를 추가하기만 하면 되는것을 알 수 있다.

여기서 @OneToMany에 mappedBy로 연관관계의 주인인 Member의 team를 지정해 주면 된다.

 

 

다음으로 일대다 연관관계가 있다.

일대다는 일과 다 중에 일이 연관관계의 주인일때를 말한다. 하지만 테이블에서는 일대다 관계에서 항상 다 쪽에 외래키가 있다.

즉 일대다 연관관계에서는 연관관계의 주인이 반대편 테이블의 외래키를 관리하는 특이한 구조를 갖게 된다.

이때, 주의할 점은 @OneToMany를 사용할때 @JoinColumn을 꼭 사용해야 한다는 것이다. 그렇지 않으면 조인 테이블 방식이 디폴트로 적용되는데 이것은 중간에 테이블을 하나 추가하는 방식으로써 의도치 않은 테이블의 생성으로 인한 테이블 관리의 불편함이 생길 수 있다.

일대다 단방향 연관관계를 나타내는 다이어그램과 코드는 다음과 같다.

@Entity
public class Member {
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    
    private String username;
}

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

일대다 단방향 매핑은 엔티티가 관리하는 외래키가 다른 테이블에 있어서 jpa를 통해 테이블을 다루는거 자체가 쉽지않다.

예를들어 team.setMembers같은 메서드를 쓸때 TEAM 테이블이 아닌 MEMBER 테이블에 update쿼리가 나가는등 코드의 일부분만 봐서는 쿼리 예측이 쉽지 않게 된다.

따라서 대다수의 경우에 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하는게 바람직하다.

 

일대다 양방향 매핑은 공식적으로 존재하지 않지만 @JoinColumn(insertable = false, updatable = false)를 이용하여 일대다 양방향 매핑을 구현할 수 있다.

일대다 양방향 매핑을 나타내는 다이어그램과 코드는 다음과 같다.

@Entity
public class Member {
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
    private Team team;
    
    private String username;
}

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

하지만 일대다 양방향 매핑 또한 대부분의 경우 다대일 양방향을 사용하여 해결할 수 있으므로 관리가 비교적 어려운 일대다 매핑보다는

구현이 용이하고 관리가 직관적인 다대일 양방향 매핑을 사용하는것이 바람직하다.

 

 

다음으로 일대일 연관관계가 있다.

일대일 연관관계에서는 주 테이블이나 대상 테이블 중에 외래키를 어디다 둘지 선택 가능하다.

예를들어 한명(MEMBER)당 최대 하나의 사물함(LOCKER)을 배정 받을 수 있다 했을때 MEMBER 테이블을 주 테이블, LOCKER테이블을 대상 테이블이라고 하자.

주 테이블에 외래키가 있는 단방향 매핑을 다이어그램과 코드로 보이면 다음과 같다.

@Entity
public class Member {
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    
    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;
    
    private String username;
}

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

보다시피 다대일 단방향 매핑과 매우 유사한것을 알 수 있다. 때문에 여기서 양방향 매핑을 거는것 또한 다대일 양방향 매핑과 매우 유사하다.

일대일 연관관계에서 주 테이블에 외래키가 있는경우 양방향 매핑에 대한 다이어그램과 코드는 다음과 같다.

@Entity
public class Member {
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    
    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;
    
    private String username;
}

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

지금까지는 일대일 연관관계중에서도 주 테이블에 외래키가 있는 경우를 살펴보았다.

다음으로 대상  테이블에 외래키가 있는경우에 대해 말하고자 한다.

먼저 대상 테이블(LOCKER)에 외래키가 있는 경우 Member(Member.locker)를 연관관계의 주인으로 일대일 단방향 매핑을 하는 방법은 없다.

대상 테이블(LOCKER)에 외래키가 있는 경우 양방향 매핑은 가능하다.

단, 연관관계의 주인 또한 LOCKER(Locker.member)로 설정 해야하고, 따라서 Member는 읽기만 가능하다.

 

일대일 관계에서 주 테이블에 외래키가 있는경우 다음과 같은 특징이 있다.

  1. 주 객체가 대상 객체의 참조를 가지는것 처럼 주 테이블에 외래키를 두고 대상 테이블을 찾는다.
  2. JPA 매핑이 편리하고 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능하므로 객체지향 개발자가 선호한다.
  3. 단점으로는 값이 없으면 외래키에 null값을 허용해야 한다.

일대일 관계에서 대상  테이블에 외래키가 있는경우 다음과 같은 특징이 있다.

  1. 대상 테이블에 외래키가 존재한다.
  2. 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변형할 때 테이블 구조를 유지할수 있어 전통적인 데이터베이스 개발자가 선호한다.
  3. 단점으로는 프록시 기능의 한계로 지연로딩으로 설정해줘도 즉시로딩이 불가피하다.

 

 

마지막으로 다대다 연관관계가 있다.

JPA에서 @ManyToMany를 사용한 다대다 매핑을 지원하지만 해당 관계에관한 다른 속성값을 추가할 수 없는 문제가 있다.

따라서 다대다 연관관계는 연결 테이블용 엔티티를 추가해서 다대일 일대다 관계로 풀어서 구현하는게 일반적이다.

예를 들어 고객(Member)과 상품(Product)이 다대다 관계라고 했을때, 고객과 상품사이에 MemberProduct라는 엔티티를 추가하여 각각 일대다 다대일 매핑을한다.

다대다 관계를 일대다 다대일 관계로 풀어서 매핑한 다이어그램과 코드는 다음과 같다.

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;
    
    @OneToMany(mappedBy = "member")
    private List<MemberProduct> memberProduct = new ArrayList<>();
    
    private String username;
}

@Entity
public class Product{
    @Id @GeneratedValue
    private Long id;
    
    @OneToMany(mappedBy = "product")
    private List<MemberProduct> memberProduct = new ArrayList<>();
    
    private String name;
}

@Entity
public class MemberProduct {
    @Id @GeneratedValue
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;
    
    @ManyToOne
    @JoinColumn(name = "PRODUCT_ID")
    private Product product;
    
    private int orderAmount;
    private int orderDate
}

 

 

참고: 자바 ORM 표준 JPA 프로그래밍 (https://www.inflearn.com/course/ORM-JPA-Basic#)

+ Recent posts