JPA - 연관관계 맵핑

YeongUng Kim·2021년 5월 29일
0

JPA

목록 보기
1/4
post-thumbnail

JPA 스터디를 시작하게 되면서 순서대로 포스팅을 하려 했지만 귀찮아서 미루고 미루다가 이 사단이 나고 말았다...그래서 연관관계 맵핑을 먼저 다루고 차후에 다른 JPA관련 포스팅을 할 예정이다.

연관관계의 3가지 키워드

  • 다중성
  • 단방향,양방향
  • 연관관계의 주인

다중성

  • 다대일(N:1) @ManyToOne
  • 일대다(1:N) @OneToMany
  • 일대일(1:1) @OneToOne
  • 다대다(N:M) @ManyToMany

연관관계의 다중성에는 이렇게 4가지의 성질이 존재한다. 데이터베이스 설계단계에서 다중성을 보통 정의하게 되는데 다대일과 일대다를 주로 사용한다. JPA 코드는 위의 어노테이션으로 연관관계를 명시해준다.

단방향,양방향

JPA의 등장이유를 한마디로 정의하자면 RDB의 사상을 객체지향언어(OOP)의 사상과 맞추도록 하는 목적을 가지고 있다.DB와 OOP의 오브젝트의 차이점이 여기서 발생하는데, 기존의 관계형 DB는 왜래키(FK)를 이용하여 두 테이블을 조인하여 연관관계를 표현한다.방향이 존재하지 않는것이다. 하지만 OOP의 오브젝트는 필드에 해당 객체의 레퍼런스를 명시해준다. 다시 말하자면 대상의 참조용필드를 가지고 있는 오브젝트만 대상 조회가 가능하다. 여기서 단방향,양방향이라는 키워드가 등장하는데, 한쪽만 다른쪽을 참조하는 것이 단방향이고 양쪽다 서로를 참조하는 경우를 양방향이라고 표현한다.

연관관계의 주인

두 테이블의 관계를 맵핑할때는 외래키를 이용하여 서로를 참조한다. 따라서 외래키 필드를 가지고 있는 테이블이 연관관계의 주인이 되는것이다. 하지만 엔티티 오브젝트의 경우는 양방향으로 맵핑할 경우 외래키를 관리하는 엔티티가 두개가 되기 때문에 둘중 하나로 연관관계의 주인을 정해줘야 한다. 보통은 DB 테이블상 외래키가 있는곳으로 연관관계의 주인을 정해준다. 연관관계의 주인이 아닌 엔티티는 외래키 수정이 불가능하다.즉 읽기(조회)만 허용된다.


N : 1(다대일)

  • N:1의 반대는 1:N(일대다)
  • DB 테이블의 외래키는 항상 N(다)에 있음
  • 연관관계의 주인은 N
  • @ManyToOne 어노테이션을 엔티티에 명시

Talk is cheap show me the code.

Member

@Entity
@Data
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private long id;
    @Column(name = "USERNAME")
    private String username;
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    Team team; // 1 관계인 team 필드
}

team 필드로 외래키를 관리한다.

Team

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

Team에는 Member 엔티티를 조회할 수 있는 필드가 없다 즉 단방향 관계.

N:1 양방향 관계

  • N:1 , 1:N 은 항상 N이 연관관계의 주인이다. 즉 N에 외래키가 존재한다.
  • 양방향 관계는 항상 서로를 참조하고 있어야 한다.
  • 연관관계의 주인이 아닌 엔티티 필드에 @OneToMany(mappedby = "name") 명시

실제 DB 테이블에는 영향을 미치지 않는다. 양방향 관계의 경우 N인 Member가 연관관계의 주인이 된다. N:1 의 반대는 1:N 이라고 했으므로 Team 엔티티에 Member List필드를 추가하고 @OneToMany(mappedby="team")을 명시해주면 양방향 관계가 된다. 해당 필드는 여러개일수 있으므로 자바 컬렉션(List,Map,Set등)을 사용해주면 된다.


1 : N

  • 연관관계의 주인이 1 이다 DB에서는 외래키를 N에 둬야한다.
  • @JoinTable 어노테이션을 명시해야한다.

연관관계의 주인이 1 이다. 하지만 DB에서는 N에 외래키를 줘야한다. 왜? 만약에 1에 외래키를 두게 되면 계속해서 Insert문이 들어가게 된다. 성능,정규화의 문제도 있고 N 쪽에 외래키를 두는게 속편하다.

