[5] 연관관계 매핑 기초

ttt-1-2·2026년 4월 5일

교재: 자바 ORM 표준 JPA 프로그래밍

5장의 목표는 객체의 참조와 테이블의 외래 키를 매핑하는 것이다.

5장의 핵심 키워드:

  • 방향 (direction): 단방향, 양방향이 있다. 방향은 객체관계에만 존재하고 테으블 관계는 항상 양방향이다.
  • 다중성 (multiplicity): 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M) 다중성이 있다.
  • 연관관계 주인 (owner): 객체를 양방향 연관관계로 만들면 연관관계의 주인을 정해야 한다.

1. 단방향 연관관계

ex: 회원과 팀의 관계를 알아보자

  • 회원과 팀이 잇다
  • 회원은 하나의 팀에만 소속될 수 있다
  • 회원과 팀은 다대일 관계다

객체 연관관계

  • 회원 객체는 Member.team 필드(멤버 볌수)로 팀 객체와 연관관계를 맺는다
  • 회원 객체와 팀 객체: 단방향 관계다.
    • 회원: Member.team 필드를 통해 팀을 알 수 있다 (member.getTeam()으로 조회)
    • 팀: 회원을 알 수 없다 (team → member 접근 필드 없음)

테이블 연관관계

  • 회원 테이블은: TEAM_ID 외래 키로 팀 테이블과 연관관계를 맺는다.
  • 회원 테이블과 팀 테이블: 양방향 관계.
    • 회원: TEAM_ID 외래 키를 통해서 회원과 팀을 조인할 수 있다
    • 팀: 회원도 조인할 수 있다
-- 회원과 팀을 조인하는 SQL
SELECT *
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.ID

-- 팀과 회원을 조인하는 SQL
SELECT *
FROM TEAM T
JOIN MEMBER M ON T.ID = M.TEAM_ID

1.1 순수한 객체 연관관계

  • 객체는 참조를 사용해서 연관관계를 탐색하는 것이 객체 그래프 탐색이라 한다.
member1.setTeam(team1);
member2.setTeam(team1);

Team findTeam = member1.getTeam();

1.2 테이블 연관관계

데이터베이스는 외래 키를 사용해서 연관관계를 탐색하는 것이 조인이라 한다.

SELECT T.*
FROM MEMBER M
	JOIN TEAM T ON M.TEAM_ID = T.ID
WHERE M.MEMBER_ID = 'member1'

1.3 객체 관계 매핑

ex: 회원 엔티티 매핑

@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team;
  • @ManyToOne: 다대일(N:1) 관계. 매핑할 때 이렇게 다중성을 나타내는 어노테이션은 필수다.
  • @JoinColumn(name=”TEAM_ID”): 조인 컬럼은 외래 키를 매핑할 때 사용한다. name 속성: 매핑할 외래 키 이름. 이 어노테이션은 생략할 수 있다

1.4 @JoinColumn

속성:

  • name: 매핑할 외래 키 이름
  • referencedColumnName: 외래 키가 참조하는 대상 테이블의 컬럼명
  • foreignKey(DDL): 외래 키 제약조건을 직접 지정. 테이블 생성할 때만 사용
  • unique, nullable, insertable, updatable, columnDefinition, table: @Column의 속성과 같다

1.5 @ManyToOne

속성:

  • optional: 연관관계가 필수인지(null 허용 여부)를 설정한다
  • fetch: 연관 엔티티의 조회 방식을 설정한다 (LAZY 또는 EAGER)
  • cascade: 연관 엔티티에 대한 영속성 전이 범위를 설정한다
  • targetEntity: 연관될 엔티티 타입을 지정한다

2. 연관관계 사용

2.1 저장

public void testSave() {

	//팀1 저장
	Team team1 = new Team("team1", "팀1");
	em.persist(team1);
	
	//회원1 저장
	Member member1 = new Member("member1", "회원1");
	member1.setTeam(team1); // 연관관계 설정: member1 -> team1
	em.persist(member1);
	
	//회원2 저장
	Member member2 = new Member("member2", "회원2");
	member2.setTeam(team1); // 연관관계 설정: member2 -> team1
	em.persist(member2);
}

