ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JPA > 연관관계 매핑
    BackEnd/JPA 2021. 4. 18. 01:02

    연관관계 매핑 기초

    객체와 테이블간 연관관계를 맺는 차이

    테이블은 FK 하나로 양방향이 되지만, (테이블은 PK, FK 연결된 키로 조인해 항상 양방향의 정보를 다 수집할 수 있다.)

    객체에는 단방향만 있다. 객체에서 양방향 관계를 만들기 위해서는 각 객체가 서로의 참조를 가지고 서로를 가르키는 두 개의 단방향 갖게 해야한다.

    위 표에서 객체의 연관관계는 Member 객체가 team 필드를 통해 Team 에 단방향을 갖고 있고, 

    테이블에서 연관관계는 MEMBER 테이블의 TEAM_ID 라는 외래키를 통해 양방향을 갖고있다고 할 수 있다.

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

     

    여러 회원은 한개의 팀에만 속할 수 있다는 조건에서 회원과 팀은 N : 1 의 관계를 갖게 된다. 

     

    엔티티 작성 시

    1. N:1 관계 명시는 필수적인 매핑정보이다.

    2. 다대일 관계에서 외래키 매핑 정보는 선택이지만 생략 시 디폴트 전략은 '필드명+"_"+테이블 기본 키 컬럼명 ' 이다.

    public class Member {
        @ManyToOne   //필수 정보 
        @JoinColumn(name="Team_ID") // 외래키 지정 - 선택적
        private Team team;
        ....    
    }

    @JoinColumn 외래키 매핑 

        디폴트 전략
    name 매핑할 외래 키 이름 필드명+"_"+테이블 기본 키 컬럼명
    refrencedColumnName 외래 키가 참조하는 대상 테이블 컬럼명 참조하는 테이블의 기본 키 컬럼명
    foreignKey(DDL) 외래 키 제약조건 직접 지정
    테이블 생성 시에만 사용
     
    unique
    nullable
    insertable
    updatable
    columnDefinition
    table
    @Column 속성과 같다  

    @ManyToOne 속성 

    optional false 이면 연관 엔티티가 항상 있어야함 true
    fetch 페치전략  
    cascade 영송석 전이 기능 사용  
    targetEntity 연관된 엔티티 타입 정보 설정.거의 사용되지 않음.
    컬렉션 사용해도 제네릭으로 타입정보 알 수 있음
    예시)
    @OneToMany
    private List<Member> member;

    @OneToMany(targetEntity=Member.class)
    private List member;

    연관관계가 있는 엔티티를 조회하는 방법은 크게 2가지이다.

    1. 객체 그래프 탐색(객체 연관관계 이용)

    Member member = entityManager.find(Member.class,"member1")
    Team team = member.getTeam();

     

    2. 객체지향 쿼리 사용 JPQL

    회원을 대상으로 조회하는데 팀1에 소속된 회원만 조회한다, 

    회원과 연관된 팀 엔티티를 검색조건으로 사용해야할 때, JPQL 의 조인을 사용한다.

    String jpql = "select m from Member m join m.team t where t.name=:teamName"
    
    List<Member> members = entityManager.createQuery(jpql, Member.class)
    				.setParameter("teamName","팀1")
    				.getResultList();

    팀의 입장에서 보면 하나의 팀이 여러 회원에게 속하므로 1: N 관계로 볼 수 있다.

    회원에 Team 이 매핑되어 있으니, 팀에서도 회원으로 연관관계를 설정하면 양방향 연관관계가 된다.

    양방향 연관관계를 맺을 때 mappedBy 속성으로 연관관계의 주인을 설정해야한다.

     

    public class Team {
        @OneToMany (mappedBy ="team")  //mappedBy 에 있는 이름은 Member 클래스에서 정의한 변수명
        private List<Member> members;  //컬렉션 
        ...
    }

    연관관계의 주인

    데이터베이스는 외래 키 (team_id) 두 테이블의 연관관계를 관리하는데, 

    객체에서는  외래키 하나로 서로를 참조하는 객체를 두 개 가진다.

    회원이 속한 팀이 바뀌면 , 선택지가 두 개 있다.

    1. Member 객체에서 team 을 변경한다.

    2. Team 객체에서 List로 관리하는 member 를 변경한다.

     

     

    Member 나 Team 둘 중 어떤 엔티티로 외래키를 관리할지 결정하는 것, 

    이걸 연관관계의 주인을 결정한다고 한다. 

    연관관계의 주인은 DB 연관관계와 매핑되고 외래 키를 관리(입력/수정/삭제)할 수 있다.

    연관관계 주인이 아닌 쪽은 readonly 성격을 가지며, mappyedBy 속성으로 주인을 지정한다.

     

    결론은 외래키가 있는 곳을 주인으로 정하는 것을 권고한다.

    1. Member 객체에서 TEAM_ID 를 관리하는 것

    > MEMBER 테이블에 있는 TEAM_ID 를 관리한다.

    2. Team 객체에서 Members를 관리한다.

    > TEAM 과 다른 테이블 MEMBER 의 TEAM_ID 를 관리한다.

    member.setTeam(team); //연관관계 주인에서 연관관계 설정
    
    team.getMembers().add(member); //무시

    * team 에서 member 를 add 하는 것은 데이터베이스에 update 되지 않지만, 

    순수 객체상태에서 기대하는 결과값을 얻기 위해서는 team 에도 member 를 add 해줘야한다.

    Team team = new Team();
    team.setName("A");
    em.persiste(team);
    
    Member member = new Member();
    member.setName("I");
    member.setTeam(team);
    em.persiste(member);
    
    team.getMembers().add(member);

    연관관계 편의 메서드

    하지만 매번 이렇게 작성하는 것이 번거로우니

    Member 에서 team 과 연관관계를 설정할 때, Team 에 값 세팅을 함께 해주는 팁이 있다.

    이 때, 주의할 점은 기존에 연관관계를 끊어줘야 안전하게 기대한 결과를 얻을 수 있다.

    * 기존 관계를 끊지 않아도 연관관계 주인이 아닌 테이블이기 때문에 DB 데이터와 관계가 없지만,

    하나의 영속성 컨텍스트에서 데이터 변경을 하고 결과를 조회하면 기대하지 않은 결과를 반환할 수 있다.

    @Entity
    class Member{
        @ManyToOne
        @JoinColumn(name="team_id")
        private Team team;
        
        public void setTeam(Team team){
        	if(this.team!=null) {      //중요!
                this.team.getMembers().remove(this);
            }    
        	this.team=team;
            team.getMembers().add(this); //양쪽에 값 세팅 
        }
        
    }

     

    왠만하면 단방향 매핑을 해 사용하고, 나중에 필요에 의해  양방향을 설정해도 테이블에 영향을 주지 않으니 

    그렇게 하는 것이 좋다.

     

    * 연관관계의 주인이라고 비지니스 로직에 더 중요한 것이 아니다!

    비지니스 중요도를 제거하고 단순히 외래 키 관리자의 의미만 부여해야 한다. 자동자와 바퀴의 관계에서 자동차가 더 중요해보이지만 바퀴가 외래키를 가진 연관관계 주인이다.

     

    * 양방향 매핑 주의점  - 무한 루프

    Member.toString 에서 getTeam 을 호출, Team.toString 에서 getMember 를 호출하면, 무한루프에 빠진다.

    Json라이브러리에서 무한루프에 빠지지 않도록 하는 어노테이션이나 기능을 제공한다.

     


    다양한 연관관계 매핑

    다중성 다대일, 일대다, 일대일, 다대다
    단방향, 양방향  
    연관관계 주인  

     

    1. 다 대 일 단방향 [ N:1 ]

    데이터베이스 테이블에서 외래키는 항상 다(N) 쪽에 있다.

    N의 멤버가 각각 1개의 팀만 가질 수 있다.

    1. 다 대 일 양방향 [ N:1 ][1:N ]

    데이터베이스 테이블에서 외래키는 항상 다(N) 쪽에 있다.

    N의 멤버가 각각 1개의 팀만 가질 수 있다.

    N 인 멤버가 연관관계의 주인 - 연관관계의 주인을 member 로 할 때 

     

    2. 일 대 다  단방향 [ 1 : N ]

    TEAM 과 MEMBER 의 관계에서 TEAM 이  MEMBER 의  FK 를 관리한다.

    자신의 테이블의 FK 를 관리하는 것이 일반적인데서 보면 특이한 구조라고 할 수 있다..

    DB에서 항상 N 인쪽에 FK 가 있기 때문에 생기는 모양이다.

    일대다 관계에서 JoinColumn을 명시하지 않으면 JPA 는 중간에 연결테이블을 만들어 연관관계를 관리한다.

     

    일대다 단방향보다 다대일 양방향을 사용하는 것이 낫다.

    본인 테이블에 외래키가 있으면 엔티티 저장과 연관관계 처리 insert 를 한번에 할 수 있는데, 

    다른 테이블에 있으면 update 를 추가로 실행해야한다.

    insert member /  insert team / update member set team_id 

     

    Team 의 members 에서 외래키를 관리하기 때문에 Member 를 저장할 때 Team 외래키가 아무 값도 저장되지 않기 때문에 Team 엔티티를 저장할 때 Team.members 정보를 확인해 회원테이블을 update 하는 것이다.

     

    성능문제도 있지만 관리도 부담스럽기 때문에, 일대다 단방향 대신 다대일 양방향을 사용하는 것이 낫다.

     

    3. 일대일 

    양쪽이 서로 하나의 관계만 가진다. 하나의 회원은 하나의 사물함만 사용하고 하나의 사물함도 한명의 회원에 의해서 사용된다.

    일대일 관계는 주 테이블이나 대상 테이블 둘 중 어느 곳에나 외래키를 가질 수 있다.

     

    1. 주테이블(MEMBER)에 외래키 

    주 객체가 대상 객체를 참조하는 것처럼 주 테이블에 외래 키를 두고 대상 테이블을 참조한다. 

    외래키를 객체 참조와 비슷하게 사용할 수 있다는 장점이 있다.

    주테이블이 외래키를 가져, 주 테이블만 확인해도 대상 테이블과 연관관계가 있는지 알 수 있다.

    단점:값이 없으면 외래키에 null 허용해야한다.

     

    2. 대상 테이블(LOCKER)에 외래키 

    테이블 관계를 일대일에서 일대다로 변경할 때 테이블 구조를 그대로 유지할 수 있다.

    단점:프록시 기능의 한계로 지연로딩을 설정해도 항상 즉시 로딩된다.

     

     

    4. 다대다 

    실무에서 사용 추천 하지 않는다.

    연결테이블을 추가해서 일대다, 다대일 관계로 풀어야한다.

    아래는 Member 와 Product 사이에 MemberProduct를 생성해 관계를 풀어나간다.

    @Entity
    public class Member {
        @OneToMany(mappedBy = "member")
        private List<Product> products = new ArrayList<>();
        ...
    }
    @Entity
    public class MemberProduct {
    
        @ManyToOne
        @JoinColumn(name="MEMBER_ID")
        private Member member;
    
        @ManyToOne
        @JoinColumn(name="PRODUCT_ID")
        private Product product;
    
    }
    @Entity
    public class Product {
    
        @OneToMany(mappedBy = "product")
        private List<MemberProduct> memberProduct = new ArrayList<>();
    
    }
    

     

    www.inflearn.com/course/ORM-JPA-Basic/dashboard

     

    자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

    JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 본 강의는 자바 백엔

    www.inflearn.com

     

    반응형

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

    JPA 경로표현식  (0) 2021.04.27
    JPA > JPQL, 프로젝션  (0) 2021.04.27
    JPA > 값 타입  (0) 2021.04.25
    JPA > 영속성 전이와 고아객체  (0) 2021.04.25
    JPA 기본  (0) 2021.04.04
Designed by Tistory.