ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JPA] 연관관계 매핑 기초
    DEV/JPA 2024. 3. 29. 17:00

    * 정보전달의 목적이 아닌 개인 스터디 정리 글 입니다.

     

    강의 : 인프런 <자바 ORM 표준 JPA 프로그래밍 기본편>

    교육자 : 김영한


    연관관계가 필요한 이유

    : 객체를 테이블에 맞추어 모델링 할 경우 문제

     

    [전제조건]

    회원과 팀이 있다.

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

    = 회원과 팀은 N:1 관계이다.

     

    1. 참조 대신 외래 키를 그대로 사용해야함

    @Entity
    public class Member {
        @Id @GeneratedValue
        private Long id;
        
        @Column(name = "USERNAME")
        private String name;
        
        @Column(name = "TEAM_ID")
        private Long teamId; // Member테이블의 외래키
     … 
    }
    
    @Entity
    public class Team {
        @Id @GeneratedValue
        private Long id;
        private String name; 
     … 
    }

     

    2. 외래 키 식별자를 직접 다뤄야함

    //Member 테이블에 team id를 같이 저장해야하는 문제 발생
    //팀 저장
    Team team = new Team();
    team.setName("TeamA");
    em.persist(team);
    
    //회원 저장
    Member member = new Member();
    member.setName("member1");
    member.setTeamId(team.getId());
    em.persist(member);

     

    3. 식별자로 다시 조회해야함. 즉, 객체 지향적인 방법은 아님

    //Member가 속한 팀을 찾을 때 번거로운 문제 발생
    //조회
    Member findMember = em.find(Member.class, member.getId()); 
    
    Long findTeamId = findMember.getTeamId();
    Team findTeam = em.find(Team.class, findTeamId));

     

    => 객체를 테이블에 맞추어 데이터 중심으로 모델링하면 협력관계를 만들 수 없다.

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

    - 객체는 참조를 사용해서 연관된 객체를 찾는다.

     

     

    단방향 연관관계

    : 객체 지향 모델링

     

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

    - 관계 매핑 + 조인 컬럼 매핑
    @ManyToOne, @JoinColumn

    @Entity
    public class Member {
        @Id @GeneratedValue
        @Column(name = "MEMBER_ID")
        private Long id;
        
        @Column(name = "USERNAME")
        private String name;
        
       /* @Column(name = "TEAM_ID")
        private Long teamId;*/
        @ManyToOne
        @JoinColumn(name = "TEAM_ID")
        private Team team;
        ...
    }

     

    2. 연관 관계 저장

    //팀 저장
    Team team = new Team();
    team.setName("TeamA");
    em.persist(team);
    
    //회원 저장
    Member member = new Member();
    member.setName("member1");
    //member.setTeamId(team.getId()); //X
    member.setTeam(team); //단방향 연관관계 설정, 참조 저장
    em.persist(member);

     

    3. 참조로 연관관계 조회 : 객체 그래프 탐색

    //조회
    Member findMember = em.find(Member.class, member.getId());
    //참조를 사용해서 연관관계 조회
    //Long findTemaId = findMember.getTeamId(); //X
    //Team findTeam = em.find(Team.class, findTemaId); //X
    Team findTeam = findMember.getTeam();

     

    조회를 해보면 영속성 컨텍스트로 인해 1차캐시에서 값을 가져온다.
    DB에서 조회하고 싶다면 조회 전
    em.flush();
    em.clear();
    선언 후 실행 하면 된다.

     

    4. 연관관계 수정

    // 새로운 팀B
    Team teamB = new Team();
    teamB.setName("TeamB");
    em.persist(teamB);
    
    // 회원1에 새로운 팀B 설정
    member.setTeam(teamB);

     

     

    양방향 연관관계와 연관관계의 주인

     

    기존 관계형 데이터베이스의 테이블연관관계를보면
    방향과 관계없이 외래키만 있으면 조인해서 양쪽 테이블 모두 조회가 가능하다.

    그런데 객체는? ManyToOne의 반대편에 해당하는 클래스에 구현 가능하지만 객체는 가급적 양방향보단 단방향이 좋다.

     

    양방향 매핑

    1. Team 클래스에 컬렉션(List) 추가

    @ManyToOne의 반대인 @OneToMany 선언

    mappedBy : 어떤 객체에 의해 매핑 되었는지 

    //Member class는 동일
    @Entity
    public class Member {
        @Id @GeneratedValue
        private Long id;
        
        @Column(name = "USERNAME")
        private String name;
        
        @ManyToOne
        @JoinColumn(name = "TEAM_ID")
        private Team team;
     … 
    }
    
    @Entity
    public class Team {
        @Id @GeneratedValue
        private Long id;
        private String name;
        
        @OneToMany(mappedBy = "team")
        List<Member> members = new ArrayList<Member>()
     … 
    }

     

    2. 반대방향으로 객체 그래프 탐색

    //조회
    Team findTeam = em.find(Team.class, team.getId()); 
    int memberSize = findTeam.getMembers().size(); //역방향 조회

     

     

    연관관계의 주인과 mappedBy

     

    - 객체 연관관계 = 2개

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

    : 객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단뱡향 관계 2개다.

    객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어 야 한다.

     

    - 테이블 연관관계 = 1개

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

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

    MEMBER.TEAM_ID 외래 키 하나로 양방향 연관관계 가짐 (양쪽으로 조인 가능)

     

    양방향 매핑 규칙

    • 객체의 두 관계중 하나를 연관관계의 주인으로 지정
    • 연관관계의 주인만이 외래 키를 관리(등록, 수정)
    • 주인이 아닌쪽은 읽기만 가능
    • 주인은 mappedBy 속성 사용X
    • 주인이 아니면 mappedBy 속성으로 주인 지정

     

    주인은 누구로 ?
    N:1관계에서 N인곳을 주인으로. 즉 외래키가 있는 곳을 주인으로 설정해야한다.
    1을 insert했을때 N이 update되는 헷갈리는 상황이 발생할수있기때문.

     

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

    : 연관관계의 주인에 값을 입력하지 않음

    Team team = new Team();
    team.setName("TeamA");
    em.persist(team);
    Member member = new Member();
    member.setName("member1");
    
    //역방향(주인이 아닌 방향)만 연관관계 설정
    team.getMembers().add(member);
    em.persist(member)

     

    DB 값 확인하면 TEAM_ID = NULL

     

    양방향 매핑 시 주인에 값을 입력해야한다.

    순수 객체관계를 고려하면 항상 양쪽 다 값을 셋팅해주는게 맞다.

    Team team = new Team();
    team.setName("TeamA");
    em.persist(team);
    Member member = new Member();
    member.setName("member1");
    team.getMembers().add(member); 
    
    //연관관계의 주인에 값 설정
    member.setTeam(team);
    em.persist(member);

     

    순수 객체 상태를 고려해서 항상 양쪽에 값을 설정할것 !

    1. 연관관계 편의 메서드를 생성하자

    : 메서드를 1이나 N 어느쪽에 넣어도 상관없음. 양방향 값 세팅시 setter에 설정.
    단, 단순 값 세팅이 아닌 양방향 값 세팅이므로 setTeam대신 changeTeam로 명명하도록하거나

    또는 team클래스에 addMember메서드를 추가 하는 방식 사용

    * 주의 : 둘 중 하나만 사용할것. 무한루프 위험

    //1. Member클래스에 편의 메서드 changeTeam 추가 예시
    public void changeTeam(Team team) {
            this.team = team;
            team.getMembers().add(this);
    }
    
    //Main
    Team team = new Team();
    team.setName("TeamA");
    em.persist(team);
    Member member = new Member();
    member.setName("member1");
    
    member.changeTeam(team);
    em.persist(member);
    
    
    //2. Team클래스에 편의 메서드 addMember 추가 예시
    public void addMember(Member member) {
            member.setTeam(this);
            members.add(member);
    }
    
    //Main
    Team team = new Team();
    team.setName("TeamA");
    em.persist(team);
    Member member = new Member();
    member.setName("member1");
    
    team.addMember(member);
    em.persist(member);

     

    2. 양방향 매핑시에 무한 루프를 조심하자

    예: toString(), lombok, JSON 생성 라이브러리

     

    강사님 추가 조언 )

    Controller에서 Entitiy 반환 하지 말자 => DTO로 반환하자
    api통신 등의 이유로 json으로 바로 return했을경우 문제
    1. 무한루프가 생길수 있다
    2. 엔티티변경 시 api 까지 바뀔 수 있다.

     

     

    양방향 매핑 결론

    : 단방향 매핑만으로도 이미 연관관계 매핑은 완료한 것

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

    'DEV > JPA' 카테고리의 다른 글

    [JPA] 고급매핑 - 상속관계 매핑  (0) 2024.04.05
    [JPA] 다양한 연관 관계 매핑 ( N:1 / 1:N / 1:1 / N:M )  (1) 2024.04.04
    [JPA] 엔티티 매핑  (0) 2024.03.07
    [JPA] 영속성 관리  (0) 2024.02.22
    [JPA] 시작하기, 기본 설정  (0) 2024.02.14

    댓글

Designed by Tistory.