컴공댕이 공부일지

[JPA] 6장 - 다양한 연관관계 매핑 본문

study/웹개발 스터디 백엔드 EFUB 💻

[JPA] 6장 - 다양한 연관관계 매핑

은솜솜솜 2025. 4. 11. 15:00
728x90

 

본 글은 도서 '자바 ORM 표준 JPA 프로그래밍'의 6장 요약 정리본입니다.

 

지난 내용 요약

엔티티의 연관관계를 매핑할  때 고려해야할 3가지

  • 다중성
    • 다대일, 일대다, 일대일, 다대다
  • 단방향, 양방향
    • 테이블은 외래 키 하나로 조인해서 모두 양방향 !
    • 객체는 참조용 필드 가진 객체만 연관 객체 조회 가능
    • 단방향 : 한 쪽만 참조, 양방향 : 양쪽이 서로 참조
  • 연관관계의 주인
    • 엔티티를 양방향으로 매핑 시, 2곳에서 서로를 참조하므로
      JPA가 두 객체 연관관계 중 하나를 정해 데이터베이스 외래 키를 관리함.
    • 외래 키 가진 테이블과 매핑한 엔티티를 주로 연관관계의 주인으로 선택
      (외래 키 관리하기 효율적이므로.)
    • 연관관계의 주인은 mappedBy 속성 사용 x
      연관관계 주인 아니면 mappedBy 속성 사용하고 연관관계의 주인 필드 이름을 값으로 입력

 


 

 

📖 다대일

다대일 : N이 1을 참조

외래키는 항상 다쪽에 존재.

→ 객체 양방향 관계에서 연관관계의 주인 또한 다쪽이다.

 

@ManyToOne 어노테이션을 사용하여 매핑

 

연관관계의 주인:

다(N) 쪽에 있는 엔티티가 연관관계의 주인이 되는 것이 일반적

@JoinColumn 어노테이션을 사용하여 외래 키를 매핑

 

 

다대일 단방향 [ N : 1 ]

하나의 엔티티에서만 다른 엔티티를 참조. 외래키 가진 엔티티에서 연관된 엔티티를 조회할 때 !

Member.team으로 팀 엔티티 참조&외래키 관리 ! 반대는 불가능 (단방향)

@Entity
public class Member {

	@Id @GeneratedValue
    @Column(name = "MEMBER_ID") //Member.team 필드를 TEAM_ID 외래키와 매핑
    private Long id;
    
    private String username;
    
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
    ...
@Entity
public class Team {
	@Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    
    private String name;
    ...

 

 

 

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

실선이 연관관계의 주인 (Member.team)

 

외래키가 있는 쪽이 연관관계의 주인!

Member.team이 연관관계의 주인.


양방향 연관관계는 항상 서로를 참조해야 함!

setTeam(), addMember() 등의 편의 메소드를 양쪽 혹은 한쪽에 작성해 서로 참조.

편의 메소드를 양쪽 다 작성할 땐 무한루프에 빠질 수 있으니 주의해야함

@Entity
public class Member {

	@Id @GeneratedValue
    @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;
        
        // 무한루프에 빠지지 않도록 체크 - 현재 멤버객체(this)가 해당 team에 포함되어있지 않다면 추가.
        if(!team.getMembers().contains(this)) {
        	team.getMembers().add(this);
        } 
    ...
@Entity
public class Team {
	@Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    
    private String name;
    
    @OneToMany(mappedby = "team") // 연관관계 주인이 Member !
    private List<Member> members = new ArrayList<Member>();
    
