JPA 연관관계 매핑 - 양방향매핑


JPA 연관관계 매핑

JPA 연관관계 매핑 - 단방향매핑 에서 JPA의 방향성에 대해서 알아보았다. 이번에는 회원관리 엔티티를 이용 하여 양방향 매핑에 대해서 알아보자.

양방향 매핑

  • 회원 엔티티의 속성은 아래와 같다.
    • 회원은 한명의 강사에 소속되어 있다.
    • 회원과 강사는 다대일(N:1) 관계이다.
  • 강사 엔티티의 속성은 아래와 같다.
    • 강사는 여러명의 회원이 등록 되어 있다.
    • 강사와 회원은 일대다(1:N) 관계이다.
  • 엔티티 연관 관계
    • Entity 연관 관계에서는 강사 엔티티에서 컬렉션으로 Members 가 추가 되었다.

    객체 ERD

  • 데이터 베이스 연관 관계

    • 단방향 구조에서의 스크립트에서 양방향으로 변경 되어도 테이블 스크립트는 변경 되는 점이 없다. 왜일까?
      테이블 구조에서는 TEACHER_ID 를 이용 하여 회원과 강사를 JOIN 하면 어떤 방향 에서든 조회가 가능하다.
      테이블은 처음 부터 양방향 관계이다.

    맴버 ERD

  • 테이블 스크립트
CREATE TABLE MEMBER(
      ID VARCHAR(255) NOT NULL, --아이디(기본 키)
      NAME VARCHAR(255),             --이름
      AGE INTEGER NOT NULL,       --나이
      ADDRESS VARCHAR(255),      --주소
      TEACHER_ID VARCHAR(255), --강사ID
      PRIMARY KEY (ID)
  )

CREATE TABLE TEACHER (
      TEACHER_ID VARCHAR(255) NOT NULL, --강사ID(기본 키)
      NAME VARCHAR(255),                --이름
      PRIMARY KEY (TEACHER_ID)
  )

테스트 코드

  • 회원 엔티티는 이미 강사 엔티티로 다대일(N:1) 연관관계 이기 때문에 단방향에서 변경 점이 없다.

    • Member Entity
      @Entity
      @Table(name = "MEMBER")
      public class Member {
          @Id
          private String id;
          private String name;
          private int age;
          private String address;
            
          @ManyToOne
          @JoinColumn(name = "TEACHER_ID")
          private Teacher teacher;
            
          public Member(String id, String name, String address) {
              super();
              this.id = id;
              this.name = name;
              this.address = address;
          }
          public void setTeacher(Teacher teacher) {
              this.teacher = teacher;
          }
          // Getter , Setter
      }
    
  • 강사 엔티티의 양방향 매핑 수정

    • 강사는 회원에게 일대다 매핑이기 때문에 Set 를 추가 하였고 @OneToMany 를 이용 하여 일대다 매핑을 했다. mappedBy 는 맴버 엔티티의 강사 필드 값을 주면 된다. 강사 엔티티의 Members 를 등록 하기 위한 addMember(Member) 메서드도 추가 되었다.
      @Entity
      public class Teacher {
          @Id
          @Column(name = "TEACHER_ID")
          private String id;
          private String name;
            
          //연관 관계 설정
          @OneToMany(mappedBy = "teacher")
          private Set<Member> members = new HashSet<Member>();
            
          public Teacher(String id, String name) {
              this.id = id;
              this.name = name;
          }
            
          //멤버 추가
          public void addMember(Member member) {
          	members.add(member);
          }
      
          // Getter , Setter
      }
    
  • Main Class

      public class Main 
      {
          public static void main( String[] args )
          {
          	//엔티티 매니저 팩토리 생성 
              EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpaExam");
              //엔티티 매니저 생성
              EntityManager em = emf.createEntityManager();
              //엔티티 트렌젝션 생성
              EntityTransaction tx = em.getTransaction();
              try {
              	//트랜잭션 시작
                  tx.begin(); 
                    
                  //회원정보 & 강사정보 등록
                  inputData(em);
                    
                  //이일타 강사의 회원을 조회
                  Teacher teacher1 = em.find(Teacher.class, "1");
                  List<Member> members = teacher1.getMembers();
                    
                  //이일타 강사의 회원 이름 출력
                  for (Member member : members) {
      				System.out.println("회원명 : " + member.getMemberName());
      			}
                    
                  tx.commit();
              } catch (Exception e) {
                  e.printStackTrace();
                  tx.rollback(); //트랜잭션 롤백
              } finally {
                  em.close(); //엔티티 매니저 종료
              }
              emf.close(); //엔티티 매니저 팩토리 종료
          }
        
      	private static void inputData(EntityManager em) {
      		//강사 생성
      		Teacher teacher1 = new Teacher("1", "이일타");
      		Teacher teacher2 = new Teacher("2", "김이타");
        		
      		em.persist(teacher1);		
      		em.persist(teacher2);
        		
      		//맴버 생성
      		Member member1 = new Member("1", "김철수", 23, "서울특별시");
      		member1.setTeacher(teacher1);   //연관관계 설정 member1 -> teacher1
      		teacher1.addMember(member1);    
      		em.persist(member1);
        		
      		Member member2 = new Member("2", "이영희", 21,"부산광역시");
      		member2.setTeacher(teacher1);   //연관관계 설정 member2 -> teacher1
      		teacher1.addMember(member2);
      		em.persist(member2);
        		
      		Member member3 = new Member("3", "박수지", 22, "광주광역시");
      		member3.setTeacher(teacher2);   //연관관계 설정 member3 -> teacher1
      		teacher2.addMember(member3);
      		em.persist(member3);
      	}
      }
    
    • 수행 결과 이일타 강사의 회원은 김철수와 이영희로 정상적으로 출력 된다.
    • 데이터 베이스도 정상으로 등록 되었다.
      DB 조회 결과

