JPA - 연관관계매핑

JPA 연관관계 매핑


JPA는 관계형 DB를 객체지향적으로 관리하기 위해 만들어진 기술로서 객체의 연관관계와 외래키 매핑하는 방법을 알아야 한다.

객체의 참조와 테이블의 외래 키를 매핑

  • 방향(Direction) : 단방향 , 양방향

  • 다중성(Multiplicity) : 다대일 (N:1) , 일대다 (1:N) , 일대일 (1:1), 다대다(N:M) 이해

  • 연관관계의 주인(Owner) : 객체 양방향 연관관계는 관리이 필요 (C 의 pointer 개념)

만약 Mybatis와 같이 Entity객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.

단방향 매핑


데이터 중심의 모델링

@Entity
class Memeber{

    @Id
    @Column(name = "MEMBER_ID")
    private Long id;

    private String name;

    @Column(name = "TEAM_ID")
    private Long teamId;
}

// ,,,,

Member member = em.find(Member.class , 1L);// Member를 select

Team team  = em.find(Team.class, member.getTeamId());
// select한 Member에서 Team의 PK값으로 Team을 select

  • 테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾는다.

  • 객체는 참조를 사용해서 연관된 객체를 찾는다.

  • 테이블과 객체 사이에는 이런 큰 간격이 있다.

위와 같이 객체간의 연관 관계가 없기 때문에 두번 호출해야하는 수고로움이 있다.

객체 중심의 모델링

@Entity
class Memeber{

    @Id
    @Column(name = "MEMBER_ID")
    private Long id;

    private String name;

    // Member(N) : Team(1)
    @ManyToOne 
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}

// ,,,,

Member member = em.find(Member.class , 1L);// Member를 select

Team team  = member.getTeam();
// select한 Member에서 get을 통해 Team을 가져온다.

이렇게 객체간의 연관관계를 설정하는것을 단방향 매핑이라고 한다.

양방향 매핑


만약 연관된 테이블이 있다면 하나는 외래키(FK)를 가지고 있어 Join 또는 FK를 통해 양방향 접근이 가능한다.

그렇다면 당연히 JPA에서도 양방향 접근을 지원한다.

Team에도 Member와 같이 연관관계를 만들어준다.

@Entity
class Memeber{

    @Id
    @Column(name = "MEMBER_ID")
    private Long id;

    private String name;

    // Member(N) : Team(1)
    @ManyToOne 
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}

// ,,,,

@Entity
class Team{

    @Id
    @Column(name = "TEAM_ID")
    private Long id;

    private String name;

    // Team(1) : Member(N)
    @OntToMany(mappedBy = "team") // member의 team과 연관관계
    private List<Member> members; 
}

근데 여기서 고민해봐야 하는 부분이 있다.

둘다 연관관계를 가지고 있다. 그렇다면 누가 연관관계의 주인(Owner)이 되는가?

양방향 매핑에는 규칙이 있다.

  • 객체의 두 관계중 하나를 연관관계의 주인으로 지정

  • 연관관계의 주인만이 외래 키를 관리 (등록 , 수정)

  • 주인이 아닌 쪽은 읽기만 가능

  • 주인은 mappedBy 속성 사용 X

  • 주인이 아니면 mappedBy 속성으로 주인 지정

위와 같은 규칙으로 객체를 보면 주인은 Member가 된다.
잠시 테이블을 생각해보면 알수 있는 사실이 하나 있는데 주인은 외래키(FK)를 가지는 테이블이 된다.

- 외래 키가 있는 곳을 주인으로 정해라
- 무조건 다인 쪽이 연관관계의 주인이 된다!

주의사항

JPA에서 연관관계를 만들게 되었을 시 객체지향적이지 않는 문제를 발견할 수 있다.

Member에는 setTeam으로 team을 넣어주게 된다. 하지만 team에는 Memeber를 넣어 주지않는다.

// 역방향(주인이 아닌 방향)만 연관관계 설정
// mappedBy는 읽기전용이라 Dirty Check가 이뤄지지 않는다. (가짜 맵핑)
team.getMembers().add(member);

굳이 넣어주지 않아도 되지 않느냐 라고 생각하는 사람이 있을수 있다.

아래에 코드를 한번 보자

Team team = new Team();
team.setName("TeamA");

em.persist(team);

Member member = new Member();
member.setUsername("memberA");
member.setTeam(team);

em.persist(member);

Member findMember = em.find(Member.class , member.getId());
List<Member> memberList = findMember.getTeam().getMembers();

