개요

엔티티는 대부분 다른 엔티티와 연관관계가 있다. 그런데 객채는 참조(주소)를 사용해서 관계를 맺고 테이블은 외래 키를 사용해서 관계를 맺는다. 이 둘은 완전히 다른 특징을 가짐.

 

객체 관계매핑(ORM)에서 가장 어려운 부분이 바로 객체 연관관계와 테이블 연관관계를 매핑하는 일이다.

 

객체와 참조와 테이블의 외래 키를 매핑하는 것이 이 장의 목표이다.

 

  • 방향(direction) : 단방향,양방향이 있다. 방향은 객체 관계에만 존재. 테이블 관계는 항상 양방향
  • 다중성(Multiplicity): N:1, 1:N, 1:1 , N:M 
  • 연관관계의 주인(owner): 객체를 양방향 연관관계로 만들면 연관관계의 주인을 정해야 한다.

 

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

객체는 참조(주소)로 연관관계

테이블은 외래키로 연관관계.

 

참조를 통한 연관관계는 언제나 단방향이다. 

1
2
3
4
5
6
7
8
9
class A{
    B b;
}
class B{
    A a;
}
 
--> B(a,b)
--> A(b,a)
cs

양방향 관계 X. 서로다른 단방향 관계 2개

외래 키를 사용하는 테이블의 연관관계

-A JOIN B가 가능하면 반대로 B JOIN A도 가능

 

 

객체 관계 매핑

//매핑

@ManyToOne

@JoinColumn(name="TEAM_ID")

private Team team;

 

매핑한 팀 엔티티

@Entity

public class Team{

          @Id

          @Column(name= "TEAM_ID")

          private String id;

}

 

객체 연관관계:회원 객체의 Member.team 필드 사용

테이블 연관관계: 회원 테이블의 MEMBER.TEAM_ID 외래 키 컬럼을 사용

 

Member.team과 MEMBER.TEAM_ID를 매핑하는것이 연관관계 매핑이다. 

 

@JoinColumn

name: 매핑할 외래 키 이름

referencedColumnName : 외래 키가 참조하는 대상 테이블의 컬럼명

foreignKey(DDL) : 외래 키 제약조건 직접 지정. (테이블 생성시만 사용)

+@Column의 속성.

 

@JoinColumn

optional: false 시 연관된 엔티티 항상 있어야함.기본값 true

fetch: 8장

cascade: 8장

 

MEMBER_ID NAME TEAM_ID TEAM_NAME
member1 회원1 team1 팀1
member2 회원2 team2 팀1

 

단방향

조회

-객체 그래프 탐색

Member member=em.find(Member.class,"member1");

Team team = member.getTeam();

 

 

-객체지향 쿼리 사용(JPQL)

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
String jpql="select m from Member m join m.team t where "+
                                "t.name=:teamName";
 
List<Member> resultList =em.createQuery(jpql,Member.class)
        .setParameter("teamName","팀1");
        .getResultList();
 
 
for( Member member :resultList){
}
    //출력시 
    member.username=회원1
    member.username-회원2
cs

:teamName과 같이 :로 시작하는 것은 파라미터를 바인딩받는 문법이다.

 

실행되는 SQL은 다음과 같다.

SELECT M. * FROM MEMBER MEMBER

INNER JOIN

   TEAM TEAM ON MEMBER.TEAM_ID=TEAM1_.ID

WHERE

   TEAM1_.NAME='팀1'

 

수정

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
//새로운 팀
Team team2= new Team("team2","팀2");
em.persist(team2);
 
//회원1에 새로운 팀2 설정
Member member = em.find(Member.class,"member1");
member.setTeam(team2);
 
실행되는 수정 SQL
UPDATE MEMBER
SET
    TEAM_ID='team2',...
WHERE
    ID='member1'
cs

수정은 엔티티 값 변경시 트랜잭션 커밋할 때 플러쉬 일어나면서 변경 감지 기능이 작동하므로 update같은 메소드 필요 없음.

 

연관관계 제거

Member member1=em.find(Member.class,"member1");

member1.setTeam(null); 연관관계 제거

 

SQL

UPDATE MEMBER

SET

   TEAM_ID=null, 

WHERE 

   ID='member1'

 

연관된 엔티티 삭제

연관된 엔티티 삭제하려면 기존에 있던 연관관계 먼저 제거하고 삭제해야 한다. 

그렇지 않으면 외래 키 제약조건으로 인해 db오류 발생.

팀1에는 회원1과 회원2가 소속되어 있다. 이때 팀1을 삭제하려면 연관관계를 먼저 끊어야 함.

 

member1.setTeam(null); //회원1 연관관계 제거

member2.setTeam(null); //회원2 연관관계 제거

em.remove(team); //팀 삭제

 

 

양방향 연관관계

 

 

일대다 관계는 여러 건과 연관관계를 맺을 수 있으므로 컬렉션을 사용해야 한다.

객체 연관관계에서 Team.members를 List 컬렉션으로 추가

-회원=>팀 (Member.team)

-팀=>회원(Team.members)

 

테이블 연관관계는 외래키 하나로 양방향 조회가능.

 

양방향 연관관계 매핑

 

//==추가==//

@OneToMany(mappedBy="team")

private List<Member> members = new ArrayList<Member>();

 

일대다 관계 매핑위해 @OneToMany 매핑 정보 사용. 

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

반대쪽 매핑이 Member.team이므로 team을 값으로 줌.

 

 

일대다 컬랙션 조회

Team team= em.find(Team.class , "team1");