연관관계의 주인(Owner)

양방향 연관관계 매핑을 할때 주인(Owner) 의 개념을 명확히 알고 있어야 한다.

  • 특징
    • 주인만이 데이터베이스의 연관관계와 매핑 되고 키를 관리(등록, 수정, 삭제) 할 수 있다.
    • 주인이 아닌 경우에는 읽기만 가능 하다.
    • 주인은 외래키를 가지고 있는 쪽이 주인이 되어야 한다.
  • 주인(Owner) 설정
    • mappedBy : 주인이 아닌 쪽에서 mappedBy 설정을 통하여 주인을 설정 할 수 있다.
          //연관 관계 설정
          @OneToMany(mappedBy = "teacher")
          private Set<Member> members = new HashSet<Member>();
    
  • 양방향 연관관계 설정시 주의점

    • 양방향 연관관계 설정시 주인이 아닌쪽에서 연관관계를 등록 했을 때를 아래 예제를 통해서 알아보자.
          //강사 생성
          Teacher teacher1 = new Teacher("1", "이일타");
          em.persist(teacher1);
            
          Member member1 = new Member("1", "김철수", 23, "서울특별시");
          em.persist(member1);
            
          //강사와 멤버의 연관관계 설정
          teacher1.getMembers().add(member1);
    
    • 멤버와 강사의 양방향 연관관계에서는 멤버가 주인(Owner) 이다. 하지만 위 소스에서는 주인이 아닌 강사에서 연관관계를 설정 했다.
      예제를 수행 후 데이터베이스를 조회 하면 아래와 같이 연관관계가 등록 되지 않았다.

      DB 조회 결과

    • 아래와 같이 연관관계의 설정은 주인(Owner) 인 멤버 엔티티에서 등록 해야 한다.

          //강사 생성
          Teacher teacher1 = new Teacher("1", "이일타");
          em.persist(teacher1);
            
          Member member1 = new Member("1", "김철수", 23, "서울특별시");
          //연관관계 설정
          member1.setTeacher(teacher1);
              
          em.persist(member1);
    

리팩토링

  • 양방향 연관관계 설정시 주인(Owner)에서 외래키를 관리 하는 것이 매우 중요하다. 멤버 엔티티에서는 강사를 등록 하고 강사 엔티티에서는 멤버를 등록 하여 관리 해야 하기 때문에 아래와 같이 리팩토링 해서 실수를 줄이도록 하자.
    //Member 엔티티의 setTeacher 메서드 수정
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
        teacher.addMember(this);    //Teacher의 멤버 등록
    }
    //강사 생성
    Teacher teacher = new Teacher("1", "이일타");
    em.persist(teacher);
    
    Member member = new Member("1", "김철수", 23, "서울특별시");
    member1.setTeacher(teacher1);
    //teacher.addMember(member);   -- setTeacher 메서드에서 addMember를 수행 하므로 생략가능

마무리

정리 하자면 단방향 연관관계 설정을 통해 테이블에는 이미 양방향 연관 관계가 설정 되어있으며 양방향 연관관계 설정은 엔티티의 탐색을 위해서 설정을 하는 추가 기능이다.
개념이 확실하게 이해가 되기 전에는 모든 방향을 단방향으로 개발 하는 것도 방법이다.

Back to blog