for (Member m : memberList) {
    System.out.println("m.getId() = " + m.getId());
}

위에 persist 후 find 하는 코드가 있다.
만약 위 코드로 실행하면 정상적으로 돌아간다 생각했으면 다시 공부를 해야한다.

주인에만 값을 입력후 persist시 insert는 정상 작동을 하게된다.
하지만 트렌젠션 안에서 find를 하게됬을시에 문제가 발생한다,

persist시에 영속성 컨텍스트의 매커니즘은 1차캐시에 값이 저장되어 find시에 캐시에 있는 값을 우선적으로 찾도록 되어 있다.

그럼 방금 persist한 데이터를 가져오게 될것이다.
여기서 문제가 발생하는데 캐시에 들어가 있는 team은에는 member와 관계는 있는지 실제로 데이터가 들어가 있지는 않다.

객체지향적으로 보면 team과 member의 값을 셋팅해주는것이 좋다. ( 테스트 케이스에서도 필요 )
또는 flush , clear가 필요하다.

편의 메서드

양방향 관계시 매번 team.getMembers().add(member);을 선언하는것은 귀찮고 시간 낭비이다.

setMemthod를 사용하여 편리하게 셋팅을 해줄 수 있다.

public void setTeam(Team team){
  this.team = team;
  team.getMembers().add(this);
}
  • 읽기전용 부분값 셋팅을 setTeam으로 이동해준다.

  • 팁으로 자바의 set메서드 관례때문에 추가로직이 있을시 change~ 메서드를 따로 작성하여 준다.

  • 편의메서드는 한쪽을 정하여 하나에만 생성한다.

무한 루프 ( toString() , lombok , JSON라이브러리 )

toString시 양방향 참조중이기에 무한 toString이 일어난다.

- lombok toString은 빼고 생성 추천

- JSON 생성 라이브러리 컨트롤러에 Entity를 반환하지 마라

다양한 연관관계 매핑


일대다 @OneToMany (1 : N)


단방향

team의 Member를 수정하면 member의 관계 pk가 업데이트된다.

- 엔티티가 관리하는 외래 키가 다른 테이블에 있음
- 연관관계 관리를 위해 추가로 UPDATE SQL 실행 (메모리 낭비)

개발을 하면서 서비스 로직의 코드를 많이 볼텐데 team에 데이터가 수정되었을 때 member가 수정된다.
실무에서 엔티티가 많을텐데 햇갈릴 수 있다.

@JoinColumn을 꼭 설정 (defualt : @JoinTable)

양방향

@ManyToOne
@JoinColumn(name = "" , insertable=false, updatable=false )
private Team team;

이렇게 작성시 누가 주인인지 알수가 없기에 insertable , updatable 을 통해 읽기전용으로 만들어 준다.

- N은 1을 알고 싶지 않을때 사용한다.  
- 실무에서는 이런 모델링을 많이 하지 않는다.  
- 일대다 단방향 매핑보다는 다대일 양방향을 이용하자 

다대일 @ManyToOne (N : 1)


일대일 @OneToOne (1 : 1)


- 어느 한쪽을 정하여 외래키 설정이 가능하다.
- 다대일과 똑같이 매핑하여 사용 가능하다.

주테이블에 외래키

  • 주 객체가 대상 객체의 참조를 가지는 것 처럼 주 테이블에 외래 키를 두고 대상 테이블을 찾음

  • 객체지향 개발자 선호

  • JPA 매핑 편리

  • 장점 : 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능

  • 단점 : 값이 없으면 외래 키에 null 허용

대상 테이블에 외래키

  • 대상 테이블에 외래 키가 존재

  • 전통적인 데이터베이스 개발자 선호

  • 장점 : 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지

  • 단점 : 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨(프록시는 뒤에서 설명)

다대다 @ManyToMany (N : M)


관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다.

하지만 객체는 다대다가 가능하다.

다대다는 편리하지만 2개의 테이블을 연결만 시켜주기 때문에 실무에서 사용하는 추가데이터 입력이 불가능하다.

정리


  • 단방향 매핑만으로도 이미 연관관계 매핑은 완료된다.

  • 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추기된 것 뿐이다.

  • JPQL에서 역방양으로 탐색할 일이 많다.

  • 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 된다. (테이블에 영향을 주지 않음)

양방향을 쓰면 양방향 접근이 가능하여 편리하기도 하지만 신경써야 되는 부분이 더 많아진다.

왠만하면 양방향보단 단방향으로 쓰는게 좋다.

Categories:

Updated:

Leave a comment