연관관계 매핑

원종서·2022년 2월 11일
1

JPA

목록 보기
6/13

객체의 참조와 테이블의 외래키 매핑

연관관계 매핑 키워드

  1. 방향 : [단방향, 양방향] 이 있으며, 회원 -> 팀 또는 팀 -> 회원 둘 중 한쪽만 참조하는 것을 단방향, 회원 <-> 팀 처럼 양쪽 다 참조하는 것을 양방향 관계라고 한다.
    방향은 객체에만 존재하고 테이블 관계는 항상 양방향 관계이다.
  1. 다중성 : [다대일, 일대다, 일대일, 다대다] 이 있으며 여러 회원은 한 팀에 속함으로 회원과 팀은 다대일관계다.
  2. 연관관계 주인 : 객체를 양방향 연관관계로 만들면 연관관계의 주인을 정해야한다.

단방향 연관관계

회원과 팀은 다대일 관계라고 가정함.

  1. 객체 연관관계
    회원 객체는 Member.team 필드로 팀 객체와 연관관계를 맺는다.
    회원 객체와 팀 객체는 단방향 관계다. 회원은 팀 필드로 팀객체를 알 수 있지만 팀에는 회원 필드가 없기에 팀을 조회할 수 없다.

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

  3. 객체와 테이블의 차이
    참조를 통한 연관관계는 언제나 단방향. 양방향으로 하고 싶다면 반대쪽에도 필드를 추가해야한다.

객체 그래프 탐색 : 참조를 사용해서 연관관계를 탐색하는 행위

객체 관계 매핑

@Entity
public class Member {
	@Id
    Long id;
    
    String username;
    
    @ManyToOne // 연관관계 매핑
    @JoinColumn(name ="TEAM_ID") // 외래키 생성
    Team team;
    
    // 연관관계 설정
    void setTeam(Team team){
    	this.team =team;;
    }
    
    ... getter,setter
    
@Entity
public class Team {
	@Id
    Long id;
    
    String name;

객체 연관관계 : 회원 객체의 Member.team 필드 사용
테이블 연관관계 : 회원 테이블의 MEMBER.TEAM_ID 외래키 사용

@JoinColumn

속성
name
= 매핑할 외래키 이름 , 기본값 : 필드명+_+참조하는 테이블의 기본 키 컬럼명
referencedColumnName
= 외래키가 참조하는 대상 테이블의 컬럼명 , 기본값 : 참조하는 테이블의 기본키 컬럼명
foreignKey
= 외래키 제약조건을 직접 지정할 수 있다.

@ManyToOne

다대일 관계

속성
optional
= false로 설정하면 연관된 엔티티가 항상 있어야함 , 기본값 true
fetch
= 글로펄 페치 전략 설정
cascade
= 영속성 전이 기능 사용
targetEntity
= 연관된 엔티티의 타입 정보 설정, 거의 사용 안함

연관관계 사용

저장

  public void joinSave(){
        Team team = new Team("team1", "팀1");
        tx.begin();

        // save team1
        em.persist(team);

        Member member1 = new Member("member1", "회원1");
        member1.setTeam(team); // 연관관계 설정 member1 -> team1

        // save member1
        em.persist(member1);


        Member member2 = new Member("member2", "회원2");
        member2.setTeam(team); // member2 -> team1

        // save member2
        em.persist(member2);
    }

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

insert into team (team_id, name) values ('team1','팀1');
insert into member (member_id, name, team_id) values ('member1' ,'회원1', 'team1');

조회

연관관계에 있는 엔티티 조회 방법
1. 객체 그래프 탐색
2. 객체지향 쿼리 사용

1. 객체 그래프 탐색

