본문 바로가기
Study/자바 ORM 표준 JPA 프로그래밍

5장. 연관관계 매핑 기초

반응형

본 포스트는 사내 스터디로 진행되는 자바 ORM 표준 JPA 프로그래밍 관련 정리입니다

 

인프런 강의도 있습니다! 짱짱

https://www.inflearn.com/course/ORM-JPA-Basic

 

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

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

www.inflearn.com


5장. 연관관계 매핑 기초

  • 방향성
    • 단방향: 관계에서 어느 한 쪽만 참조하는 것을 단방향 관계라고 한다
    • 양방향: 관계에서 양쪽 모두가 서로를 참조하는 것을 양방향 관계라고 한다
  • 다중성
    • 다대일(N:1)
    • 일대다(1:N)
    • 일대일(1:1)
    • 다대다(N:M)
  • 연관관계 주인: 객체를 양방향 연관관계로 만들면 연관관계의 주인을 정해야한다
  • 객체의 연관관계 vs 테이블 연관관계
    • 객체는 참조(주소)로 연관관계를 맺는다 ( 객체  그래프를 이용해 탐색 )
    • 테이블은 외래 키(FK)로 연관관계를 맺는다(JOIN 사용해 조회 )

 

5-1. 단방향 연관관계

  • 참조를 통한 연관관계는 언제나 단방향이다
  • 객체간에 연관관계를 양방향으로 만들고 싶으면 반대쪽에도 필드를 추가해서 참조를 보관해야한다
  • 정확히 얘기하면 양방향 관계는 없고, 서로 다른 방향을 참조하는 단방향 관계 2개가 있을 뿐이다
  • 예제 ( Member와 Team 관계 매핑, Member는 Team을 보유한다 )
    // 1:N
    
    @Entity
    public class Member {
    
        @Id
        @Column(name = "MEMBER_ID")
        private String id;
        
        private String username;
        
        @ManyToOne
        @JoinColumn(name="TEAM_ID")
        private Team team;
        
        public void setTeam(Team team) {
            this.team = team;
        }
        
        ...
    }​


    // 1:N
    
    @Entity
    public class Team {
    
        @Id
        @Column(name = "TEAM_ID")
        private String id;
        
        private String name;
        
        ...
    }​
    • @ManyToOne
      • 다대일(N:1) 관계라는 매핑 정보
      • 회원(다)과 팀(일)은 다대일 관계
      • 연관관계 매핑시 다중성을 다타내는 어노테이션을 필수로 붙여야한다
      • 속성
        속성 기능 기본값
        optional false로 설정하면 연관된 엔티티가 항상 있어야 한다 true
        fetch 글로벌 페치 전략을 사용 @ManyToOne = FetchType.EAGER
        @OneToMany = FetchType.LAZY
        cascade 영속성 전이 기능을 사용  
        targetEntity
        ( 거의 사용 안함 )
        연관된 엔티티의 타입 정보를 설정  
    • @JoinColumn(name="TEAM_ID")
      • 외래 키를 매핑할 때 사용
      • name 속성에는 매핑할 외래 키 이름(컬럼명, Java 변수명 아님)을 지정
      • 속성
        속성 기능 기본값
        name 매핑할 외래 키 이름 필드명 + '_' + 참조하는 테이블의 기본 키 컬럼 명
        referencedColumnName 외래 키가 참조하는 대상 테이블의 컬럼명 참조하는 테이블의 기본 키 컬럼명
        foreignKey(DDL) 외래 키 제약조건 정의  
        unique
        nullable
        insertable
        updatable
        columnDefinition
        table
        @Column의 속성과 같음

 

