JPA 연관관계 매핑 1

윤현우·2023년 6월 17일
0
post-thumbnail

가장 먼저 JPA를 쓰는 이유부터 알아야 한다.

현재 우리는 SQL Mapper나 ORM 기술들을 이용하여, DB와 연결하여 사용하고 있다.

이중 JPA는 자바 진영의 ORM 기술 표준으로, 과거 JDBC API를 사용해 매핑을 해주던 것을 보다 간결하게 만들어주는 프레임워크이다.

뭐 이전보다 SQL문을 덜 쓴다는 것에 대해 장점을 가져서 JPA를 사용하는 것도 있지만, 가장 큰 문제는 따로있다.

바로 객체와 DB와의 패러다임의 불일치 문제를 해결하기 위해 사용하는 것이다.


객체와 DB와의 패러다임 불일치

자바같은 객체 지향 언어는 추상화, 캡슐화, 정보은닉, 상속, 다형성 등 다양한 장치들을 제공한다.

특히 객체들 간에는 참조를 통해 다른 객체와 연관관계를 가지고, 참조에 접근해 연관된 객체를 조회한다.

반면, 테이블은 외래키(foreign key)를 이용해 다른 테이블과 연관관계를 가지고 조인을 사용해 연관된 테이블을 조회한다.

객체들은 참조가 있는 방향으로만 조회할 수 있다. 하지만 DB의 외래키들은 양방향으로 조회가 가능하다.

결국 우리는 FK, 외래키를 이용한 테이블에 맞춘 객체 모델을 사용해서, DB 테이블을 모델링했다.

class Member{

	String id;		// MEMBER_ID 컬럼 사용
    Long teamId;	// TEAM_ID FK 컬럼 사용
    String username;
}

class Team{

	Long id;		// TEAM_ID PK 사용
    String name;
}

이렇게 객체를 테이블에 맞추어 모델링하면 객체를 테이블에 저장하거나 조회할 때는 편하지만, 객체에서는 연관된 객체의 참조를 보관해야 참조를 통해 연관된 객체를 찾을 수 있다.

이렇게 되면 좋은 객체 모델링은 기대하기 어렵고 결국 객체지향의 특징을 잃어버리게 된다.

결국 객체지향의 특징을 잃지 않고 테이블을 모델링 하기위해 JPA라는 것이 필요한 것이다.

참조를 사용하는 객체 모델

class Member{

	String id;		// MEMBER_ID 컬럼 사용
    Team team;		// 참조로 연관관계를 맺는다.
    String username;
    
    Team getTeam() {
    	return team;
    }
}

class Team{

	Long id;		// TEAM_ID PK 사용
    String name;
}

이처럼 참조를 사용하는 객체모델을 사용하면 객체를 테이블에 저장하거나 조회하기 쉽지 않다.

Member 객체는 team 필드로 연관관계를 맺고 Member 테이블은 TEAM_ID로 연관관계를 맺기 때문인데, 객체 모델은 외래키가 필요 없고 단지 참조만 있으면 된다.

반면에 테이블은 참조가 필요없고 외래키만 있으면 된다.

결국, 우리는 JPA를 이용해서 변환역활을 해야 한다.


연관관계 매핑

객체는 참조를 사용해서 관계를 맺고, 테이블은 외래키를 사용해 관계를 맺는다.

이러한 연관관계들을 매핑하는 것이 JPA의 역할이다.


키워드

연관관계의 매핑을 이해하기 위해 세가지의 키워드가 있다.

방향

  • [단방향, 양방향]
  • 회원과 팀이 관계가 있을 때, 회원 -> 팀, 팀 -> 회원 둘중 한 쪽만 참조하는 것을 단방향관계, 양쪽 모두 서로 참조하는 것을 양방향 관계라 한다.
  • 방향은 객체관계에만 존재, 테이블 관계는 항상 양방향이다.

다중성

  • [다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M)]
  • 회원과 팀이 관계가 있을 때, 여러 회원은 한팀에 속한다. 따라서 회원과 팀은 다대일 관계다.
  • 반대로 팀과 회원은 일대다 관계이다.

연관관계의 주인

  • 객체를 양방향 연관관계로 만들면 연관관계의 주인을 정해야 한다.

단방향, 양방향 연관관계

단방향

이 전에 말했듯, 객체는 단방향으로 관계를 맺고, 테이블은 양방향으로 관계를 맺는다.

테이블은 항상 양방향으로 관계를 맺기 때문에, JPA에서 나오는 연관관계의 방향을 객체들의 방향을 의미한다.

