연관관계 매핑 기초

2024. 4. 11. 00:52JAVA/스프링 데이터 JPA

※ 기본 용어 정리

1) 방향 : 

1 - 1) 단방향 : 관계가 있을 때 둘 중 한쪽만 참조하는 것을 단방향 관계라고 한다.

1 - 2) 양방향 : 관계가 있을 때 서로를 참조하는 것을 양방향 관계라고 한다.

 

2) 다중성 : 다대일(N : 1), 일대다(1 : N), 일대일(1 : 1), 다대다(N : M) 다중성이 있다.

 

3) 연관관계의 주인 : 양방향 연관관계로 만들 시 연관관계의 주인을 정해야 한다.

 

1. 단방향 연관관계

다음과 같은 상황을 가정한다.

 

- 회원과 팀이 있다.

- 회원은 하나의 팀에만 소속될 수 있다.

- 회원과 팀은 다대일 관계다.

 

● 객체 연관관계

- 회원 객체는 Member.team 필드로 팀 객체와 연관관계를 맺는다.

- 회원 객체와 팀 객체는 단방향 관계다. 회원은 Member.team 필드를 통해서 팀을 알 수 있지만 반대로 팀은 회원을 알 수 없다.

 

테이블 연관관계

- 회원 테이블은 TEAM_ID 외래키로 팀 테이블과 연관관계를 맺는다.

- 회원 테이블과 팀 테이블은 양방향 관계다.

 

객체 연관관계 vs 테이블 연관관계 정리

- 객체는 참조(주소)로 연관관계를 맺는다.

- 테이블은 외래 키로 연관관계를 맺는다.

 

객체 관계 매핑

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

 

 

1) JoinColumn

JoinColumn(조인 컬럼)은 왜래 키를 매핑할 때 사용된다. 사용법은 아래와 같다.

속성 기능 기본값
name 매핑할 외래 키 이름 필드명 + _ + 참조하는 테이블의 기본 키 컬럼명
referencedColumnName 외래 키가 참조하는 대상 테이블의 컬럼명 참조하는 테이블의 기본 키 컬럼명
foreignKey(DDL) 외래키 제약조건을 직접 지정할 수 있다. 이 속성은 테이블을 생성할 때에만 사용한다.  

 

※ JoinColumn 생략

기본 전략 : 필드명 + _ + 참조하는 테이블의 컬럼명

 

2) @ManyToOne

@ManyToOne 어노테이션은 다대일 관계에서 사용한다.

 

※ @ManyToOne 속성

속성 기능 기본값
optional false로 설정하면 연관된 엔티티가 항상 있어야 한다. true
fetch 글로벌 패치 전략을 설정한다.  @ManyToOne = FetchType.EAGER
@OneToMany = FetchType.LAZY
cascade 영속성 전이 기능을 사용한다.  
targetEntity 연관된 엔티티의 타입 정보를 설정한다. 이 기능은 거의 사용하지 않는다.  

 

2. 연관관계 사용

2 - 1) 저장

※ JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속상태여야 한다.

 

public void testSave() {
	
    Team team1 = new Team("team1", "팀1");
    em.persist(team1);
    
    Member member1 = new Member("member1", "회원1");
    member1.setTeam(team1);
    em.persist(member);
    
    Member member2 = new Member("member2", "회원2");
    member2.setTeam(team1);
    em.persist(member2);
}

 

※ 중요한 부분

member1.setTeam(team1);
em.persist(member);

 

회원 엔티티는 팀 엔티티를 참조하고 저장했다. JPA는 참조한 팀의 식별자를 외래 키로 사용해서 적절한 등록 쿼리를 생성한다.

 

2 - 2) 조회

연관관계가 있는 엔티티를 조회하는 방법은 크게 2가지다.

- 객체 그래프 탐색 : 객체를 통해 연관된 엔티티를 조회한다.

- 객체지향 쿼리 사용 : SQL에서 연관된 테이블을 조인하여 검색 조건을 사용한다.

 

2 - 3) 수정

