Spring/Spring Data JPA

[자바 ORM 표준 JPA 프로그래밍] 5. 연관관계 매핑 기초

noahkim_ 2023. 12. 27. 19:44

김영한 님의 "자바 ORM 표준 JPA 프로그래밍" 책을 정리한 포스팅 입니다.

 

1. 단방향 연관관계

객체 연관관계

  • 참조로 연관관계를 맺음.

 

단방향 관계
  • 한 객체가 다른 객체 참조 O (다른 객체가 반대 객체 참조 X)
  • 객체 그래프 탐색을 통해 연관관계 파악 가능

 

테이블 연관관계

  • 외래키로 연관관계를 맺습니다.

 

양방향 관계
  • 한 객체가 다른 객체 참조 O + 다른 객체가 반대 객체 참조 O
  • 연관관계인 모든 테이블이 조인 쿼리에 쓰임

 

연관 관계 매핑

@JoinColumn
  • 외래키 설정 (조인 시 사용)
name 외래키 이름
referencedColummnName 외래키가 참조하는 컬럼명
foreignKey 외래키 제약조건 (테이블 생성시에만)

 

@ManyToOne
  • 다대일 관계 매핑
optional 관련 엔티티 존재 유무 true
fetch 페치 전략 @ManyToOne = FetchType.EAGER
@OneToMany=FetchType.LAZY
cascade 영속성 전이 기능 사용 유무  
targetEntity 관련 엔티티 타입  

 

 

@Entity
public class Member {
  @Id
  @Column(name = "MEMBER_ID")
  private String id;
  
  //연관관계 매핑
  @ManyToOne
  @JoinColumn(name = "TEAM_ID")
  private Team team;
  
  //연관관계 설정
  public void setTeam(Team team) {
    this.team = team;
  }
  //Getter, Setter ...
}

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

 

 

2. 연관관계 사용

저장

참조 객체와 함께 DB에 저장  
  • (슈퍼타입) 참조객체 저장
  • (서브타입) 객체 저장

 

조회

객체 그래프 탐색
  • 연관관계를 사용하여 조회
Member member = em.find(Member.class, "member1");
Team team = member.getTeam(); // 객체 그래프 탐색

 

JPQL
String jpql = "select m from Member m join m.team t where " + "t.name=:teamName";
			
List<Member> resultList = em.createQuery(jpql, Member.class)
        .setParameter("teamName", "팀1")
        .getResultList();

 

수정

  • em.update() 메서드 없음.
  • 커밋할 때(flush()), 변경 감지 기능이 동작함.

 

제거

  • 연관관계 객체 null로 셋팅
private static void deleteRelation(EntityManager em) {
    Member member = em.find(Member.class, "member1");
    member.setTeam(null);
}

 

 

3. 양방향 연관관계

  • 테이블: 외래키 하나로 양방향 조회 가능
  • 객체에서도 양방향 조회 설정 가능 (매핑)

 

양방향 연관관계 매핑

@OneToMany
  • 일대다 관계
  • 여러 건과 연관관계 설정 가능
  • mappedBy
    • (양방향 매핑일 때) 연관관계 주인 설정
    • 반대쪽 매핑객체의 필드 이름
@Entity
public class Member {
    @Id
    @Column(name = "MEMBER_ID")
    private String id;

    //연관관계 매핑
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team; 
}

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

    @OneToMany(mappedBy = "team")
    private List<member> members = new ArrayList<Member>();  
}

 

 

4. 연관관계 주인

테이블
  • 외래키 하나로 두 테이블의 연관관계 관리

 

객체
  • 양방향 연관관계 존재 X
  • 단방향 연관관계 2개를 묶어 구현 (두 개의 참조 발생)
  • 외래키를 관리할 주인 설정 필요 (연관관계 주인)

 

주인

  • 외래키 지정 (연관관계 매핑)
  • 외래키 관리 (설정, 변경)

 

주인 X

  • 읽기만 가능

 

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

연관관계 맺기

주인에 연관관계 값 입력 X
  • db에 null 값으로 저장됨
Member member = new Member(); // 주인
member.setName("Member1");
em.persist(member);

Team team = new Team();
team.setName("TeamA");
team.getMembers().add(member);
em.persist(team);

 

해결방안 1
  • 필수) 주인으로부터 연관관계 설정 
  • 권장) 양쪽 모두에  셋팅 (순수한 객체 고려): 테스트 용이
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member(); // 주인
member.setName("Member1");
member.setTeam(team);
em.persist(member);

team.getMembers().add(member);

 

해결방안 2
  • 연관관계 편의 메소드 (주인)
  • setter 함수를 정의하여 사용 (양쪽 모두에게 값이 입력되도록)
public class Member {
    private Team team;

    public void setTeam(Team team) {
        this.team = team;
        team.getMembers().add(this);
    }
}

 

연관관계 변경

이전 연관관계의 객체가 영속 상태
  • 연관관계만 변경 시, 수정 전의 연관관계 객체를 읽음
member1.setTeam(team1);
member1.setTeam(team2);

Member findMember = team1.getMembers().get(0); // 여전히 member1이 조회된다.

 

해결방안
  • 연관관계를 변경할 경우, 반대 객체의 연관관계도 삭제 필요
public void setTeam(Team team) {
    if (this.team != null) {
    	this.team.getMembers().remove(this);
    }

    this.team = team;
    team.getMembers().add(this);
}

 

무한 루프

  • 객체 간 상호 참조로 인해 발생할 수 있음
  • toString(), equals(), hashCode()
  • JSON Serializing

 

해결 방안 (toString())
  • 순환 참조 피하기
public class Team {
    private String name;
    private List<Member> members = new ArrayList<>();

    @Override
    public String toString() {
        return "Team{name='" + name + "'}"; // members 리스트 제외
    }
}

public class Member {
    private String name;
    private Team team;

    @Override
    public String toString() {
        return "Member{name='" + name + "'}"; // team 필드 제외
    }
}

 

해결 방안 (JSON Serializing)
  • @JsonManagedReference: 직렬화 시, 포함되는 필드 지정
  • @JsonBackReference: 직렬화 시, 제외되는 필드 지정
public class Team {
    private String name;
    
    @JsonManagedReference
    private List<Member> members = new ArrayList<>();
}

public class Member {
    private String name;
    
    @JsonBackReference
    private Team team;
}