예를 들어 Member와 Team 객체가 있다고 가정하자.

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

  • 객체 연관관계
    • 회원 객체는 Member.team 필드로 팀객체와 연관관계를 맺는다.
    • 따라서 현재 Member와 team 객체는 단방향 관계이다.
    • 단방향 관계이므로, member.getTeam()으로 조회가 가능하지만, team.getMembers()는 불가능하다.
  • 테이블 연관관계
    • 회원 테이블은 TEAM_ID 외래 키로 Team 테이블과 연관관계를 맺는다.
    • 테이블은 항상 양방향 관계이다. 따라서 Member Join Team과 Team Join Member 둘다 가능하다.

따라서 객체 그래프 탐색으로 Member 에서 Team을 조회할 수 있지만, Team에서는 Member를 조회할 수 없다.

양방향

그렇다면 객체 연관관계도 양방향으로 만들면 되는 것이 아닐까라는 생각을 할 것이다.

테이블도 외래키로의 연관관계를 이용해 양방향으로 만드니 객체에서도 양방향으로 만들면 되는거 아니야? 싶을 것이다.

가능하다.

팀에서 회원을 조회할 수 있게 양방향 연관관계로 만들 수 있다.

테이블은 양방향 연관관계이다. 하지만 객체에서의 연관관계는 양방향처럼 보일지라도, 결국 단방향인 것이 2개인 것이다.

엄밀히 말하면 객체에는 양방향 연관관계라는 것이 없다.

이렇게 단방향과 양방향의 차이를 알아보았다.

이제는 다중성과 방향을 동시에 알아보자.


다대일 단방향 연관관계

가장 먼저 코드를 통해 확인해보자

@Entity
public class Member {
    @Id
    @GeneratedValue
    private Long id;
    private String MemberName;
    @ManyToOne
    @JoinColumn(name="TEAM_ID")
    private Team team;
}

@Entity
public class Team {
    @Id
    @GeneratedValue
    private Long id;
    private String teamName;
}
  • 객체 연관관계: 회원 객체의 Member.Team 필드 사용
  • 테이블 연관관계: 회원 테이블의 MEMBER.TEAM_ID 외래키 컬럼을 사용

단방향 연관관계에서의 객체와 테이블이 사용할 수 있는 연관관계를 나타내었다.

객체 연관관계는 단방향으로 되어있기 때문에 Team.Member 필드를 사용할 수 없다.

반면에 테이블은 외래키를 이용해 양방향으로 조회를 할 수 있다.

@ManyToOne

  • 다대일 연관관계를 확인 시키는 어노테이션
  • 현재 Member 객체가 다 쪽이기 때문에 다대일 연관관계 어노테이션을 사용한 것이다.

@JoinColumn(name="TEAM_ID")

  • 해당 필드를 외래키로 설정하겠다는 어노테이션이다.

다대일 양방향 연관관계

양방향 연관관계 매핑 코드는 다음과 같다.

@Entity
public class Team {
    @Id
    @GeneratedValue
    private Long id;
    private String teamName;
    //양방향 매핑을 위해 추가
    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();
}

@Entity
public class Member {
    @Id
    @GeneratedValue
    private Long id;
    private String MemberName;
    @ManyToOne
    @JoinColumn(name="TEAM_ID")
    private Team team;
}

단방향 연관관계와 다르게 Team필드에 Member를 저장할 수 있는 필드가 생성된 것을 볼 수 있다.

해당 필드로 인해 Team에서 Member를 객체 그래프 탐색을 통해 조회할 수 있게 된다.

@OneToMany(mappedBy = "team")

  • 객체에서의 양방향 관계는 단방향인 관계가 양쪽으로 있다고 설명했다. 따라서 다대일의 반대는 일대다 이므로 OneToMany어노테이션을 사용한다.

  • mappedBy 속성은 양방향 매핑일 때 사용된다.

여기서 연관관계의 주인에 대해 나온다.

객체에는 양방향 관계는 없고 단방향인 연관관계가 두개 있다는 것인데, 그렇다면 외래키도 두개여야 할 것이라는 생각이 들 것이다.

하지만 테이블 특성상 외래키는 한부분에 있고, 한 곳에서 양방향 연관관계를 맺는다.

이 때문에 JPA는 두 객체 연관관계 중 하나를 정해서 테이블의 외래키를 관리 해야 한다.

이것을 연관관계의 주인이라고 한다.

양방향 매핑의 규칙

연관관계의 주인만이 데이터베이스 연관관계와 매핑되고 외래키를 관리(등록, 수정, 삭제)를 할 수 있다.반면에 주인이 아닌 쪽은 읽기만 할 수 있다.

그래서 연관관계의 주인은 내맘대로 설정하는 것인가?

그것도 아니다. 대부분 외래키가 있는 곳이 연관관계의 주인이 된다.

왜냐하면, 그것이 더 관리하기 편하기 때문이다.

profile
개발자가 되는 그날까지

0개의 댓글