글 검색

공개된 글의 제목과 본문을 검색합니다.

    닫기 Esc


    1. 단방향 연관관계#

    • 연관관계 중에선 다대일(N:1) 단방향 관계를 가장 먼저 이해해야 한다.
    • 회원과 팀의 다대일 관계 예시
      • 회원과 팀이 있다.
      • 회원은 하나의 팀에만 소속될 수 있다.
      • 회원과 팀은 다대일 관계다.
    • 객체 연관관계
      • 회원 객체는 Member.team 필드로 팀 객체와 연관관계를 맺는다.
      • 회원은 Team에 접근 가능하지만 반대 방향은 접근하는 필드가 없다.
    • 테이블 연관관계
      • 회원 테이블은 TEAM_ID 외래 키로 팀 테이블과 연관관계를 맺는다.
      • 회원 테이블과 팀 테이블은 양방향 관계로 TEAM_ID를 통해서 서로 조인이 가능하다.
    • 객체 연관관계와 테이블 연관관계의 가장 큰 차이
      • 참조를 통한 연관관계는 언제나 단방향으로 양방향 관계를 만드록 싶다면 반대쪽에도 필드를 추가해 참조를 보관해야 한다.
      • 서로 참조하는 것을 양방향 연관관계라 하지만 정확히는 양방향이 아닌 서로 다른 단방향 관계 2개다.
      • 반면 테이블은 외래 키 하나로 양방향으로 조인 가능하다.
    • 객체 연관관계 VS 테이블 연관관계 정리
      • 객체는 참조로 연관관계를 맺는다.
      • 테이블은 외래키로 연관관계를 맺는다.

    1.1. 순수한 객체 연관관계#

    java
    @Entity
    public class Member {
    
        @Id
        @Column(name = "MEMBER_ID")
        private Long id;
    
        private String username;
    
        //연관 관계 매핑
        @ManyToOne
        @JoinColumn(name="TEAM_ID")
        private Team team;
    
        //연관관계 설정
        public void setTeam(Team team) {
            this.team = team;
        }
    
        //Getter, Setter ...
    }
    
    public class Team {
    		private String id;
    		private String name;
    
    		//Getter, Setter ...
    }
    java
    //팀 저장
    Team team = new Team();
    team.setName("wooteco"); 
    em.persist(team);
    
    //회원 저장
    Member member = new Member();
    member.setName("conas");
    member.setTeamId(team.getId());
    em.persist(member);

    1.2. 테이블 연관관계#

    sql
    CREATE TABLE MEMBER {
        MEMBER_ID VARCHAR(255) NOT NULL,
        TEAM_ID VARCHAR(255),
        USERNAME VARCHAR(255),
        PRIMARY KEY (MEMBER_ID)
    }
    
    CREATE TABLE TEAM {
        TEAM_ID VARCHAR(255) NOT NULL,
        NAME VARCHAR(255),
        PRIMARY KEY (TEAM_ID)
    }
    
    //회원 테이블의 TEAM_ID에 외래 키 제약조건 설정
    ALTER TABLE MEMBER ADD CONSTRAINT FK_MEMBER_TEAM
        FOREIGN KEY (TEAM_ID)
        REFERENCES TEAM
    테이블 DDL
    sql
    INSERT INTO TEAM(TEAM_ID, NAME) VALUES('team1', '팀1');
    INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME)
    VALUES('member1', 'team1', '회원1');
    INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME)
    VALUES('member2', 'team1', '회원2');
    회원1과 회원2을 팀1에 소속
    sql
    SELECT T.* FROM MEMBER M JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
    WHERE M.MEMBER_ID = 'member1'
    회원1이 소속된 팀 조회

    1.3. 객체 관계 매핑#

    java
    @Entity
    public class Member {
    
        @Id
        @Column(name = "MEMBER_ID")
        prviate String id;
        
        private String username;
        
        //연관관계 매핑
        @ManyToOne
        @JoinColumn(name="TEAM_ID")
        private Team team;
        
        //연관관계 설정
        public void setTeam(Team team) {
            this.team = team;
        }
        
        //Getter, Setter ...
    }
    매핑한 회원 엔티티
    java
    @Entity
    public class Team {
    
        @Id
        @Column(name = "TEAM_ID")
        private String id;
        
        private String name;
        
        //Getter, Setter ...
    }
    매핑한 팀 엔티티
    • 객체 연관관계 : 회원 객체의 Memeber.team 필드 사용
    • 테이블 연관관계 : 회원 테이블의 MEMBER.TEAM_ID 외래 키 컬럼 사용
    • @ManyToOne 이름 그대로 다대일 관계라는 매핑 정보로 다중성을 나타내는 어노테이션을 필수로 사용해야 한다.
    • @JoinColumn(name="TEAM_ID") 조인 컬럼은 외래 키를 매핑할 때 이용하며 name 속성에 매핑할 왜래 키 이름을 지정한다.
      • 이 어노테이션은 생략할 수 있다.

    1.4. @JoinColumn#

    속성설명기본값
    name매핑할 외래 키 이름필드명 + _ + 참조하는 테이블의기본 키 컬럼명
    referencedColumnName외래 키가 참조하는대상 테이블의 컬럼명참조하는 테이블의 기본 키 컬럼명
    foreignKey(DDL)외래 키 제약조건 직접 지정힐 수 있고 테이블 생성할 때만 사용한다.
    unique
    nullable
    insertable
    columnDefinition
    table
    @Column의 속성과 같음

    1.5. @ManyToOne#

    속성설명기본값
    optionalfalse로 설정 시 연관된 엔티티가 항상 존재해야 한다.true
    fetch글로벌 패치 전략을 설정한다.- @ManyToOne=FetchType.EAGER
    - @OneToMnay=FetchType.LAZY
    cascade속성 전이 기능을 사용한다.
    targetEntity- 연관된 엔티티의 타입 정보 설정한다.
    - 거의 사용하지 않는다.
    - 컬렉션 사용해도 제네릭으로 타입 정보 알 수 있다.

    2. 연관관계 사용#

    2.1. 저장#

    java
    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);
    }
    회원과 팀을 저장하는 코드
    • JPA는 참조한 팀의 식별자를 외래 키로 사용해 적절한 등록 쿼리를 생성한다.

    2.2. 조회#

    • 연관관계가 있는 엔티티를 조회하는 방법은 크게 2가지다.
      • 객체 그래프 탐색
        java
        Member member = em.find(Member.class, "member1");
        Team team = member.getTeam();  //객체 그래프 탐색
        System.out.println("팀 이름 = " + team.getName();
        객체 그래프 탐색 방법
      • 객체지향 쿼리 사용 (JPQL)
        java
        private static void queryLogicJoin(EntityManager em) {
        
            //회원과 팀 간 관계가 존재하는 필드(m.team)를 통해 Member와 Team 조인
            String jpql = "select m from Member m join m.team t where " +
                    "t.name=:teamName";  //조인한 t.name을 검색조건으로 사용
                    			 //파라미터 바인딩
            
            List<Member> resultList = em.createQuery(jpql, Member.class)
                    .setParameter("teamName", "팀1")  //팀1에 속한 회원만 검색
                    .getResultList();
                    
            for (Member member : resultList) {
                System.out.println("[query] member.username=" +
                        member.getUsername());
            }
            
        }
        
        //결과 : [query] member.username=회원1
        //결과 : [query] member.username=회원2
        JPQL 조인 검색
        • JPQL은 조인을 지원한다.

    2.3. 수정#

    java
    private static void updateRelation(EntityManger em) {
    
        //새로운 팀2
        Team team2 = new Team("team2", "팀2");
        em.persist(team2);
        
        //회원1에 새로운 팀2 설정
        Member member = em.find(Member.class, "member1");
        member.setTeam(team2);
        
    }
    • 수정은 불러온 엔티티의 값만 변경하면 커밋할 때 플러시가일어나며 변경감지 기능이 작동한다.
    • 연관관계 수정도 참조하는 대상만 변경하면 나머지는 JPA가 자동으로 처리한다.

    2.4. 연관관계 제거#

    java
    private static void deleteRelation(EntityManager em) {
    
        Member member1 = em.find(Member.class, "member1");
        member1.setTeam(null);  //연관관계 제거
        
    }

    2.5. 연관된 엔티티 제거#

    • 연관된 엔티티를 삭제하려면 기존에 있던 연관관계를 먼저 제거하고 삭제해야 한다.
      • 왜래 키 제약조건으로 인해, DB에서 오류가 발생한다.
    java
    member1.setTeam(null);  //회원1 연관관계 제거
    member2.setTeam(null);  //회원2 연관관계 제거
    em.remove(team);	//팀 삭제

    3. 양방향 연관관계#

    • 객체의 관계
      • 회원 → 팀
      • 팀 → 회원
    • 테이블 관계는 왜래 키 하나로 양방향으로 조회할 수 있다.

    3.1. 양방향 연관관계 매핑#

    java
    @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;
        }
    
        //Getter, Setter
    }
    매핑한 회원 엔티티
    java
    @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>();
    
        // Getter, Setter ...
    }
    매핑한 팀 엔티티
    • 팀과 회원은 일대다 관계다 때문에 팀 엔티티에 컬렉션인 List<Member> members를 추가한다.

    3.2. 일대다 컬렉션 조회#

    java
    public void biDirection() {
    
        Team team = em.find(Team.class, "team1");
        List<Member> members = team.getMembers();   // 팀 -> 회원, 객체그래프 탐색
    
        for (Member member : members) {
            System.out.println("member.username = " + 
                    member.getUsername());
        }
    }
    
    //결과
    //member.username = 회원1
    //member.username = 회원2
    }
    • 그래프 탐색을 사용해 조회한 회원들을 출려

    4. 연관관계의 주인#

    • 왜 연관관계 설정시 mappedBy가 필요할까?
    • 외래 키 하나로 두 테이블의 연관관계를 관리한다.
    • 엔티티를 단방향으로 매핑하면 참조를 하나만 사용하므로 이 참조로 외래키를 관리하면 된다.
    • 엔티티를 양방향으로 설정하면 객체의 참조는 둘인데 왜래 키는 하나다.
      • 따라서 둘 사이의 차이가 발생한다.
    • 이런 차이로 인해 JPA에서는 두 객체 연관관계 중 하나를 정해 테이블의 왜래 키를 관리해야 하는데 이것을 연관관계 주인이라 한다.

    4.1. 양방향 매핑의 규칙: 연관관계의 주인#

    • 연관관계의 주인만이 데이타베이스 연관관계와 매핑된다.
    • 연관관계의 주인만이 외래키를 관리(등록, 수정, 삭제)할 수 있다.
    • 주인이 아닌 쪽은 읽기만 할 수 있다.
    • 연관관계의 주인을 정한다는 것은 사실 외래 키 관리자를 선택하는 것이다.

    4.2. 연관관계의 주인은 왜래 키가 있는 곳#

    • 연관관계의 주인은 테이블에 외래 키가 있는 곳으로 정해야 한다.
    • Team 엔티티는 mappedBy를 통해 주인이 아님을 설정.
    java
    class Team {
        @OneToMany(mappedBy = "team")  // 연관관계 주인인 Member.team
        private List<Member> members = new ArrayList<Member>();
    }

    5. 양방향 연관관계 저장#

    java
    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);
    }
    양방향 연관관계 저장
    • 단방향 연관 관계에서 살펴본 회원과 팀을 저장하는 코드와 완전히 같다.
    java
    team1.getMembers().add(member1);        //무시
    team1.getMembers().add(member2);        //무시
    
    member1.setTeam(team1);                 //연관관계 설정(연관관계의 주인)
    member2.setTeam(team1);                 //연관관계 설정(연관관계의 주인)
    • 주인이 아닌 곳에서 입력된 값은 왜래 키에 영향을 주지 않는다.

    6. 양방향 연관관계의 주의점#

    • 양방향 연관관계를 설정하고 가장 흔히 하는 실수는 연관관계의 주인에는 값을 입력하지 않고 주인이 아닌 곳에서만 값을 입력하는 것
    java
    public void testSaveNonOwner() {
    
        //회원1 저장
        Member member1 = new Member("member1", "회원1");
        em.persist(member1);
    
        //회원2 저장
        Member member2 = new Member("member2", "회원2");
        em.persist(member2);
    
        Team team1 = new Team("team1", "팀1");
    
        //주인이 아닌 곳에 연관관계 설정
        team1.getMembers().add(member1);
        team2.getMembers().add(member2);
    
        em.persist(team1);
    }
    • 실제 데이터베이스에서는 값이 들어가지 않는다.

    6.1. 순수한 객체까지 고려한 양방향 연관관계#

    • 사실 객체 관점에서 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전하다.
    • 양쪽 방향 모두 값을 입력하지 않으면 JPA를 사용하지 않는 순수한 객체 상태에서 심각한 문제가 발생할 수 있다.
    java
    public void testORM_양방향() {
    	  //팀1 저장
    	  Team team1 = new Team("team1", "팀1");
    	  em.persist(team1);
    	  Member member1 = new Member("member1", "회원1");
    
    	  // 양방향 연관관계 설정
    	  member1.setTeam(team1); // 연관관계 설정 member1 -> team1
    	  team1.getMembers().add(member1); // 연관관계 설정 team1 -> member1
    	  em.persist(member1);
    	  Member member2 = new Member("member2", "회원2");
    	  // 양방향 연관관계 설정
    	  member2.setTeam(team1); // 연관관계 설정 member2 -> team1
    	  team1.getMembers().add(member2); // 연관관계 설정 team1 -> member2
    	  em.persist(member2);
    }
    전체 글 둘러보기