jpa

Jpa 연관관계 정리

malangcow 2022. 8. 22.

연관관계를 매핑한다?

 

일반적으로 DB에서 테이블의 연관관계를 매핑할 땐 외래키를 사용하고 양방향관계를 사용한다.

select *
from team t
join member m on t.team_id = m.team.id;

select *
from member m
join team t on m.team.id = t.team_id;
  • 이와 같이 하나의 외래키로 양방향 매핑이 되어 양방향 조회가 가능하다.

 

하지만 ORM에서 객체는 참조를 사용하여 매핑하고 방향성이 존재한다.

 

@Entity()
@Table(name = "MEMBER")
public class Member {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}
  • team 객체의 team_id 컬럼을 참조하여 단방향 매핑을 하는 예이다.
  • @JoinColumn을 사용해서 외래키 TEAM_ID를 명확하게 지정해 줄 수 있다.

 

객체에서 양방향 매핑을 하려면 어떻게 해야할까?

  • 객체는 항상 단방향 매핑만 가능하다.
  • 양방향이라고 말하는 것은 사실 두 개의 단방향 연관관계이다.
  • 엔티티를 양방향 관계로 설정할 경우 객체의 참조는 2개인데 외래키는 하나이기 때문에 차이가 발생하게된다.
    고로 두 개의 연관관계에서 관리자가 필요하게되고 연관관계의 주인이라고 표현한다.
  • 일반적으로 연관관계의 주인 쪽만 등록, 수정, 삭제를 하고 아닌 쪽은 읽기만 가능하다.
연관관계의 주인을 정한다. ==> 외래키 관리자를 선택한다.

 

MappedBy 속성을 사용하여 연관관계의 주인을 결정한다.

  • mappedBy속성이 없는 곳이 연관관계의 주인이다
@Entity
public class Team {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "team")
    private List<Member> memberList;
}
  • USER (1) <---> TEAM (N) 의 연관관계이다. 
  • USER객체에서 team으로 참조를 하기 때문에 mappedBy 속성에 "team"을 넣어준다.
  • Team 객체는 mappedBy 지정이 되었으므로 주인이 아니고 읽기만 가능하다.
  • 일반적으로 N:1, 즉 다대일 관계의 경우 N을 주인으로 정한다.

 

양방향 연관관계에서 관계 맺기

@Test
void 양방향연관관계_테스트_주인아닌쪽() {
    Team team = teamRepository.findById(1L).get();
    Member member = userRepository.findById(1L).get();

    team.getMemberList().add(member);
    teamRepository.save(team);

    Member reGetMember = userRepository.findById(1L).get();
    System.out.println(reGetMember.getTeam()); 			// print : null
    assertEquals(team, reGetMember.getTeam());	 		// fail
}
  • 연관관계의 주인이 아닌 team에서 member의 추가를 강행해도 연관관계는 설정이 되지 않는다.
    member에 team은 null이된다.

 

@Test
void 양방향연관관계_테스트_주인() {
    Member member = userRepository.findById(1L).get();
    Team team = teamRepository.findById(1L).get();

    member.setTeam(team);
    userRepository.save(member);

    Member savedMember = userRepository.findById(1L).get();
    System.out.println(savedMember.getTeam()); 			// print : team
    asssertEquals(team, reGetMember.getTeam()); 		// success
}
  • 연관관계의 주인인 member에서 team을 setting하면 연관관계가 설정된다.

 

순수 객체 관계를 고려해서 양 쪽 다 값을 세팅하자.

@Entity()
public class Member {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
    
    // 묶어서 설정
    public void setTeam(Team team) {
        this.setTeam(team);
        team.getMemberList().add(this);
    }
}
  • 양방향 매핑으로 사용한다 할지라도 객체이기에 한 쪽의 값을 변경해도 반대 쪽 객체가 변화하지 않을 것이다.
  • 순수 객체 관계를 고려해서 양 쪽 다 값을 세팅할 수 있도록 로직을 구현하자.

 

 

 

그럼 orm에서 양방향 관계를 어떻게 써야할까?

  • 기본적으로 단방향 관계만으로 설계가 가능하므로 단방향으로 설계한다.
  • 추후 반대 쪽에서도 접근이 필요하다면 양방향 연관관계를 추가하는 식으로 설정하는게 좋다.!
  • 왜냐하면 위에서 봤다시피 단방향참조의 user에서, 양방향 관계를 team에서 설정해줘도 user에선 변한게 없고
    당연히 DB테이블에 영향을 주지 않기 때문에 확장이 간편하다.

 

다대일, 일대다가 아닌 관계에서는 어떨까?

일대일관계

  • 어떤 테이블이든 외래키를 가질 수 있다.
  • 주테이블에 외래키를 놓을 때의 장점
    • 외래키를 객체 참조와 비슷하게 사용할 수 있다.
    • 주테이블만 확인해도 연관관계를 알 수 있다.
  • 대상 테이블에 외래키
    • 일대다관계로 변경할때 구조를 그대로 갖고갈 수 있다는 장점이있다.

다대다관계

  • RDB에선 정규화된 테이블 2개로 다대다를 표현할 수 없다.
  • @Jointable로 연결테이블을 설정할 순 있지만.. 새로운 식별자를 사용하는게 좋다.
  • jpa에서 @ManyToMany를 제공해주긴 하지만 한계가 있어 중간테이블을 두어 관계를 풀어주는 것이 일반적이다.

 

'jpa' 카테고리의 다른 글

ORM과 JPA 개념정리  (0) 2022.08.15

댓글