본문 바로가기
Framework/🍃Spring

JPA - 연관관계의 매핑 [N:1다대일 | 1:N 일대다]

by 발개발자 2022. 12. 20.
반응형

JPA에서 다양한 연관관계의 매핑에 대해서 알아보자.

 

JPA에서는 객체와 객체의 사이를 다양한 관계로 매핑할 수 있다.

이때 고려해야할 사항이 세가지가 있다.

 

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

 

보통 위 세가지 사항을 고려해서 연관관계를 설정하여야 한다.

그렇다면 위의 고려사항을 생각해서 JPA로 만들 수 있는 연관관계의 종류에 대해 알아보자.

 

  다대일 [ N:1 ]

실무에서 가장 많이 쓰이는 연관관계로 N:1의 관계를 갖는다.

여기서 외래키는 테이블 기준으로 N쪽에 설정되어야 한다.

즉, N이 연관관계의 주인인 관계이다.

 

 

  다대일 단방향

Member는 N, Team은 1의 관계로 N:1 관계이다. 

DB에서 N쪽에 외래키를 걸듯이, 객체에서도 N쪽에 참조를 걸어야 1에게 찾아갈 수 있다.

그래서 N쪽에 참조를 걸어 단방향 매핑을 진행한다.

그렇다면 코드로 알아보자.

 

Member.class

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

 

Team.class

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

 

위의 코드와 같이 N인 Member쪽에서만 Team에 대해 @ManyToOne어노테이션을 통해 참조를 걸어 단방향 매핑을 진행할 수 있다. 위와 같은 형태가 다대일 단방향이다.

 

 

  다대일 양방향

위의 코드에서 양방향으로 바꾸려면 Team에서 Member객체를 List형태로 참조로 가지게 되면 양방향이 된다.

따라서 Team만 수정해주면 된다.

 

Team.class

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

 

Team에선 Member에 대해 일대다 관계이니 @OneToMany 어노테이션을 지정해주고, mappedBy를 통해 연관관계의 주인을 설정해준다. 당연히 외래키를 가지고 있는 N쪽이 연관관계의 주인이니, Team에서 Member의 외래키 필드명을 mappedBy로 지정해 연관관계의 주인을 설정해준다.

이렇게 하면 Team에서 Member, Member에서 Team으로 양방향으로 객체의 참조가 가능해진다.

 


 

  일대다 [ 1:N ]

일대다는 다대일의 관계에서 연관관계를 주인을 다(N)이 아닌 일(1)로 지정하는 것이다.

 

 

  일대다 단방향

 

 

이 모델은 JPA표준스펙에서 지원하지만, 실무에서는 대체로 권장하지 않는 편이다.

위의 관계도를 봐보자.

Team이 Member테이블의 FK를 관리하고 있다. 

DB에서는 FK가 Member에 있지만, 객체에선 Team이 주인으로 Member를 관리하기 때문에 발생하는 관계이다.

이렇게되면 Team에서 members의 값을 수정하면 Member라는 다른 테이블의 FK를 업데이트를 쳐줘야 한다.

 

이 구조를 왜 why 권장하지 않는지 코드로 알아보자.

 

Team.class

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

 

Member.class

@Entity
public class Member {
 
 @Id
 @Column(name = "MEMBER_ID")
 private String id;
 
 @Column(name = "USERNAME")
 private String username;
 
 
 // Getter
 // Setter
}

 

Main.class

...
tx.begin();
try {
    Member member = new Member();
    member.setUsername("member1");
    
    em.persist(member);
    
    Team team = new Team();
    team.setName("teamA");
    team.getMembers().add(member);
    
    em.persist(team);
    
    tx.commit();

} catch ...

 

Team에서 @OneToMany와 @JoinColumn을 통해 Team과 Member의 일대다 관계에서의 본인 주인으로 지정했다.

이 관계에서 Insert를하게 되면 Member가 저장되는 건 맞지만, team.getMembers.add로 Member테이블의 FK를 관리하기때문에 추가로 Update쿼리가 나갈 수 밖에 없다. 즉 Member가 Insert된후 Member의 FK가 Update쳐지는 구조이다.

 

정리하자면, 객체 본인 테이블에 FK가 있다면 Insert 쿼리 한번으로 끝나지만, 객체와 다른 테이블에 FK를 핸들링하고자하면 Insert이후  Update 쿼리가 나갈 수 밖에 없는 구조인 셈이다.

만일, 수십개의 테이블이 엮여 비즈니스로직을 처리하는 실무에서 이런 구조로 사용한다면 너무 복잡해져 사용할수가 없는 것이다.

 

그래서 실무에선 이를 해결하기 위해 다대일 단방향관계를 매핑하고 필요시 양방향을 추가하는 전략으로 보완해나간다.

 

정리하자면,

  • 일대다 단방향은 일대다(1:N)에서 일(1)이 연관관계의 주인이다.
  • 테이블 일대다 관계는 항상 다(N) 쪽에 외래키가 있다.
  • 객체와 테이블의 차이 때문에 반대편 테이블의 외래키를 관리하는 특이한 구조이다.
  • 연관관계 관리를 위해 추가로 Update 쿼리 실행한다.

 

 

 

  일대다 단방향

1:N 구조에서 양방향 매핑은 사실 존재하지 않는다.

양방향 매핑에서 FK는 N쪽에 있기 때문에 @OneToMany는 주인이 될 수 없기 때문이다.

따라서 이 구조 대신 다대일 양방향을 사용을 권장한다.

그렇다고 1:N구조의 양방향 매핑이 불가능한 것은 아니다. 강제로 읽기전용 필드를 사용해서 양방향 처럼 사용할 수 있다.

 

그렇다면 바로 코드로 알아보자.

 

Team.class

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

 

Member.class

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

Member에서 Team에 대한 다대일 단방향 관계를 추가해주었다.

그런데 Team에서도 같은 FK를 관리하기 때문에 Member에서 insertable과 updatable을 통해 읽기전용으로 지정하여 문제를 해결해 주었다.

 

이렇게 일대다 양방향까지 알아보았다.

 

다음은 포스팅에선 일대일 | 다대다에 대해 알아보자.

 

 

본 내용은 아래의 강의를 참고했습니다.

https://www.inflearn.com/course/ORM-JPA-Basic

반응형

댓글