[자바 ORM 표준 JPA 프로그래밍 - 기본편] 다양한 연관관계 매핑

이재표·2023년 9월 29일
0

연관관계 매핑시 고려사항 3가지

  • 다중성 : 관계의 종류(eg. 일대다, 다대다..)

    • jpa의 다중성은 전부 db와 매핑하기 위한것으로 데이터베이스의 다중성을 기준으로 하면 된다.
  • 단방향, 양방향

    • 테이블 입장에서 바라볼때
      • 외래키 하나로 join을 통해 테이블을 가져오게 되어 사실상 방향이라는 개념은 존재하지 않는다.
    • 객체의 입장에서 바라볼때
      • 참조용 필드가 있으면 참조가 가능하게 되어 한쪽에서만 참조하면 단방향 양쪽이 서로 참조하면 양방향으로 관계가 정의된다.
  • 연관관계의 주인

    • 테이블의 경우 fk 하나로 두 테이블이 연관관계를 맺지만, 객체 양방향의 경우 A->B, B->A처럼 참조가 두곳에 생기게 된다.
    • 결국 객체는 참조가 두곳이지만 테이블은 외래키를 한곳에서만 관리하기때문에 관리하는 객체와 테이블을 결정해줘야한다.
    • 즉, 연관관계의 주인은 외래키를 관리하는 참조를 의미하고, 연관관계 주인을 통해서만 저장,삭제 등이 가능하고, 주인이 아닌 객체는 단순 조회만 가능하게된다.

다대일

다대일 단방향매핑

결과적으로 말하면 '다'쪽 테이블이 연관관계의 주인이 되어야한다.
만약 '일'쪽 테이블이 주인이 된다면 '다'쪽 테이블을 같은 '일'테이블에 넣어주기위해 같은 객체를 여러번 insert해줘야하게 되기에, 무조건 '다'쪽이 연관관계 주인이 되어야한다.

fk를 통해 join할수있게되니, 해당 컬럼을 통해 join하겠다는 의미로 @JoinColumn 어노테이션을 통해 fk를 지정해줄수 있다.

@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;

양방향으로 만들면 어떻게 될까?

보통 주인이 아닌 객체에서 주인을 너무 자주 호출하거나 필요로 할때 양방향으로 만들어주는데, 주인이 아니면 조회만 가능하다.
주인이 있는 쪽(다)에 @JoinColumn으로 외래키인것을 표시해주고, 주인이 아닌쪽에 mappedBy옵션을 통해 fk에 매핑하겠다는 의미를 준다.

@ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
------------------------------------------    
@OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();

일대다

일대다 단방향

일이 연관관계 주인인 관계이다.
그런데 방금 '다'쪽이 연관관계의 주인이 되어야한다.물론 객체에서 다음과 같은 경우가 나올수있지만 전에 말한것처럼 DB입장에서는 무조건 다쪽에 FK가 들어가야한다. 따라서 DB에서는 객체와 달리 '다' 테이블에 FK가 들어가게 된다.
일대다 관계를 그린 그림을 보면 당연히 멤버에 외래키(다)가 들어가게됨. 그러면 team객체를 바꿨을때 team테이블이 아닌 member테이블의 값을 변경해줘야함. 즉, 객체와 데이터베이스가 fk를 관리하는 주체가 달라지는것을 볼수 있다.

코드를 보면 이전과 달리 @JoinColumn을 '일'쪽에 넣어주는 것을 볼수 있다. @JoinColumn을 넣지 않으면 중간테이블이 자동으로 만들어지기 때문에 꼭 넣어줘야한다.

@OneToMany()
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<>();

이렇게하여 em.persist()를 해보면 위의 그림과 같이 Team객체가 fk를 가지고 있지만 데이터베이스상에서는 member테이블에 fk가 가있는것을 볼수 있다.
쿼리를 보면
/* create one-to-many row hellojpa.Team.members */ update Member 라고 다른 테이블에 update쿼리가 나간것을 볼수 있다. 사실 성능상의 문제는 크지 않지만 심각한것은 개발을 진행하며 테이블이 많아지고, 코드가 많아진다면 운영이 힘들어진다는 문제가 있다.
그래서 그냥 객체지향적으로 조금 손해(멤버객체는 팀이 필요없지만 팀 필드를 만드는것)가 있더라도 다대일 양방향으로 만드는 것이 편리하다
그냥 다대일 양방향 매핑을 이용하자!

