본문 바로가기
JPA/JPA

[JPA] 다양한 연관관계 매핑 - 다대일, 일대다

by yoon_seon 2023. 4. 14.

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

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

다중성

연관 관계는 대칭성을 갖는다.

  • 다대일 ↔ 일대다
  • 일대일 ↔ 일대일
  • 다대다 ↔ 다대다

다중성 어노테이션

  • 다대일 : @ManyToOne
  • 일대다 : @OneToMany
  • 일대일 : @OneToOne
  • 다대다 : @ManyToMany

 

 

단방향, 양방향

  • 테이블
    테이블은 외래 키 하나로 양쪽을 조인할 수 있다. 그렇기 때문에 사실 방향이라는 개념이 없다.

  • 객체
    객체는 참조용 필드가 있는 쪽으로만 참조가 가능하다.

    한쪽만 참조하면 단방향, 양쪽이 서로 참조하면 양방향이라 한다.
    사실 객체에는 양방향이 없고 양쪽이 각각 단방향으로 서로를 바라보는 것이다. 이것을 우리는 양방향이라고 부른다.

 

연관관계의 주인

  • 테이블은 외래키 하나로 두 테이블이 연관관계를 맺는다.
  • 객체 양방향 관계는 A → B 참조, B → A 참조 해야하는 것처럼 참조가 두 개다.
    그러므로 둘 중 테이블의 외래키를 관리할 곳을 지정해야 한다.
  • 외래키를 관리하는 참조가 연관관계의 주인이다.
  • 주인의 반대편은 외래키에 영향을 주지 않는다. 단순 조회(읽기)만 가능하다.

 

다대일[N:1]

다대일 단방향

여러 회원들은 하나의 팀을 참조하므로 다대일 관계이다. 반대로 팀은 회원을 참조하지 않으므로 단방향 관계이다.

이러한 관계를 다대일 단방향 관계라고 한다.

외래키가 있는 곳에 다 쪽에 참조를 걸고 연관관계를 매핑한다. 이것이 다대일 단방향 관계이다.(가장 많이 쓰인다.)

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

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

다대일 단방향에서는 다 쪽인 Member에 @ManyToOne을 추가하고 @JoinColumn을 사용하여 외래키만 매핑해 주었다.

 

 

다대일 양방향

만약 Team 객체에서도 Member를 조회하고 싶다면 Team에서도 Member를 참조하면 된다.

Team에서 Member를 참조해도 연관관계의 주인인 Member가 외래키를 관리하고 있기 때문에 테이블에 전혀 영향을 주지 않는다.

일(1) 쪽에서도 연관관계의 주인인 다 쪽을 참조하도록 연관관계를 매핑하였다. 이제 양쪽에서 서로를 참조한다.

이것이 다대일 양방향 관계이다.

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

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

Team에 @OneToMany을 추가하고 mappedBy를 사용하여 어떤 정보에 의해 매핑이 되었는지 설정해 주었다.(연관관계의 주인객체에서 참조되는 변수명을 지정)

 

일대다 [1:N]

일대다 단방향

하나의 팀은 여러 회원들을 참조하므로 일대다 관계이다. 반대로 회원은 팀을 참조하지 않으므로 단방향 관계이다.

이러한 관계를 일대다 단방향 관계라고 한다.

일대다 단방향은 연관관계의 주인이 일(1) 쪽에 둔 것이다.

테이블의 일대다 관계는 항상 다(N) 쪽에 외래키가 있지만 객체와 테이블의 차이 때문에 반대편 테이블의 외래키를 관리한다.

그렇기 때문에 객체에서는 일(1) 쪽에서  다(N) 쪽 객체를 조작(생성, 수정, 삭제)하게 된다.

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

@Entity
@Getter @Setter
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에서는 일(1) 쪽인 Team의 어떠한 정보도 없게 하고

반대로 Team에서는 @OneToMany을 추가하고 @JoinColumn을 사용하여 Member를 참조하게 해 주었다.

* @JoinColumn는 필수이다. 그렇지 않으면 조인 테이블 방식을 사용하여 중간에 테이블 하나를 추가한다.

 

다대일 단방향에서는 다 쪽인 Member에 @ManyToOne을 추가하고 @JoinColumn을 사용하여 외래키만 매핑해 주었다.

 

Member member = new Member();
member.setUsername("memberA");
em.persist(member);

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

em.persist(team);

Member를 persist() 후 Team에 해당 Member 정보를 추가하여 실행하였다.

 

Team과 Member의 INSERT 쿼리가 생성된다. 하지만 INSERT 후 Member를 UPDATE 하는 쿼리도 생성된다.

왜??

 team.getMembers().add(member); 때문이다.

Team 엔티티를 저장하는데 Member 테이블의 TEAM_ID를 업데이트할 방법이다 없다. 그렇기 때문에 Member 테이블을 UPDATE 쿼리가 생성된 것이다.

이 방법은 어쩔 수 없이 UPDATE 쿼리가 한번 더 생성되어야 한다.

 

UPDATE 쿼리가 한번 더 생성되어도 성능상 크게 문제는 없지만 소스 코드만 볼 때는 Team(일)만 수정한 것 같은데 Member(다) UPDATE 쿼리가 발생하는 치명적인 문제가 있다.

 

일대다 단방향 단점

  • 엔티티가 관리하는 외래키가 다른 테이블에 있다.
  • 연관관계 관리를 위해 추가로 UPDATE SQL이 실행된다.

 

실무에서는 테이블이 수십 개가 넘기 때문에 이러한 문제가 발생하면 유지보수에 매우 힘들어진다.

그렇기 때문에 실무에서 사용하지 않고 이러한 경우 다대일 단방향 관계에 양방향으로 변경하는 쪽으로 관계를 맺는 게 훨씬 수월하다.

일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자.

 

 

일대다 양뱡향

공식적으로 일대다 양방향 매핑은 존재하지 않는다. 하지만 상황상 일(1) 쪽에서 다쪽을 참조하는 일대다 단방향으로 매핑해 놨는데 다쪽에서 일(1) 쪽으로도 보고 싶으면 어떻게 해야 할까?

@Entity
@Getter @Setter
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<>(); 
	
}

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

Member에 Team 객체를 생성하고 @ManyToOne과 @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)을 설정해 주었다.

@JoinColumn으로 Team도 연관관계의 주인처럼 만들고 insertable, updatable 옵션을 사용하여 읽기 전용으로 만들었다.

관리는 Team으로 하고 읽기는 Member로 하고 하는 것이다

 

하지만 이 방법은 비추천...

 

다대일 양방향을 사용하자!

 

 

 


해당 글은 인프런의 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 강의를 정리한 내용입니다.

 

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

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

www.inflearn.com

 

 

'JPA > JPA' 카테고리의 다른 글

[JPA] 다양한 연관관계 매핑 - 다대다  (0) 2023.04.14
[JPA] 다양한 연관관계 매핑 - 일대일  (0) 2023.04.14
[JPA] 연관관계 기초  (0) 2023.04.12
[JPA] 엔티티 매핑  (0) 2023.04.12
[JPA] 플러시와 준영속 상태  (0) 2023.04.11

댓글