JPA

[JPA] 연관 관계 매핑 기초2 (양방향)

나는시화 2024. 1. 9. 17:02

예제 시나리오(양방향)

  • 회원과 팀이 있다. 
  • 회원은 하나의 팀에만 소속될 수 있다. 
  • 회원과 팀은 다대일 관계다.

Member Entity

  • Member Entity는 바뀐 것이 없다. 
package jbabook.jpa.shop.domain;

import jakarta.persistence.*;

@Entity
public class Member {
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    @Column(name = "USERNAME")
    private String username;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "TEAM_ID")
    private Team team; // Team Entity와 조인 
    public Team getTeam() {
        return team;
    }
    public void setTeam(Team team) {
        this.team = team;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
}

Team Entity

  • @OneToMany 어노테이션을 사용 
  • mappedBy = "" 사용해주어야함. (Member Entity에서 Team이 어떤 변수명으로 사용되고 있는지 매핑.)
  • 연관관계 주인(양방향 매핑 규칙)
    • 다(Member) 쪽에서 외래 키를 관리해주어야 함.
    • 객체의 두 관계중 하나를 연관관계의 주인으로 지정
    • 연관관계 주인만이 외래 키를 관리(등록, 수정)
    • 주인이 아닌 쪽은 읽기만 가능 
    • 주인은 mappedBy 속성 사용X
    • 주인이 아니라면 mappedBy 속성으로 주인 지정
package jbabook.jpa.shop.domain;

import jakarta.persistence.*;

import java.util.ArrayList;
import java.util.List;

@Entity
public class Team {
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    @Column(name = "TEAM_NAME")
    private String name;
    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Long getId() {
        return id;
    }
    public List<Member> getMembers() {
        return members;
    }
}

package jbabook.jpa.shop;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Persistence;
import jbabook.jpa.shop.domain.Member;
import jbabook.jpa.shop.domain.Team;

import java.util.List;

public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try {
            Team team = new Team();
            team.setName("teamName1");
            em.persist(team);

            Member member = new Member();
            member.setUsername("name1");
            Member member2 = new Member();
            member2.setUsername("name2");
            member.setTeam(team);
            member2.setTeam(team);
            em.persist(member);
            em.persist(member2);

            em.flush();
            em.clear();

            Member findMember = em.find(Member.class,member.getId());
            List<Member> members = findMember.getTeam().getMembers();
            for(Member mem : members){
                System.out.println("members name = " + mem.getUsername());
            }

            tx.commit();
        } catch (Exception e){
            tx.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}

member -> team , team -> membber 양방향에서 데이터 조회 가능 


주의!

  • 웬만하면 단방향으로 설계하기
  • member.setTeam() 메서드로 연관관계 주인한테만 데이터를 집어넣어도 DB에서 가져올 때는 정상적으로 가져와짐. 
  • DB에서 가져오는 것이 아닌 1차 캐시에서 가지고 올 때는 빈 배열을 가지고옴. 
  • 따라서 객체지향적으로 셋팅하기 위해선 데이터를 양쪽에 넣어주는 게 맞음. 
    • 예) member.setTeam(team); team.getMembers().add(member);
package jbabook.jpa.shop;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Persistence;
import jbabook.jpa.shop.domain.Member;
import jbabook.jpa.shop.domain.Team;

import java.util.List;

public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try {
            Team team = new Team();
            team.setName("teamName1");
            em.persist(team);

            Member member = new Member();
            member.setUsername("name1");
            em.persist(member);
			team.getMembers(); // 빈 배열 
            team.getMembers().add(member); // 이걸 안 넣어주면 객체답지가 않음. 또한 em.flush()와 em.clear(); 가 없어서 데이터가 출력되지 않음. 

            Team findTeam = em.find(Team.class,team.getId());
            List<Member> members = findTeam.getMembers();
            for(Member mem : members){
                System.out.println("mem.getUsername = " + mem.getUsername());
            }

            tx.commit();
        } catch (Exception e){
            tx.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}

 

아래는 연관관계 편의 메서드를 생성한 것.(Member Entity의 setTeam() 메서드)

public void setTeam(Team team) {
    this.team = team;
    team.getMembers().add(this);
}

양방향 매핑 정리

  • 단방퍙 매핑만으로도 이미 연관관계 매핑은 완료
  • 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
  • JPQL에서 역방향으로 탐색할 일이 많음 
  • 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨(테이블에 영향을 주지 않음)