    public void addMember(Member member) {
    	this.members.add(member);
        
        //무한 루프에 빠지지 않도록 체크 - this의 팀에 속하지 않았을 때, 멤버 추가
        if(member.getTeam() != this) {
        	member.setTeam(this)
        }
    ...

 

 

 


 

 

 

📖 일대다

일대다 : 1이 N을 참조

 

일대다 관계는 다대일의 반대방향

일대다 관계는 엔티티를 하나 이상 참조할 수 있으므로, (1이 다를 참조 !)

자바 컬렉션인 Collection, List, Set, Map 중 하나를 사용

 

일대다 단방향 [ 1:N ]

 

보통 자신이 매핑한 테이블의 외래 키를 관리하는데, 이 매핑은 반대쪽 테이블에 있는 외래 키를 관리.

다대일, 일대다 단방향의 연관관계 매핑 차이

 

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

    @OneToMany
    @JoinColumn(name = "TEAM_ID") // 명시적 외래키 지정 ; MEMBER 테이블의 TEAM_ID(FK)
    private List<Member> members = new ArrayList<Member>();
	
    ...
}
@Entity
public class Member {
    @Id @GeneneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    ...
}

 

 

❓일대다 단방향 매핑의 단점

매핑한 객체가 관리하는 외래 키가 다른 테이블에 있다는 점. 

본인 테이블에 외래 키가 있으면, 엔티티의 저장과 연관관계 처리를 INSERT SQL 한 번으로 끝낼 수 있지만

다른 테이블에 외래 키가 존재하므로, 연관관계 처리를 위한 UPDATE SQL을 추가로 실행해야함.

 

Team만이 Member를 참조하는 필드를 가지고 있으므로, Member를 insert로 저장하고, 해당 멤버가 속한 팀 정보를 업데이트하기 위해 update 진행해야함. 본인 테이블에 외래 키가 있으면 insert만으로 모든 처리가 끝남.

 

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

앞서 언급했듯, 엔티티를 매핑한 테이블이 아닌 다른 테이블이 외래 키를 관리해야함. 성능 및 관리 차원에서 별로 !
다대일 양방향은 외래 키가 본인 테이블에 있어 관리가 쉽고, 두 매핑의 테이블 모양이 완전히 같아 엔티티만 약간 수정하면 된다. 상황에 따라 다르지만, 다대일 양방향 매핑을 보다 더 권장한다.

 

 

 

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

사실 다대일 양방향과 동일함. 본 교재에서는 왼쪽(일)이 오른쪽(다)를 참조한다고 가정했음.

 

관계형 데이터베이스 특성상 일대다, 다대일 관계는 항상 다 쪽에 외래 키가 있음.

따라서 연관관계의 주인은 언제나 ManyToOne이므로, ManyToOne에는 mappedBy 속성이 없다.

 

그러나 아래와 같은 방식으로 일대다 양방향 매핑을 시도할 수 있다.

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

    @OneToMany
    @JoinColumn(name = "TEAM_ID") //MEMBER 테이블의 TEAM_ID(FK)
    private List<Member> members = new ArrayList<Member>();
	
    ...
}
@Entity
public class Member {
	@Id @GeneneratedValue
	@Column(name = "MEMBER_ID")
	private Long id;

	//다대일 단방향 매핑 추가 (읽기전용)
	@ManyToOne
	@JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
	private Team team;
	...
}

 

‼️일대다 단방향 + 다대일 단방향 (읽기전용) → likes 일대다 양방향

 

두 연관관계 모두 같은 TEAM_ID 외래 키를 관리하므로 문제가 생길 수 있기에,

다대일 쪽에는 외래 키를 수정하지 못하도록 속성을 false로 두어 읽기만 가능하도록 했다.

 

 위 방식은 일대다 양방향이라기보단, 일대다 단방향 매핑 반대편에 다대일 단방향 매핑을 읽기 전용으로 추가해서 일대다 양방향처럼 보이도록 하는 방법이다. 일대다 단방향 매핑이 가지는 단점을 그대로 가진다.(반대편 테이블의 외래키를 관리하는 것!)

 

결론 ! 가능한 다대일 양방향 매핑을 사용본인 테이블의 외래키를 본인이 관리하도록 할 것 ~



 


 

 

 

📖 일대일 [1:1]

두 엔티티가 정확히 하나씩 서로 연관되는 관계

 

일대일 관계는 그 반대도 일대일 관계 !

 

일대일 관계는 주 or 대상 테이블 둘 중 어느 곳이나 외래 키를 가질 수 있다.
(이전 관계들에서는 항상 다(N)쪽이 외래 키를 가짐)


주/대상 테이블 무관하게 외래 키 하나만 있으면 양쪽으로 조회 가능 + 일대일 관계는 반대쪽도 일대일 관계.
따라서 일대일 관계는 주/대상 중 누가 외래 키를 가질지 선택해야 함

 