2.2 조회

  • 연관관계가 있는 엔티티를 조회하는 방법은 크게 2가지다.
    • 객체 그래프 탐색

      Team team = member.getTeam();
    • 객체지향 쿼리 사용 JPQL

      List<Member> resultList = em.createQuery(jpql, Member.class)
      		.setParameter("teamName", "팀1");
      		.getResultList();

2.3 수정

//회원1에 새로운 팀2 설정
Member member = em.find(Member.class, "member1");
member.setTeam(team2);

2.4 연관관계 제거

member1.setTeam(null); //연관관계 제거

2.5 연관된 엔티티 삭제

member1.setTeam(null); //회원1 연관관계 제거
member2.setTeam(null); //회원2 연관관계 제거
em.remove(team); //팀 삭제

3. 양방향 연관관계

회원 → 팀 접근하고 반대 방향 팀 → 회원 접근할 수 있는 양방향 연관관계로 매핑해 볼 것이다.

3.1 양방향 연관관계 매핑

매핑한 팀 엔티티:

@Entity
public class Team {

	@Id
	@Column(name="TEAM_ID")
	private Stirng id;
	
	// 추가
	@OneToMany(mappedBy = "team")
	private List<Member> members = new ArrayList<Member>();
	
}
  • mappedBy: 양방향 매핑일 때 사용, 반대 쪽 매핑의 필드 이름을 값으로 주면된다.

3.2 일대다 컬렉션 조회

List<Member> members = team.getMembers(); // (팀 -> 회원), 객체 그래프 탐색

4. 연관관계의 주인

복습:

  • 테이블: 외래 키 하나로 두 테이블의 연관관계를 관리한다
  • 엔티티: 단방향으로 매핑하면 참조를 하나만 사용

→ 엔티티를 양방향 관계로 설정하면 객체의 참조는 둘인데 외래 키는 하나다.

연관관계의 주인

  • 두 연관관계 중 하나를 연관관계의 주인으로 정해야한다.
    • 연관관계의 주인만이 데이터베이스 연관관계와 매핑되고 외래 키를 관리(등록, 수정, 삭제)할 수 있다
    • 주인이 아닌 쪽: 읽기만 할 수 있다

연관관계의 주인은 외래 키가 있는 곳

연관관계의 주인은 외래 키(FK)를 가진 쪽이고 Team–Member 관계에서는 TEAM_ID를 가진 MEMBER가 주인이 된다.

5. 양방향 연관관계 저장

양방향 연관관계는 연관관계의 주인이 외래 키를 관리한다. 주인이 아닌 방향은 값을 설정하지 않아도 데이터베이스에 외래 키 값이 정상 입력된다.

team1.getMembers().add(member1); // 무시
team1.getMembers().add(member2); // 무시

member1.setTeam(team1); // 연관관계 설정 
member2.setTeam(team1); // 연관관계 설정

6. 양방향 연관관계의 주의점

외래 키 값이 저장되지 않는다면 연관관계의 주인이 아닌 쪽에서 값을 설정했는지 먼저 확인해야 한다.

6.1 순수한 객체까지 고려한 양방향 연관관계

객체 관점에서 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전하다.

member1.setTeam(team1); // 회원 -> 팀
team1.getMembers().add(member1); // 팀 -> 회원

6.2 연관관계 편의 메소드

양방향 연관관계는 양쪽 다 신경 써야 한다. 각각 호출하다 보면 실수로 둘 중 하나만 호출해서 양방향이 깨질 수 있다. 편의 메소드 사용 방법:

  private Team team;

  public void setTeam(Team team) {

      if (this.team != null) {
        this.team.getMembers().remove(this);
      }
      this.team = team;
      team.getMembers().add(this);
  }
}

0개의 댓글