 public void joinSearch(){

        Member member = em.find(Member.class, "member1");
        Team team = member.getTeam(); // 객체 그래프 탐색

2. 객체지향 쿼리 사용

팀1 에 소속된 회원만 조회하려면 팀 엔티티를 검색 조건으로 사용해야함. 즉 조인을 이용해야한다. 객체지향 쿼리에서도 조인을 지원한다.,

		  // 팀 1에 소속된 모든 회원 조회
        String jpql = "select m from Member m join m.team t where t.name = :name";

        List<Member> members = em.createQuery(jpql, Member.class)
        .setParameter("name","팀1").getResultList();

        for (Member member1 : members) {
            System.out.println("member1.getUsername() = " + member1.getUsername());
        }
        

수정

 public void joinUpdate(){
        Team team2 = new Team("team2", "팀2");
        em.persist(team2);

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

        // member.team 은 기존에 유니크 아이디가 team1인 팀이였는데, 밑의 코드로 team2로 바꿨다.
        member.setTeam(team2);
    }

제거

  //연관관계 제거
    public void joinRemove(){

        Member member = em.find(Member.class, "member1");
        //  멤버1의 팀 연관관계를 제거함.
        member.setTeam(null);

        // 단 연관된 엔티티를 삭제하려면 기존의 연관관계를 먼저 삭제해야한다. ( 외리키 제약조건에 의해)

        Member member2 = em.find(Member.class, "member2");
        //  멤버1의 팀 연관관계를 제거함.
        member2.setTeam(null);


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

    }

양방향 연관관계

객체 연관관계 : 회원과 팀은 다대일 관계이고 팀에서 회원은 일대다 관계다. 일대다 관계는 여러개의 연관관계를 갖을 수 있음으로 컬렉션을 사용한다.
테이블 연관관계 : 원래부터 양방향이다.

@Entity
public class Member {
    @Id
    @Column(name="MEMBER_ID)
    private Long id;

    private String username;

	@ManyToOne
    @JoinTable(name = "TEAM_ID")
    private Team team;
    
    public void setTeam(Team team){
    	this.team = team; // 연관관계 설정.
   }
   ...
}

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

    private String name;

	@OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();
    
    ...
}
  • 연관관계 team.members , member.team 두개 존재.

일대다 관계를 매핑하기 위해 @원투매니 매핑정보를 사용했다.
mappedBy 속성은 양방향 매핑일 때 사용하는데 반대쪽 매핑의 필드 이름을 값을 넣어준다. (Member.team 임으로 team으로 넣어준다.)

일대다 컬렉션 조회

 public void biDirection(){
        Team team = em.find(Team.class, "team1");
        List<Member> members = team.getMembers(); // 팀 -> 회원 , 객체그래프탐색

        for (Member member : members) {
            System.out.println("member.getUsername() = " + member.getUsername()); // 회원1, 회원2
        }
    }

연관관계 주인

객체에는 양항향 연관관계라는 것이 없다. 서로 다른 단뱡향 연관관계 2개를 사용해 양방향인 것 처럼 보이게 한것 뿐.
반면에 데베 테이블은 외래키 하나로 양쪽을 조인할 수 잇다. 외래키 하나로 양방향 연관관계를 맺는다.

엔티티를 단방향으로 매핑하면 참조를 하나만 사용하므로, 이 참조로 외래키를 관리하면 되지만. 엔티티를 양방향으로 매핑하면
회원 -> 팀, 팀 -> 회원 두곳에서 서로 참조하기 때문에 객체의 연관관계를 관리하는 포인트가 2곳이 된다.

엔티티를 양항향 연관관계로 설정하면 객체의 참조는 둘인데 외래키는 하나이다. 테이블과 차이가 발생한다.
이런 차이로 인해 JPA는 두 객체 연관관계 중 하나를 정해 테이블의 외래키를 관리해야 하는데 이를 연관관계 주인 이라고 부른다.

양향향 매핑의 구칙 : 연관관계 주인

연관관계 주인만이 데베 연관관계와 매핑되고 외래키를 관리 할 수 잇다.
주인이 아닌 쪽은 읽기만 할 수 잇다.

주인이 아닌 엔티티에 mappedBy 속성을 사용한다.

연관관계의 주인은 외래키가 있는 곳으로 설정해주면 된다.

양방향 연관관계 저장

public void multiJoinSave(){
        Team team = new Team("team1", "1");
        em.persist(team);

        Member member1 = new Member("member1", "회원1");
        member1.setTeam(team); // 연관관계 설정 member1->team1
        // 연관관계 주인인 member.team 을 통해 팀의 연관관계를 설정하고 있다.
        em.persist(member1);

        Member member2 = new Member("member2", "회원2");
        member2.setTeam(team);

        em.persist(member2);

    }

연관관계 주인이 아닌 팀에서 연관관계를 저장 수정해도 { team.getMembers().add(member1) } 해도 외래키의 값은 바뀌지 않는다.

순수한 객체까지 고려한 양방향 연관관계

순수 객체를 위해서라면 연관관계 주인이 아닌 쪽도 값을 입력해 줘야한다.

 public void 순수한_객체까지_고려한_양방향_연관관계(){
        Team team = new Team("team1", "팀1");

        Member member1 = new Member("member1", "회원1");
		Member member2 = new Member("member2", "회원2");
        
        
        member1.setTeam(team); // 연관관계 설정 member1->team1
        member2.setTeam(team); // member2->team1
        
        
        System.out.println(team.getMembers().size()) // 0
        
        // 위를 해결하기 위해선

	    team.getMembers().add(member1); // 연관관계 설정 team1-> member1
        team.getMembers().add(member2); // 연관관계 설정 team1-> member2
        System.out.println(team.getMembers().size()) // 2
        
    }

연관관계 편의 메서드

결국 양방향 연관관계는 양쪽 다 신경 써야한다.
양방향 관계에서 두 코드는 하나인 것처럼 사용하는 것이 안전하다.

아래와 같이 리펙토링 하는 것이 안전하다

public class Member {
	public void setMember(Team team){
		this.team = team;
        team.getMembers().add(this);
   }

연관관계 편의 메서드 작성 시에는 주의사항이 있다.

member1.setTeam(teamA); // 연관관계 설정 member1->team1
member1.setTeam(teamB);

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

member1.setTeam(team);를 호출한 직후 객체 연관관계는

member1 <-> teamA 으로 서로 참조한다.

그 후 member1.setTeam(teamB); 를 호출하면

teamA -> member1 <-> teamB 인 관계를 나타낸다.

teamB로 변경할때 teamA -> member1로 관계를 제거하지 않았다.

public setTeam(Team team){ // 연관간계 설정
	if(this.team != null){
   	this.team.getMembers.remove(this);
   }
   
   this.team =team;
   team.getMembers().add(this);
}
	

정리

  1. 단방향 매핑만으로 테이블과 객체의 연관관계 매핑은 완료된 것이다.
  2. 단방향을 양방향으로 만들면 반대방향으로 객체 그래프 탐래프 탐색 기능이 추가된다.
  3. 양방향 연관관계를 매핑하려면 객체에서 양쪽 방향 모두를 관리해야한다.
  • 주의
    양방향 매핑 시 무한루프에 빠지지 않도록 주의야한다.
    엔티티의 toString 메서드를 호출할 시 무한로프에 빠질 가능성이 높다. (JSON으로 변환할 때)
    이를 방지하고자 request, response 객체를 따로 정의하는 것이 좋은 방법이다.

0개의 댓글