  • 주 테이블 : 참조되는 테이블
  • 대상 테이블 : 주 테이블의 기본 키를 외래 키로 포함하는. 참조하는 테이블

 

1. 주 테이블에 외래 키

  • 주 테이블에 외래 키를 두고 대상 테이블을 참조
  • 주 테이블이 외래 키를 가지고 있으므로 주 테이블만 확인해도 대상 테이블과 연관관계가 있는지 알 수 있다.

 

단방향

@Entity
public class Member {
    @Id @GeneneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;
	
    ...
}

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

    ...
}

 

 

 

양방향

연관관계의 주인을 정해야 함 (Member.locker)

@Entity
public class Member {
    @Id @GeneneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;
	
    ...
}

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

	//양방향 연관관계 매핑
	@OneToOne(mappedBy = "locker")
    private Member member;
    ...
}

 

 

 

 

 

2. 대상 테이블에 외래 키

  • 대상 테이블에 외래 키를 두고 주 테이블을 참조
  • 테이블 관계를 일대일에서 일대다로 변경할 때 테이블 구조를 그대로 유지할 수 있다.

 

단방향

→ 매핑 X

 

양방향

일대일 대상 테이블 외래키에 단방향은 JPA 지원 X

일대일 매핑에서 대상 테이블에 외래 키를 두고 싶으면 양방향으로 매핑한다.

  • 주 테이블 단방향: 주 테이블에 외래 키를 두고 대상 테이블을 참조한다. @OneToOne과 @JoinColumn을 주 테이블 엔티티에 사용한다.
  • 대상 테이블 단방향: 대상 테이블에 외래 키를 두고 주 테이블을 참조한다. @OneToOne과 @JoinColumn을 대상 테이블 엔티티에 사용한다.
  • 양방향: 주 테이블 또는 대상 테이블 중 하나를 연관관계의 주인으로 설정하고, 다른 쪽은 mappedBy를 사용하여 주인을 참조한다. 주로 외래 키가 있는 테이블을 주인으로 설정한다. @OneToOne 어노테이션을 사용한다.
  • 장점: 특정 엔티티에 대한 상세 정보를 별도의 테이블로 관리하여 테이블을 분리하고, 필요할 때만 조인하여 조회할 수 있다.

 

 


 

 

 

📖 다대다 [N:N]

 

  • 여러 개의 엔티티가 여러 개의 다른 엔티티와 연관되는 관계.
  • 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다.
  • 연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야만 한다.

 

  • 매핑: 관계형 데이터베이스에서는 중간 테이블을 사용하여 다대다 관계를 표현한다. JPA에서는 @ManyToMany 어노테이션과 @JoinTable 어노테이션을 사용하여 중간 테이블을 매핑한다.
    • @JoinTable: 중간 테이블의 이름, 조인할 컬럼 정보 등을 지정한다. joinColumns 속성에는 현재 엔티티를 매핑하는 외래 키 정보를, inverseJoinColumns 속성에는 반대쪽 엔티티를 매핑하는 외래 키 정보를 설정한다.
  • 단방향: 하나의 엔티티에서만 다른 엔티티들의 컬렉션을 참조하는 방식.
  • 양방향: 양쪽 엔티티 모두 서로의 컬렉션을 참조하는 방식. 한 쪽 엔티티에 @ManyToMany와 @JoinTable을 설정하고, 반대쪽 엔티티에는 @ManyToMany와 mappedBy를 사용하여 연관관계의 주인이 아님을 명시한다.
  • 주의사항: 다대다 관계는 편리하지만, 중간 테이블에 추가적인 정보(예: 주문 시간, 수량 등)를 관리하기 어렵다는 단점이 있다. 이 경우에는 별도의 엔티티를 만들어 다대일-일대다 관계로 풀어내는 것을 고려해야 한다.

 

 


 

 

📖 정리

 

▼ 가능한 모든 연관관계들

  • 다대일 : 단방향, 양방향 (왼쪽(다)을 연관관계의 주인으로)
  • 일대다 : 단방향, 양방향
  • 일대일 : 주 테이블 단방향, 양방향
  • 일대일 : 대상 테이블 단방향, 양방향
  • 다대다 : 단방향, 양방향
728x90
Comments