Backend

[JPA] 연관관계 매핑

연_우리 2022. 2. 20. 23:27
반응형

목차

     

     

     

    목표 : "객체의 참조와 테이블의 외래키를 매핑"

     

     

    시나리오

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

     - N명의 회원은 1개의 팀에 소속될 수 있다.

     - 1개의 팀은 N명의 회원을 가질 수 있다.

     

     

    객체를 테이블에 맞추어 모델링 : 외래키를 그대로 객체에 구현

    Team team = new Team();
    team.setName("TeamA");
    em.persist(team);
    
    Member member = new Member();
    member.setName("member1");
    member.setTeamId(team.getId());	//⭐⭐⭐ 식별자 저장
    em.persist(member);
    
    
    Member findMember = em.find(Member.class, member.getId());
    Team findTeam = em.find(Team.class, team.getId());	//⭐⭐⭐ 식별자 조회

     

    데이터 중심 모델링의 문제점

    테이블은 외래키로 조인을 사용하여 연관된 테이블을 찾는다.

    하지만 객체는? 참조를 사용하여 연관된 객체를 찾는다.

    => 어디에..맞추지?!

     

     

     

    객체 지향 모델링 : 객체 연관관계 사용

    JPA에게 객체 간 연관관계를 알려주어서 테이블에도 연관관계가 적용되도록 해야한다.

     

     

    단방향 매핑

    1. 연관관계 파악 : 누가 1이고 누가 N인가?

                            Team이 1이고 Member가 N이다. 

                            Team테이블에 PK가 있고 Member테이블에 FK가 있다.

     

    2. N에 @ManyToOne, @JoinColumn을 작성한다

     

    @Entity
    public class Member{
    	@Id @GeneratedValue
        private Long id;
        private String username;
        @ManyToOne				//⭐⭐⭐ Member가 N(many), Team이 1(one)이다.
        @JoinColumn(name = "team_id")	//⭐⭐⭐ team_id로 FK 설정한다.
        private Team team;
    }
    
    @Entity
    public class Team{
    	@Id @GeneratedValue
        private Long id;
        private String name;
    }
    Team team = new Team();
    team.setName("TeamA");
    em.persist(team);
    
    Member member = new Member();
    member.setName("member1");
    member.setTeam(team);	//⭐⭐⭐ 참조 저장
    em.persist(member);
    
    
    Member findMember = em.find(Member.class, member.getId());
    Team findTeam = fiindMember.getTeam();		//⭐⭐⭐ 참조 조회

     

    양방향 매핑

    1. 연관관계 파악 : 누가 1이고 누가 N인가?

                            Team이 1이고 Member가 N이다. 

                            Team테이블에 PK가 있고 Member테이블에 FK가 있다.

                            Team객체는 List<Member>가 있고 Member객체는 Team이 있다.

                            Team이 상대(Member)를 N개 가져올 수 있고, Member는 상대(Team)을 1개 가져올 수 있다.

     

    2. N에 @ManyToOne, @JoinColumn을 작성한다

       1에 @OneToMany(mappedBy="~")를 작성한다.

     

    @Entity
    public class Member{
    	@Id @GeneratedValue
        private Long id;
        private String username;
        @ManyToOne				//⭐⭐⭐ Member가 N(many), Team이 1(one)이다.
        @JoinColumn(name = "team_id")	//⭐⭐⭐ team_id로 FK 설정한다.
        private Team team;
    }
    
    @Entity
    public class Team{
    	@Id @GeneratedValue
        private Long id;
        private String name;
        @OneToMany(mappedBy = "team")	//⭐⭐⭐ Team이 1(one), Member가 N(many)이다.
        private List<Member> members = new ArrayList<>();	//add할때 NullPointerException안뜨게!
        //mappedBy에는 One에 해당되는 객체의 어떤 필드와 연관되어있는지 작성
    }
    Team team = new Team();
    team.setName("TeamA");
    em.persist(team);
    
    Member member = new Member();
    member.setName("member1");
    member.setTeam(team);	//⭐⭐⭐ 참조 저장
    em.persist(member);
    
    
    Member findMember = em.find(Member.class, member.getId());
    Team findTeam = fiindMember.getTeam();		//⭐⭐⭐ 참조 조회
    List<Member> members = findTeam.getMembers();	//⭐⭐⭐ 참조 조회

     

     

    연관관계의 주인

    객체의 연관관계에는 방향이 있지만, 테이블의 연관관계에는 방향이 없다!

     

     

    양방향 매핑의 경우를 생각해보자.

    객체 테이블
    Member → Team 단방향 1개
    Member ← Team 단방향 1개
    Member ↔ Team 양방향 1개

    객체의 양방향관계는 서로 다른 단방향관계 2개이다.

    테이블은 외래키 하나로 두 테이블의 연관관계를 관리한다. (조인)

     

    연관관계의 주인 : 테이블에서 외래키가 있는 곳을 주인으로 하자

    양방향 매핑 규칙

     - 두 객체 중 하나만 연관관계의 주인으로 지정

     - 연관관계의 주인만이 외래키를 관리(등록, 수정)

     - 주인이 아닌 쪽은 읽기만 가능

     - 주인이 아닌쪽이 mappedBy속성을 사용한다. (이미 누군가에 의해 연관이 되어져있다.)

     

     

    테이블에서 외래키가 있는 곳의 반대를 주인으로하면??

    여기서 Team을 주인으로 한다고 생각해보자.

    List<Member>를 수정하면 Team에 쿼리가 나가는 것이 아닌 Member에 쿼리가 나가게된다. (수정 주체는 Team인데..)

     

     

    양방향 매핑 시 가장 많이 하는 실수!

    연관관계의 주인이 아닌 곳에 값을 넣는 것

    현재 Member가 연관관계의 주인이고, Team은 주인이 아니다.

    Member member = new Member();
    member.setName("member1");
    //member.setTeam(team);	//⭐⭐⭐ 주석처리
    em.persist(member);
    
    
    Team team = new Team();
    team.setName("TeamA");
    team.getMembers().add(member);	//⭐⭐⭐ 참조저장
    em.persist(team);

    주인이 아닌 곳(Team)에 Member를 추가했다.

    위 코드의 결과는?

    Team에 null이 들어갔다.

    주인이 아닌 곳(Team)에 Member를 추가해도 DB에 반영되지 않는다.

     

     

    Team team = new Team();
    team.setName("TeamA");
    //team.getMembers().add(member);	//⭐⭐⭐ 주석처리
    em.persist(team);
    
    
    Member member = new Member();
    member.setName("member1");
    member.setTeam(team);	//⭐⭐⭐ 참조저장
    em.persist(member);

    주인인 곳(Member)에 Team을 추가했다.

    위 코드의 결과는?

    값이 정상적으로 들어갔다.

     

    (..일단 헷갈린다.)

     

    객체입장에서 생각해보자.

    Member입장에서는 setTeam(team)을 해주어야 Team값이 입력되고

    Team입장에서는 add(member)을 해주어야 Member가 리스트에 저장된다.

     

    결론 : 양방향 매핑은 연관관계의 주인이던 아니던 둘다 값을 넣어주자!!

     

     

    아니면, 연관관계의 주인인 Member의 setTeam()메서드를 아래와 같이 수정하자

    (아예 리스트에 넣어주는 로직까지 추가!)

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

     

     

     

    양방향 매핑 시 무한루프 조심!

     - toString()

    public class Member{
        @Override
        public String toString(){
        	return "Member{" + "id=" + id + ", username=" + username + ", team=" + team + "}";
        }
    }
    
    public class Team{
        @Override
        public String toString(){
        	return "Team{" + "id=" + id + ", name=" + name + ", members=" + members + "}";
        }
    }

    Member의 team에서 Team을 호출

    Team의 members에서 다시 리스트의 크기만큼 Member호출....

     

    컨트롤러에서 Entity를 JSON으로 바꿔서 그대로 응답해주게되면

    JSON생성 라이브러리가 마찬가지로 계속 무한루프로 호출하게된다!

     

     

     

    정리

     - 되도록 단방향 매핑으로 연관관계 매핑을 끝내자

     - 양방향 매핑은 객체 그래프 탐색기능이 추가된 것 뿐이다.

     - 정말 필요한 경우에만! 양방향 매핑을 걸어주자

     

     

     

     

     

    참고 : 자바 ORM 표준 JPA 프로그래밍 - 기본편
    반응형
    • 네이버 블러그 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 구글 플러스 공유하기
    • 카카오톡 공유하기