수정은 em.update() 같은 메소드가 없다. 트랜잭션을 커밋할 때 플러시가 일어나면서 변경 감지 기능이 작동하기 때문에 굳이 필요 없는 것이다.

 

2 - 4) 연관관계 제거

member1.setTeam(null);

 

2 -5) 연관된 엔티티 삭제

연관된 엔티티를 삭제하려면 기존에 있던 연관관계를 먼저 제거하고 삭제해야 한다. 그렇지 않으면 외래 키 제약조건으로 인해 데이터베이스에서 오류가 발생한다.

 

3. 양방향 연관관계

데이터베이스 테이블은 외래 키 하나로 양방향으로 조회할 수 있으므로 처음부터 양방향 관계다. 기존에 예를 들었던 회원과 팀 엔티티를 가져오자. 이 때 회원 엔티티에는 변경할 부분이 없고 팀 엔티티를 변경해야 한다. 팀 엔티티에 다음을 추가한다.

@OneToMany(mappedBy = "team")
privateList<Member>members = new ArrayList<Member>();

 

mappedBy 속성은 양방향 매핑일 때 사용하는데 반대쪽 매핑의 필드 이름을 값으로 주면 된다.

 

4. 연관관계의 주인

Q : mappedBy는 왜 필요할까?

A : 엄밀히 이야기하면 객체에는 양방향 연관관계 라는 것이 없는다 서로 다른 단방향 연관관계 2개를 애플리케이션 로직으로 잘 묶어서 양방향인 것처럼 보이게 한다. 반면 데이터베이스 테이블은 앞서 설명햇듯이 외래키 하나로 양쪽이 서로 조인할 수 있다.

 

객체 연관관계는 다음과 같다.

 

- 회원 -> 팀 연관관계 1개 (단방향)

- 팀 -> 회원 연관관계 1개 (단방향)

 

테이블 연관관계는 다음과 같다.

 

- 회원 <-> 팀의 연관관계 1개 (양방향)

 

테이블은 외래 키 하나로 두 테이블의 연관관계를 관리하지만 엔티티는 양방향 연관관계로 설정하면 객체의 참조가 둘이지고 외래 키는 하나가 되어버리므로 둘 사이에 차이가 발생한다. 따라서 두 객체 중 하나를 정해서 테이블의 키를 관리하게 하는데 이것이 연관관계의 주인이다.

 

Q : 그렇다면 연관관게의 주인은 어떻게 설정하는가?

A : 주인은 mappedBy 속성을 사용하지 않는다. 즉, mappedBy를 사용하지 않는 쪽이 주인이다. 

 

Q : 주인의 특권이 있는가?

A : 연관관계의 주인만이 데이터베이스 연관관계와 매핑되고 외래키를 관리할 수 있다. 반면 주인이 아닌 쪽은 읽기만 할 수 있다.

 

5. 양방향 연관관계 저장

team1.getMembers().add(member1);
team1.getMembers().add(member2);

 

이런 코드가 있어야할 것 같지만 Team.members는 연관관계의 주인이 아니므로 이곳에 입력된 값은 외래 키에 영향을 주지 않는다.

 

6. 양방향 연관관계의 주의점

양방향 연관관계를 설정하고 가장 흔히 하는 실수는 연관관계의 주인에는 값을 입력하지 않고, 주인이 아닌 곳에만 값을 입력하는 것이다. 객체 관점에서 보면 양쪽 방향에 모두 값을 입력해주는 것이 안전하다.

 

연관관계를 변경할 때는 기존 팀이 있으면 기존 팀과 회원의 연관관계를 삭제하는 코드를 추가해야 한다.

 

이러한 점 때문에 객체에서 양방향 연관관계를 사용하려면 로직을 견고하게 작성해야 한다.

'JAVA > 스프링 데이터 JPA' 카테고리의 다른 글

프록시와 연관관계 매핑  (0) 2024.05.06
다양한 연관관계 매핑  (0) 2024.04.18
엔티티 매핑  (2) 2024.04.04
영속성 관리  (0) 2024.03.27
데이터베이스 스키마 자동 생성  (0) 2023.12.27