5-2. 연관관계 사용

  • 저장
    // persist
    
    public void testSave() {
        // 팀1 저장
        Team team1 = new Team("team1", "팀1");
        em.persist(team1);
        
        // 회원1 저장
        Member member1 = new Member("member1", "회원1");
        member1.setTeam(team1); // 연관관계 설정 member1 -> team1
        em.persist(member1);
        
        // 회원2 저장
        Member member2 = new Member("member2", "회원2");
        member2.setTeam(team1); // 연관관계 설정 member2 -> team1
        em.persist(member2);
    }​
    • "team1"이라는 Team 객체를 만들어 영속시킴
    • 만들어진 team1을 "member1"과 "member2"에 각각 연관관계를 설정한 후 각 Member 객체를 영속시킴
    • 반드시 연관관계를 설정하려는 대상은 영속상태여야 한다 ( Team 객체 )
      • 만약 team1이 persist가 안된 객체 상태로 member1, member2에 연관관계 설정이 된다면 JPA는 아무런 연관관계 설정도 하지 않는다
    • 발생하는 쿼리
      -- SQL
      
      INSERT INTO TEAM (TEAM_ID, NAME) VALUES ('team1', '팀1');
      INSERT INTO MEMBER (MEMBER_ID, NAME, TEAM_ID) VALUES ('member1', '회원1', 'team1');
      INSERT INTO MEMBER (MEMBER_ID, NAME, TEAM_ID) VALUES ('member2', '회원2', 'team1');
  • 조회
    • 연관관계가 있는 엔티티를 조회하는 방법
      • 객체 그래프 탐색(객체 연관관계를 사용한 조회)
        • 자바에서 참조된 객체를 사용하는 것 처럼 (getter) getXXX()을 사용해서 참조된 엔티티를 조회하는 것
      • 객체지향 쿼리 사용(JPQL)
        • JPQL 조인을 이용해 조회한다
          // JPQL
          
          private static void queryLogicJoin(EntityManager em) {
          
              String jqpl = "select m from Member m join m.team t where " +
                            "t.name=:teamName";
                            
              List<Member> resultList = em.createQuery(jpql, Member.class)
                                          .setParameter("teamName", "팀1")
                                          .getResultList();
              
              ...
          }
        • 실제 실행되는 쿼리
          -- SQL
          
          SELECT M.* FROM MEMBER MEMBER
          INNER JOIN
              TEAM TEAM ON MEMBER.TEAM_ID = TEAM1_.ID
          WHERE
              TEAM1_.NAME='팀1';​
  • 수정
    • 변경 감지를 통한 엔티티 수정
      // modify
      
      private static void updateRelation(Entitymanager em) {
      
          Team team2 = new Team("team2", "팀2");
          em.persist(team2);
          
          Member member = em.find(Member.class, "member1");
          member.setTeam(team2);
      }​

      • 실제 발생하는 쿼리
        -- SQL
        
        UPDATE MEMBER
        SET
            TEAM_ID='team2', ...
        WHERE
            ID='member1';​
      • Update와 같은 다른 EntityManager의 메서드 호출없이도 영속상태의 엔티티를 변경하려면 위 코드처럼 수정만 해주면 된다
      • 영속성 컨텍스트에는 이미 초기 Database에서 꺼내온 값 혹은 저장된 값이 스냅샷으로 관리되고 있으며, 트랜잭션이 커밋하고 플러시가 일어날때 해당 스냅샷과 현재 Entity의 값을 비교하여 변경 감지 기능이 일어난다
  • 삭제
    • 연결된 참조 객체를 null로 만들면 된다 ( 트랜잭션 flush가 일어날 때 JPA가 자동으로 변경감지 )
    • 유의사항
      • 연관된 엔티티를 삭제하려면 반드시 기존에 연관관계를 먼저 제거하고 마지막으로 참조 객체를 지워야한다
      • 외래 키 제약조건으로 인해 데이터베이스에서 오류가 발생한다
      • 실제 운영 환경에서는 외래 키 제약조건을 거는 것이 부담스러워 안 걸기도 하니 상황에 따라 판단하면 될 것 같습니다

 

