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

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

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

여기서는 방향(단방향, 양방향)에 따른 연관관계 매핑 방법을 설명하려 한다.

 

회원과 팀이 있고 회원은 하나의 팀에만 소속될 수 있다고 하자. 즉, 회원과 팀은 다대일 관계이다.

먼저 객체를 테이블에 맞춰 모델링 하면 다음과 같다.

위 그림에서 MEMBER테이블에는 TEAM_ID라는 외래 키가 있고, Member객체에는 teamId라는 필드가 있다. 이를 코드로 나타내면 다음과 같다.

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

이러한 엔티티를 저장하고 조회하는 방법은 다음과 같다.

//팀 저장
 Team team = new Team();
 team.setName("TeamA");
 em.persist(team);
 
 //회원 저장
 Member member = new Member();
 member.setName("member1");
 member.setTeamId(team.getId());
 em.persist(member);
 
//Member 조회
 Member findMember = em.find(Member.class, member.getId()); 
 
 //연관관계가 없음
 Team findTeam = em.find(Team.class, team.getId());

테이블은 외래 키를 통해 조인연산을 하여 연관된 테이블을 찾는 반면, 객체는 참조를 사용해서 연관된 객체를 찾는다.

이처럼 관계형 데이터베이스의 테이블과 객체 사이에는 큰 간격이 있다.

 

 

우리는 연관관계 매핑을 통해 테이블과 객체 사이의 간격을 매울수 있다.

이제 연관관계를 사용한 객체 지향 모델링을 살펴보자.

위 그림처럼 테이블 연관관계에서는 MEMBER테이블에 TEAM_ID라는 외래키가 있고,

객체 연관관계에서는 Member객체에 Team객체를 참조하는 team필드가 있음을 알 수 있다.

이를 코드로 표현하면 다음과 같다.

@Entity
@Getter @Setter
public class Member { 

  @Id @GeneratedValue
  private Long id;
  
  @Column(name = "USERNAME")
  private String name;
  private int age;
  
  @ManyToOne
  @JoinColumn(name = "TEAM_ID")
  private Team team;
 }
 
 @Entity
 @Getter @Setter
 public class Team {
 
   @Id @GeneratedValue
   private Long id;
   
   private String name; 
 }

여기서 @ManyToOne 어노테이션은 다대일(Member객체 입장에서 Team과의 관계) 매핑정보임을 나타낸다.

@JoinColumn은 외래키를 매핑 할 때 사용되는데, name은 매핑할 외래키의 이름을 지정한다.

 

이런식으로 엔티티를 설계한 경우 저장, 조회, 수정하는 코드는 다음과 같다.

// 팀 저장
 Team team = new Team();
 team.setName("TeamA");
 em.persist(team);
 
 //회원 저장
 Member member = new Member();
 member.setName("member1");
 member.setTeam(team); //단방향 연관관계 설정, 참조 저장
 em.persist(member);
 
 //조회
 Member findMember = em.find(Member.class, member.getId());
//참조를 사용해서 연관관계 조회
 Team findTeam = findMember.getTeam();
 
 // 새로운 팀B
 Team teamB = new Team();
 teamB.setName("TeamB");
 em.persist(teamB);
 // 회원1에 새로운 팀B 설정
 member.setTeam(teamB);

 

이 시점에서 우리는 방향(단방향, 양방향)에 대해 얘기할 수 있다.

관계형 데이터베이스의 경우 외래키만으로 두 테이블의 관계를 나타낼 수 있다. 하지만 우리는 앞서 말했듯이 연관관계를 이용한 객체 지향 모델링을 하고자 한다.

이 경우 두 엔티티 사이의 관계를 외래키(관계된 상대 테이블의 id값)가 아닌 상대 객체 자체를 참조하여 관계를 나타낸다.

이때 위에 엔티티를 정의한 코드에서처럼 Member 엔티티에만 Team객체를 참조하게 되면 단방향 연관관계라 하고

Team 엔티티에서는 자체적으로 자신에게 속한 Member들이 어떤것들이 있는지 알 수 없게 된다.

즉, 단방향 연관관계만으로는 team.getMembers()같은 코드를 구현할 수 없게 된다.

이를 해결하기 위해 등장한게 양방향 연관관계이다. 양방향 연관관계를 표현한 객체와 테이블 사이의 관계는 다음과 같다.

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

단방향 연관관계와는 다르게 Team 엔티티에 List형태의 members필드가 추가된것을 확인할 수 있다.

이 필드를 통해 이제 Team에서도 어떤 Member들이 속해 있는지를 알 수 있게 된다.

하지만 양방향 연관관계에서는 주의해야할 점이 있다. 바로 연관관계의 주인을 정해야 하는것이다.

만약 Member 객체의 team필드를 수정하였지만 해당관계를 동일하게 나타내는 Team 객체의 members필드는 수정하지 않았다고 가정해보자.

이때 jpa는 수정을 반영해야할지 말아야할지 결정을 내려야만 한다. 때문에 연관관계의 주인이라는 개념이 필요하고

jpa는 연관관계의 주인인 쪽의 값을 반영하여 테이블을 관리하게 된다.

다르게 말하면 주인이 아닌쪽은 수정이 받아들여지지 않고 오직 읽기만 가능하다.

예를들어 Member가 Member와 Team사이 관계의 연관관계 주인이라면 team.getMembers().add(member)같은 코드는 데이터베이스에 반영되지 않는다.

 

연관관계의 주인을 정하는 방법은 간단하다. 외래 키가 있는 곳을 주인으로 정하면 된다.

위를 예시로 들면, Member와 Team사이의 관계에서 Member(Member.team)를 연관관계의 주인으로 하면 된다.

연관관계의 주인을 정했으면, 이제 연관관계의 주인만이 외래 키를 관리(등록, 수정)해야 한다.

주인이 아닌쪽은 읽기만 가능하며, mappedBy 속성으로 자신의 주인을 지정해 주면 된다.

 

양방향 연관관계에서 주의할 점은 연관관계를 나타내는 참조 값(Member의 team필드 또는 Team의 members필드)을 수정할때 양쪽 모두 수정해야 한다는 것이다.

그렇지 않으면 데이터베이스에서 받아오는 정보와 1차캐시에 있는 정보의 불일치가 일어나는등의 문제가 생길 수 있다.

 

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

+ Recent posts