일대다 양방향 매핑

멤버테이블에 TEAM객체 참조를 넣고 @JoinColumn을 붙히고 insertable과 updatable 옵션을 넣으면 읽기 전용이된다.

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

해당 옵션을 통해 값이 넣어지지도, 업데이트 되지도 않아 강제적으로 조회만 가능한 양방향 매핑을 완성해주었다.
하지만 일대다 양방향은 사실 공식적으로 지원되진 않으니, 그냥 다대일 양방향 이용하자..ㅎ

일대일

일대일 관계는 사실 반대로해도 일대일 매핑이된다. 즉, 주 테이블이나 대상 테이블 중에 외래키를 아무곳이나 넣을수 있다!! 연관관계의 주인을 정했다면, 외래키에 데이터베이스 유니크 제약조건이 추가관리되어야한다!!(안해도 되는데 대신 관리를 엄청 잘해야한다.)

주테이블에 fk가 있는경우

단방향

멤버가 주테이블이다.
다대일 단방향과 유사하다.

양방향 또한 다대일 양방향과 같다보면된다.

대상테이블에 fk가 있다면?

단방향

단방향으로는 해당 관계는 지원되지 않는다.

양방향

단방향과 달리 양방향일때는 대상테이블의 참조를 그대로 테이블에 넣으면 된다. 하지만 사실 이것은 주테이블에 양방향의 방향만 바꾼것 뿐이다. 즉 일대일은 내것만 관리할수 있다.

테이블만을 생각할때 어떻게든 일대일 관계는 유효하지만 위의 그림에서 사물함에 fk가 있는것이 유연하게 테이블을 관리할수 있다. 반대로 멤버에 fk가 있다면 테이블 필드를 추가하고 변경해야하는 부분이 많다.(보통 1명이 여러개의 사물함을 갖는 로직으로 변하는 경우가 많기때문에)
하지만 보통 멤버를 많이 조회하기 때문에 백엔드 입장에서는 멤버에 fk가 있는게 성능도 있고 여러 이점이 있다.

일대일 정리
주테이블에 외래키가 있을때
장점 : 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인가능
단점 : 값이 없으면 외래키에 null 허용

대상테이블에 외래키가 있을때
장점 : 주테이블과 대상 테이블을 일대일에서 일대다 관계로 변경시 테이블 구조를 유지
단점 : 프록시 기능의 한계로 지연로딩으로 설정해도 항상 즉시 로딩됨.

JPA입장에서 실제 테이블에 데이터가 존재하는지 확인되어야 프록시객체를 만들수 있다.
즉, 멤버 테이블만 조회하는 것이 아닌 락커테이블을 통해 멤버 아이디가 있는지 없는지를 확인해야 그 후에 프록시 객체를 만들거나 null을 만들수있는데, 확인과정에서 어차피 쿼리가 나가게 되기 때문에 굳이 지연로딩을 지원할 필요가 없어진다.

다대다

결론부터 말하면 다대다는 사용하면 안된다.
테이블에서 다대다는 연결테이블을 추가해서 일대다, 다대일 관계로 풀어내야한다.

객체는 컬렉션을 사용해서 객체 2개로 다대다가 가능하지만 한계가 분명하다.

무엇이 문제일까?

다대다관계의 경우 중간 테이블이 하나 새로 만들어진다.
자동으로 만들어 지기때문에 편해보이지만 연결테이블이 단순히 연결만 하고 끝나느것이 아닌 주문시간, 수량과 같은 여러 데이터가 들어올수 있는데, 그것을 커스텀하는것이 불가능하다.
또한 중간테이블을 개발자가 컨트롤할수 없어 쿼리또한 컨트롤이 불가하다는 단점이 있다.

그럼 어떻게 극복할까?


개발자가 직접 연결테이블(중간테이블)용 엔티티를 추가한다. 중간 테이블을 만들고, 일대다, 다대일 관계를 통해 중간테이블과 테이블을 연결하여 사용하는것을 권장. 또한 중간테이블을 엔티티로써 직접 만들기 때문에 새로운 필드들을 추가하여 사용할수 있게 된다.
또한 fk두개를 엮어서 pk로 사용하는 경우가 있지만, 종속된 값이 아닌 관련없는 새로운 id를 만들어 pk로 사용하는것이 바람직하다

0개의 댓글