List<Member> members= team.getMembers();

 

 

연관관계의 주인

mappedBy는 왜 필요할까?

엄밀히 이야기하면 객체는 양방향 연관관계 없음.

 

엔티티를 양방향 연관관계로 설정하면 객체의 참조는 둘인데 외래 키는 하나다.

따라서 둘 사이에 차이가 발생한다.

-> 이런 차이로인해 JPA에서는 두 객체 연관관계 중 하나를 정해서 테이블의 외래키를 관리해야 하는데 이것을

연관관계의 주인(Owner)라고 한다.

 

연관관계의 주인만이 db 연관관계와 매핑되고 외래 키를 관리 (등록,수정,삭제)할 수 있다.

반면 주인이 아닌 쪽은 읽기만 할 수 있다.

 

  • 주인은 mappedby 속성을 사용하지 않는다.
  • 주인이 아니면 mappedBy속성을 사용해서 속성의 값으로 연관관계의 주인을 지정해야 한다.

그렇다면 Member.team, Team.members 둘 중 어떤 것을 연관관계의 주인으로 정해야 할까?

 

다음 두 코드를 보자.

 

-회원 -> 팀(Member.team) 방향

1
2
3
4
5
6
7
 
class Member{
    @ManyToOne
    @JoinColumn(name="TEAM_ID")
    private Team team;
}
 
cs

-팀 -> 회원(Team.members) 방향

1
2
3
4
5
6
 
class Team{
    @OneToMany
    private List<Member> members= new ArrayList<Member>();
}
 
cs

연관관계의 주인을 정한다는 것은 사실 외래 키 관리자를 선택하는 것.

 

여기서는 회원 테이블에 있는 TEAM_ID 외래 키를 관리자를 선택해야한다.

-회원 엔티티에 있는 Member.team을 주인으로 선택시:

 자기테이블에 있는 외래 키를 관리하면 된다.

 

-팀엔티티에 있는 Team.members를 주인으로 선택시:

물리적으로 전혀 다른 테이블의 외래 키를 관리해야 한다. 왜냐 하면 이 경우 Team.members가 있는 Team 엔티티는

TEAM테이블에 매핑되어 있는데 관리해야할 외래 키는 MEMBER테이블에 있기 때문.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void testSave(){
    //팀1 저장
    Team team1=new Team("team1","팀1");
    em.persist(team1);
    
    //회원1 저장
    Member member1=new Member("member1","회원1");
    member1.setTeam(team1); //연관관계 설정 member1 -> team1
    em.persist(member1);
    
    //회원2 저장
    Member member2= new Member("member2","회원2");
    member2.setTeam(team1); //연관관계 설정 member2 -> team1
    em.persist(member2);
}
cs

회원1,회원 2에 연관관계의 주인인 Member.team 필드를 통해 연관관계 설정하고 저장함.

이 코드는 단방향 연관관계에서 살펴본 예제 코드와 같다.

 

양방향 연관관계의 주의점

 

team1.getMembers().add(member1);

team1.getMembers().add(member2);

 

SELECT * FROM MEMBER;

회원을 조회한 결과 TEAM_ID가 null값으로 저장됨.

연관관계의 주인이 아닌 Team.members에만 값을 저장했기 때문.

연관관계의 주인만이 외래 키의 값을 변경할 수 있다. 

예제코드는 Member.team에 아무 값도 입력하지 않았다. -> 따라서 TEAM_ID외래 키의 값도 null이 저장된다.

 

but

객체 관점에서 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전하다.

why? 

양방향 비 설정시 team1.getMembers().size()가 0이 나올수 있음.

 

 

member1.setTeam(team1);

team1.getMembers().add(member1);

em.persit(member1);

 

양방향 연관관계는 결국 양쪽 다 신경 써야 한다. 

각각 호출하면 실수로 양방향 깨질 수 있으므로

Tip:두코드는 하나인 것처럼 사용하는 것이 안전하다.

 

연관관계 편의 메소드 작성 시 주의사항

 

member1.setTeam(team1);

member1.setTeam(team2);

Member findMember=teamA.getMember(); //member1이 여전히 조회됨.

1
2
3
4
5
6
7
8
9
 
public void setTeam(Team team){
    //기존 팀과 관계를 제거
    if(this.team != null){
        this.team.getMembers().remove(this);
    }
    this.team= team;
    team.getMembers().add(this);
}
cs

 

이 코드는 객체에서 서로 다른 단방향 연관관계 2개를 양방향인 것처럼 보이게 하려고 얼마나 많은 고민과

수고가 필요한지 보여준다. 반면에 관계형 db는 외래 키 하나로 문제를 단순하게 해결한다. 

 

결론: 객체에서 양방향 연관관계를 사용하려면 로직을 견고하게 작성해야 한다. 

 

 

정리

 

양방향의 장점은 반대방향으로 객체 그래프 탐색 기능이 추가된 것뿐이다.

member.getTeam(); 

team.getMembers();

 

  • 단방향 매핑만으로 테이블과 객체의 연관관계 매핑은 이미 완료.
  • 단방향을 양방향으로 만들면 반대방향으로 객체 그래프 탐색 기능이 추가된다.
  • 양방향 연관관계를 매핑하려면 객체에서 양쪽 방향을 모두 관리해야 한다. 

 

'자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글

7장  (0) 2021.05.04
6장  (0) 2021.05.02
4장  (0) 2021.05.01
1장 JPA란 무엇인가?  (0) 2021.04.30
1장 JPA를 사용하는 이유(비교)  (0) 2021.04.30

+ Recent posts