5-3. 양방향 연관관계

  • 양방향 매핑은 참조되는 두 관계속 엔티티들이 서로를 단방향으로 참조하면 된다
  • 기존 Team 객체 수정
    // Team Entity
    
    @Entity
    public class Team {
    
        @Id
        @Column(name="TEAM_ID")
        private String id;
        
        private String name;
        
        // 추가된 부분
        @OneToMany(mappedBy = "team")
        private List<Member> members = new ArrayList<>();
        
        ...
    }​

    • Member -> Team: 다대일 관계(@ManyToOne), 다에 속하는 Member가 연관관계 주인으로써 @JoinColumn을 갖는다
    • Team -> Member: 일대다 관계(@OneToMany), 일에 속하는 Team, mappedBy 속성으로 반대쪽 연관관계 주인의 매핑 필드 이름(자바, 참조하고 있는 변수 이름)을 적어준다
  • 기존 참조 객체 조회와 같이 객체 그래프 탐색을 사용해서 조회할 수 있다

 

5-4. 연관관계의 주인

  • @OneToMany를 추가하여 양방향 연관관계를 맺을 때, mappedBy 속성은 왜 필요할까?
    • 엔티티를 양방향 연관관계로 설정하면 객체의 참조(단방향 참조)는 둘인데 외래 키는 하나인 상황이 발생한다
    • 참조되는 두 개의 단방향 관계 중 어느 쪽에서 외래 키를 관리해야하는가 하는 물음이 남게되는데 이 외래 키를 관리하는 곳을 연관관계의 주인 이라고 한다
  • 연관관계의 주인
    • 연관관계의 주인만이 데이터베이스 연관관계와 매핑되고, 외래 키를 관리(등록, 수정, 삭제)할 수 있다
    • 반면 주인이 아닌 쪽은 읽기만 할 수 있다
    • 연관관계 주인은 mappedBy 속성을 사용하지 않는다
    • 주인이 아닌 곳에서 mappedBy 속성을 사용해 연관관계 주인을 지정해줘야한다
  • 누가 연관관계의 주인이 되어야할까?
    • 일반적으로 다쪽에 연관관계 주인을 매핑한다
    • 연관관계 주인이 된다는 것은 외래 키(FK) 관리자가 된다는 뜻인데 TEAM과 MEMBER의 관계를 생각해봤을때 관계중 다쪽이 되는 MEMBER에 TEAM 외래 키를 설정하게 되고, 연관관계의 주인을 MEMBER가 가져가야 동일한 테이블에서 외래 키를 관리 할 수 있게 된다
    • 따라서 다쪽에 매핑하는 @ManyToOne에는 mappedBy 속성이 없고, @OneToMany쪽에만 mappedBy 속성이 존재한다

 

5-5. 양방향 연관관계의 주의점

  • 가장 흔히 하는 실수는 연관관계의 주인에는 값을 입력하지 않고, 주인이 아닌 곳에만 값을 입력하는 것이다
  • 데이터 베이스에 외래 키 값이 정상적으로 저장되지 않으면 해당 사항부터 의심해보자
  • 해결 방법, 연관관계 편의 메소드를 만들자
    • 연관관계 주인에도 셋팅하고, 반대편인 곳에서도 셋팅하는 두 번의 메서드를 호출하다 보면 둘 중 하나만 호출하여 양방향이 깨질 수 있다
    • 연관관계 편의 메소드 설정
      // 연관관계 편의 메소드
      
      public class Member {
      
          private Team team;
          
          public void setTeam(Team team) {
          
              // 기존 팀과의 관계 삭제
              if (this.team != null) {
                  this.team.getMembers().remove(this);
              }
              
              this.team = team;
              team.getMembers().add(this);
          }
          
          ...
      
      }​
    • 중간의 기존 팀과의 관계 삭제 등 양방향 관계 설정시 신경써야 할 부분이 많다
    • 또한 의존성 싸이클이 돌며, 심지어 자칫 잘못하면 toString() 등의 메서드 호출 부분에서 무한 루프에 빠져 OOM이 일어날 수 도 있다
    • 단방향 매핑만으로 테이블과 객체의 연관관계 매핑은 이미 끝났으므로, 개인적으로는 반드시 필요한 상황이 아니면.. 양방향 연관 관계는 안맺는게 좋을 것 같다

 

728x90
반응형

'Study > 자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글

6장. 다양한 연관관계 매핑  (0) 2021.09.13
4장. 엔티티 매핑  (0) 2021.08.31
3장. 영속성 관리  (0) 2021.08.22
1장. JPA 소개  (0) 2021.08.09