Team

@Entity
@Data
public class Team {
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;
    @OneToMany
    @JoinColumn(name="team_id")
    private List<Member> members = new ArrayList<>();
}

@JoinTable 어노테이션에 Member의 외래키를 명시해줘야한다. 그렇지않으면 조인테이블이 만들어지기 떄문.

실제로 영속성 컨텍스트에 등록을 시켜서 실행해보면 뜬금없이 update문이 실행되는걸 볼수있다. 왜? 외래키를 가지고 있지 않은 Team이 Member를 관리하기 때문. member를 생성하고 저장할때는 team을 참조하고 있지 않기 때문에 team_id 가 null 인 상태로 일단 insert가 되고 team 엔티티를 생성할때 members 리스트의 필드 값을 참조하여 update가 되는것.

RDB와 OOP의 사상과 관점이 서로 다르다보니 좀 특이한 구조가 나왔다. 1이 N의 외래키를 관라하는 상황이 되어버렸다. 위 코드처럼 간단한 예제야 뭐 어찌어찌 한다 쳐도 실무에서는 테이블 수십,수백개가 엮여서 돌텐데..벌써 머리아프다.

그냥 N : 1 양방향 관계를 사용하자.

1 : N 양방향 관계

공식적으로는 존재하지 않는다. @OneToMany는 연관관계의 주인이 될 수 없다. 야매로는 가능하다.

class Member{

@ManyToOne  @JoinColumn(name="TEAM_ID",Insertable=false,Updatable=false)
private Team team;

}

1 : N 단방향 맵핑 반대편에 같은 외래키를 사용하는 @ManyToOne 단방향 맵핑을 읽기 전용으로 추가하면 된다.
이런식으로 insert,update 불가하도록 읽기전용으로 만들면 사용은 가능한데..그냥 안쓰는 쪽으로 가는게 낫겠다.


1 : 1

  • 주 테이블, 대상 테이블 중 외래키 선택이 가능하다
  • 외래키에 Unique 제약조건을 추가하여 관리한다
  • 반대편은 mappedby를 적용한다

코드를 보자

Member

class Member{
	@OneToOne(mappedby="member") @JoinColumn(name="locker_id)
	private Locker locker;
}

Locker

class Locker{
	@OneToOne(mappedby="member")
    	private Memeber member;
}

양방향이다. 단방향은 JPA 공식 스펙에 없다.
그러면 외래키는 어디에 둬야하나 고민이 생기겠지만 케바케다 각자의 장단점이 존재하기 떄문

주 테이블에 외래키가 있을경우

  • 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인이 가능하다.
  • 주 테이블에 외래키를 두고 대상 테이블 조회가 가능하다.
  • 값이 없으면 null을 허용해야한다. 이슈가 발생할 소지가 있다.

대상 테이블에 외래키가 있을경우

  • null 문제는 발생하지 않는다.
  • 지연로딩 지원이 안된다. 프록시 기능의 한계 때문, 꼭 써야한다면 @LazyToOne 어노테이션을 사용해야한다.

N : M

  • 객체지향 언어에서는 다대다 연관관계 맵핑이 가능하다
  • 하지만 RDB에서는 정규화된 테이블은 다대다 표현이 불가능하다.
  • 그래서 조인테이블을 추가해야 한다.

실무에서 쓰기에는 무리가 있다. 다대일, 일대다로 바꿔서 써야한다.
Member , Product 2개의 엔티티가 있다고 가정해보자 @ManyToMany 어노테이션을 사용하게 되면 Member_Product라는 조인테이블이 생성된다. 조인테이블을 만들어놓고 @JoinTable(name ="joinTable") 어노테이션을 사용하여 조인테이블을 지정해 줘도 된다. 하지만 단순히 조인테이블이 연결 기능만 하고 끝나는 것도 아니고 실제 비즈니스 로직은 휠씬 복잡한데, 새로운 필드를 추가해야할 요구사항이 발생할 경우 조인테이블은 필드추가가 불가능하다. 즉, 맵핑정보(member_id , team_id)만 허용된다. 두개의 테이블을 조인할때 조인테이블을 사용하기 때문에 실제 쿼리도 조금 이상하다.
되도록이면 @OneToMany,@ManyToOne 사용하자


profile
기록하지 않으면 까먹어서 만든 블로그..

0개의 댓글