JPA 스터디 (6장)

soon world·2021년 7월 8일
0

JPA

목록 보기
4/8

6장 다양한 연관관계 매핑

엔티티 연관관계 매핑 시 고려 사항

  • 다중성
    • 다대일(@ManyToOne)
    • 일대다(@OneToMany)
    • 일대일(@OneToOne)
    • 다대다(@ManyToMany)
  • 단방향, 양방향
    • 테이블의 경우 외래 키 하나로 조인 사용하여 양방향 쿼리가 가능하므로 방향이라는 개념 존재하지 않음
    • 객체의 경우 한쪽만 참조하는 것을 단방향 관계 라고 하고 양쪽 모두 참조 하는 것을 양방향 관계 라고 한다.
  • 연관관계의 주인
    • 데이터베이스
      • 연관관계 관리 포인트는 외래 키 하나
    • 객체
      • 외래 키를 가진 테이블과 매핑한 엔티티가 외래 키를 관리 하는게 효율적이라 보통 이곳을 연관관계의 주인으로 선택 (5장 참고)
      • 주인이 아닌 방향은 외래 키 변경 불가, 읽기만 가능

6.1 다대일

  • 데이터베이스 테이블의 일(1),다(N) 관계에서 외래 키는 항상 다 쪽에 있다.

  • 따라서 객체 양방향 관계에서 연관관계 주인은 항상 다 쪽이다.

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

    [회원 엔티티]
    
    @Entity
    public class Member {
    	
    	@Id @GeneratedValue
    	@Column(name = "Member_ID")
    	private Long id;
    	
    	private String username;
    	
    	@ManyToOne
    	@JoinColumn(name = "TEAM_ID")
    	private Team team;
    	
    	...
    }
    
    [팀 엔티티]
    
    @Entity
    public class Team {
    	
    	@Id @GeneratedValue
    	@Column(name = "TEAM_ID")
    	private Long id;
    
    	private String name;
    	
    	...
    }
  • 다대일 양방향 [N:1, 1:N]

    [회원 엔티티]
    
    @Entity
    public class Member {
    	
    	@Id @GeneratedValue
    	@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;
    
    		if(!team.getmembers().contains(this)) { //무한루프 빠지지 않도록
    			team.getMembers().add(this);
    		}
    	}	
    }
    
    [팀 엔티티]
    
    @Entity
    public class Team {
    	
    	@Id @GeneratedValue
    	@Column(name = "TEAM_ID")
    	private Long id;
    
    	private String name;
    	
    	@OneToMany(mappedBy = "team")
    	private List<Member> members = new ArrayList<Member>();
    	
    	//추가 된 부분
    	public void addMember(Member member) {
    		this.members.add(member);
    		if(member.getTeam() != this) {
    			member.setTeam(this);
    		}
    	}
    	
    	...
    }
    • 양방향은 외래 키가 있는 쪽이 연관관계의 주인!
      • Team.members 는 조회를 위한 JPQL 이나 객체 그래프 탐색 시 사용 한다.
    • 양방향 연관관계는 항상 서로를 참조 해야 한다!
      • 편의 메소드 양쪽 다 작성 시 무한루프 빠지는 것에 주의

