본문 바로가기
프로그래밍/Spring

[JPA] 테이블 연관관계 정리

by YuminK 2023. 8. 6.

ORM 사용 이유

객체의 참조 방식과 관계형 DB의 연관관계는 다르다.

예를 들어 Table의 외래값을 그대로 객체로 옮긴다면 **Id 형태의 필드를 선언하지만 객체의 개념에서 다른 객체를 참조하는 개념은 아니다. 물론 Id값을 이용하여 조회하는데 사용할 수는 있지만, 객체지향 프로그래밍답지 않다.

 

따라서 테이블의 외래키 대신 참조 값을 넣고 어노테이션을 이용하여 테이블의 연관관계를 정의하는 것이다. 

프로그래밍을 할 때는 OOP처럼 코드를 작성하되, DB에 저장할 때는 외래키로 매핑해주는 것이다.

 

객체 A와 B가 있다면 A에서 B로의 참조, B에서 A로의 참조를 가질 수 있다. 다만 테이블의 경우에는 한쪽 테이블에 외래키가 존재하는 경우 A테이블로 B를 JOIN, B테이블로 A를 JOIN하므로 방향을 가지지 않는다. 

 

@ManyToOne

다대일의 관계이다. 다에 해당하는 쪽에 외래키를 두도록 설계하고 클래스 내에서 @JoinColumn(name ="외래키 이름")을 작성한다. 또한 id값을 필드로 두는 것이 아니라 객체를 참조한다. 

 

A 객체는 연관관계의 주인으로서 참조 객체를 변경하면 값이 변경된다. 

상대쪽 객체에서 A객체를 참조하길 원하는 경우 @OneToMany(mappedBy = "A객체의 필드 이름")을 지정하여 연관관계와 연관관계의 주인을 지정한다. 외래키를 가지지 않으므로 @JoinColumn은 쓰지 않는다. 

 

주인이 아닌 쪽은 읽기만 가능하다. 변경시 데이터 적용 X

 

다만, 객체지향 프로그래밍 특성에 맞게 A 객체의 참조 객체 값을 수정할 때, 상대 객체에도 값을 넣어주는 것을 권한다.

A객체가 참조 1개를 하고 있고 상대가 List<A> list 형식의 참조를 하고 있다면 A와 상대 모두 처리한다.

 

외래키를 가진 쪽에 연관관계 편의 메서드를 작성하여 일괄적으로 처리하도록 한다.

 

Tip1) 설계시에는 단방향으로 설계하고 양방향 설계(참조)가 필요한 경우 추가하는 것이 좋다. 

Tip2) list 초기화시 관례상 미리 ArrayList로 할당을 해둔다. (예: List<Member> members = new ArrayList<>(); )

 

@OneToMany

 

다(Member) 쪽에 외래키를 두는데 객체의 참조는 일(Team)쪽에서 하고 있는 형태이다.

@OneToMany를 설정하고, Team의 PK를 @JoinColumn에서 설정한다. 

 

일대다 연결관계는 객체와 테이블의 차이 때문에 반대편 테이블이 외래키를 관리하는 특이한 형태가 된다.

Team쪽이 연관관계의 주인이므로 members를 수정하면 멤버 테이블이 변경된다. 

 

문제는 팀에 대한 쿼리뿐만 아니라, 멤버를 UPDATE하는 쿼리가 추가적으로 진행된다. 

 

일대다보다 다대일 관계로 설계하여 외래키를 가진 쪽에서 한번에 쿼리를 실행하도록 만든다. 

멤버에서 팀을 참조할 일이 없어도 객체적 손해를 보는 것이 '일대다' 관계보다 낫다. 

 

일대다에서 양방향 참조를 하기 위해서는 멤버쪽에 @ManyToOne을 선언하고 @JoinColumn에서 외래키를 선언하되, Insertable과 updatable을 false로 둔다. 

 

Member.java 

 

즉, JPA내에서는 Member를 readonly로 처리를 하기 때문에 양방향 참조가 된다. 

공식적으로 지원하는 내용은 아니다. 

 

@OneToOne

일대일 관계는 어느 쪽에 외래키를 넣든 상관이 없다. 

외래키에 데이터베이스 유니크 제약 조건을 추가한다. 

 

주 객체가 대상 객체의 참조를 가지는 경우 개발이 편리하다. (Member -> Locker)

주 테이블만 조회해도 대상 테이블 데이터를 확인할 수 있다.

JPA 매핑이 편리하다. 

 

대상 테이블에 외래키가 있는 경우는 @OneToMany의 관계가 된 경우(Member, Locker 순) 변경에 유리할 수 있다.

(상황에 따라 다름)

 

프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩이 된다. 

 

@ManyToMany

관계형 데이터베이스에서 다대다 관계를 표현할 수 없다. (객체는 가능)

 

실무에서 사용하지 말아야 한다. 

중간 테이블이 숨겨져 있어 쿼리 처리가 복잡해진다. 

중간 테이블에 원하는 필드를 추가할 수 없다. 

 

=> 연결 테이블을 추가하여 OneToMany와 ManyToOne의 관계로 분리해야 한다. 

내부적으로 중간 Table을 생성하도록 두지 말고, 중간 테이블을 하나 만들고 원하는 필드를 추가하는 것이 낫다.

 

사이드 쪽에서 참조하길 원하는 경우,

연관관계를 설정해주고(OneToMany) mappedBy를 설정해준다. ("member" 또는 "product")

출처: 자바 ORM 강의 1(김영한)

댓글