6.2 일대다

  • 다대일 관계의 반대 방향, 일대다 관계는 엔티티를 하나 이상 참조 할 수 있어서 자바 컬렉션인 Collection, List, Set, Map 중에 하나를 사용 해야 한다.

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

    [팀 엔티티]
    
    @Entity
    public class Team {
    	
    	@Id @GeneratedValue
    	@Column(name = "TEAM_ID")
    	private Long id;
    	
    	private String name;
    
    	@OneToMany
    	@JoinColumn(name = "TEAM_ID") // ★ MEMBER 테이블의 TEAM_ID (FK) !!
    	private List<Member> members = new ArrayList<Member>();
    	...
    }
    
    @Entity
    public class Member {
    	
    	@Id @GeneratedValue
    	@Column(name = "MEMBER_ID")
    	private Long id;
    
    	private String username;
    
    	...
    }
    • 일대다 단방향 매핑 시 @JoinColumn 을 반드시 명시 해야 한다.(그렇지 않을 시 연결 테이블을 중간에 두고 연관관계를 관리 하는 조인 테이블 전략을 기본으로 사용한다.)

    • 일대다 단방향 매핑 단점

      • 매핑한 객체가 관리하는 외래 키가 다른 테이블에 있음!!

      • 본인 테이블에 외래 키가 있으면 INSERT SQL 한 번으로 끝낼 수 있지만, 다른 테이블에 외래 키가 있으면 UPDATE SQL을 추가로 실행 해야 한다. ( 다른 테이블에 외래키가 있으니 값을 모르니까)

        [일대다 단방향 매핑 단점]
        
        public void testSave() {
        	
        	Member member1 = new Member("member1");
        	Member member2 = new Member("member2");
        	
        	Team team1 = new Team("team1");
        	team1.getMembers().add(member1);
        	team1.getMembers().add(member2);
        	
        	em.persist(member1); //INSERT - member1
        	em.persist(member2); //INSERT - member2
        	em.persist(team1); //INSERT-team1 
        	//UPDATE-member1.fk 
        	//UPDATE-member2.fk
        	transaction.commit();
        
        }
        
        //결과 SQL
        insert into Member (MEMBER_ID, username) values(null,?)
        insert into Member (MEMBER_ID, username) values(null,?)
        insert into Team (TEAM_ID, name) values(null,?)
        update Member set TEAM_ID = ? where MEMBER_ID = ?
        update Member set TEAM_ID = ? where MEMBER_ID = ?
        
    • 일대다 단방향 매핑보단 다대일 양방향 매핑을 사용 할 것

  • 일대다 양방향[1:N, N:1]

    • 일대다 양방향 매핑은 존재하지 않는다.
    • 양방향 매핑에서 @OneToManay 는 연관관계의 주인이 될 수 없다.
      • 관계형 데이터베이스 특성상 일대다, 다대일 관계는 항상 다 쪽에 외래 키가 있음
    • 방법이 없는건 아니나, 가급적 다대일 양방향 매핑을 사용 할 것 (관리하기 힘들어요)

6.3 일대일[1:1]

  • 양쪽이 서로 하나의 관계만 가진다.

  • 특징

    • 일대일 관계는 그 반대도 일대일 관계이다.
    • 일대일 관계는 주 테이블이나 대상 테이블 둘 중 어느 곳이나 외래 키를 가질 수 있음
    • 따라서 주 테이블에 외래 키를 가지는 경우와, 대상 테이블에 외래 키를 가지는 경우, 2가지의 방법이 있음, 누가 가지게 할 지 선택 해야 한다.
  • 주 테이블에 외래 키

    • 객체지향 개발자들이 선호하는 방법
    • JPA도 주 테이블에 외래 키가 있으면 좀 더 편리하게 매핑 가능.
  • 대상 테이블에 외래 키

    • 일대일 관계 중 대상 테이블에 외래 키가 있는 단방향 관계는 JPA 에서 지원 하지 않음
    • 대상 테이블에 외래 키를 두려면 양방향 매핑을 해야 한다.

6.4 다대다[N:N]

관계형 데이터베이스의 경우 정규화된 테이블 2개로 다대다 관계를 표현할 수 없어서 중간에 연결 테이블을 두어 일대다, 다대일 관계로 풀어낼 수 있다. 하지만 객체는 2개로 다대다 관계를 만들 수 있다.

  • 다대다 단방향

    @Entity
    public class Member {
    	
    	@Id @Column(name = "MEMBER_ID")
    	private String id;
    
    	private String username;
    
    	@ManyToMany
    	@JoinTable(name = "MEMBER_PRODUCT",
    		joinColumns = @JoinColumn(name = "MEMBER_ID"),
    		inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID"))
    	private List<Product> products = new ArrayList<Product>();
    	...
    }
    
    @Entity
    pbulic class Product {
    	
    	@Id @Column(name = "PRODUCT_ID")
    	private String id;
    
    	private String name;
      ...
    }
    • @JoinTable.name : 연결 테이블 지정
    • @JoinTable.joinColumns : 현재 방향인 Member 와 매핑할 조인 컬럼 정보 지정
    • @JoinTable.inverseJoinColumns : 반대 방향인 Product와 매핑할 조인 컬럼 정보 지정
  • 다대다 : 양방향

    @Entity
    public class Product {
    	
    	@Id
    	private String id;
    	
    	@ManyToMany(mappedBy = "products") //역방향 추가
    	private List<Member> members;
    	
      ...
    }
    • 역시나 양방향의 연관관계는 연관관계 편의메소드를 추가하여 관리하는 것이 편리 하다.

      public void addProduct(Product product) {
      	products.add(product);
      	product.getMembers().add(this);
      }
      ...
  • 다대다: 연결 엔티티 사용

    • 연결테이블에 컬럼이 추가 될 경우 @ManyToMany 를 사용 할 수 없다. ( 추가한 컬럼들을 매핑할 수 없기 때문) 즉, 관계형 데이터베이스처럼 중간 객체를 만들어야 한다.

      @Entity
      @IdClass(MemberProductId.class)
      public class MemberProduct {
      
      	@Id
      	@ManyToOne
      	@JoinColumn(name = "MEMBER_ID")
      	private Member member; //MemberProductId.member 와 연결
      
      	@Id
      	@ManyToOne
      	@JoinColumn(name = "PRODUCT_ID")
      	private Product product; //MemberProductId.product 와 연결
      	
      	private int orderAmount;
      
      }
    • 회원상품 엔티티는 기본 키가 MEMBER_ID, PRODUCT_ID 로 구성된 복합 기본키 이다.

    • 위 MemberProductId 클래스의 경우 MemberProductId 클래스를 구현해야 한다.

    • JPA 에서 복합 기본키를 사용하려면 별도의 식별자 클래스를 만들어야 하며 엔티티에 @IdClass 를 사용해 식별자 클래스를 지정하면 된다.

    • 복합 기본키를 위한 식별자 클래스의 특징

      • 복합 기본키는 별도의 식별자 클래스로 만들어야 한다.
      • Serializable 을 구현해야 한다.
      • equals 와 hashCode 메소드 구현
      • 기본 생성자 있어야 함
      • 식별자 클래스의 접근 제어자는 public 이어야 함
      • @EmbeddedId 사용 하는 방법도 존재
  • 다대다 : 새로운 기본 키 사용

    • 추천하는 기본 키 생성 전략은 데이터베이스에서 자동으로 생성해 주는 대리 키를 Long 값으로 사용 하는 것 (장점 : 간편하고, 거의 영구히 쓸 수 있으며 비즈니스에 의존하지 않음, ORM 매핑 시 복합 키를 만들지 않아도 됨)

      @Entity
      public class Order {
      	
      	@Id @GeneratedValue
      	@Column(name = "ORDER_ID")
      	private Long id;  //Long 값의 새로운 기본키를 사용하니 보기 깔끔해짐
      
      	@ManyToOne
      	@JoinColumn(name = "MEMBER_ID")
      	private Member member;
      
      	@ManyToOne
      	@JoinColumn(name = "PRODUCT_ID")
      	private Product product;
      
      	private int orderAmount;
      	...
      
      }
    • 식별자 클래스를 사용하지 않아 코드가 단순해 지는 효과가 있음

  • 정리

    • 식별관계 : 받아온 식별자를 기본 키 + 외래 키로 사용
    • 비식별 관계 : 받아온 식별자는 외래 키로만 사용하고 새로운 식별자 추가
    • 객체 입장에서는 비식별 관계를 이용하는 것이 복합 키를 위한 식별자 클래스를 만들지 않아도 되므로 비식별 관계를 추천
profile
Hello SoonWorld

0개의 댓글