다양한 연관관계 매핑

원종서·2022년 2월 12일
1

JPA

목록 보기
7/13

다대일

다대일 단방향

@Entity
public class Member {
	@Id
    @Column(name = "MEMBER_ID);
    Long id;
    
    String username;
    
    @ManyToOne
    @JoinColumn(name ="TEAM_ID")
    Team team;
    
    // g,setter
    
@Entity
public clas Team {
	@Id
    @Column(name= "TEAM_ID")
    Long id;
    
    String name;
    
    // g,setter
    

@JoinColumn(name ="TEAM_ID") 를 이용해서 Member.team 필드를 TEAM_ID 컬럼과 매핑했다.
따라서 Member.team 으로 외래키를 관리한다.

다대일 양방향

@Entity
public class Member {
	@Id
   @Column(name = "MEMBER_ID);
   Long id;
   
   String username;
   
   @ManyToOne
   @JoinColumn(name ="TEAM_ID")
   Team team;
  
  	무한 로프에 빠지지 않도록 체크..
   public void setTeam(Team team) {
   	if(!team.getMembers().contains(this)){
       	team.getMembers().add(this);
       }
       this.team = team;
  }
  
   // g,setter
   
@Entity
public clas Team {
	@Id
   @Column(name= "TEAM_ID")
   Long id;
   
   String name;
   
   @OneToMany(mappedBy ="team")
   List<Member> members = new ArrayList<>();
   
  
  	void addMember(Member member){
   	this.member = member;
       if(member.getTeam() != this)){
       	member.setTeam(this);
       }
   
   // g,setter
   
  • 연관관계 주인이 아닌 Team.members 필드는 단지 조회를 위한 JPQL 또는 객체 그래프 탐색을 위해 존재하는 것이다.

  • 양방향 연관관계는 항상 서로를 참조해야한다.
    편의 메소드는 양쪽에 다 작성할 수 있는데, 양쪽에 다 작성하면 무한루프에 빠질 가능성이 있음으로 조심해서 사용해야한다.

일대다

일대다 단방향

팀 -> 회원

팀과 회원의 일대다 단방향 관계에서 (팀에 연간관계 주인) Team.members 로 회원 테이블의 TEAM_ID 를 관리한다.
이 매핑은 반대쪽 테이블에 있는 외래키를 관리한다. (테이블에서 외래키는 항상 "다" 쪽에 있기 때문에 )

@Entity
public clas Team {
	@Id
    @Column(name= "TEAM_ID")
    Long id;
    
    String name;
    
    @OneToMany
    @JoinColumn(name ="TEAM_ID") // MEMBER 테이블의 TEAM_ID (FK)
   	List<Member> members = new ArrayList<>();
   // g,setter
    
  • 일대다 단방향 단점
    객체가 관리하는 외래키가 반대편 테이블에 있기 때문에, insert , update 쿼리 두개를 보내야함으로 쿼리의 량이 증가하는 단점이 있다.

그러므로 일대다 양방향을 보다는 다대일 양방향을 사용하는 것이 바람직한다.

일대다 양방향

이것도 비효율적이다 다대일 양방향을 사용하는 것이 바람직하다.

일대일

  1. 회원은 하나의 사물함을 사용하고 사물함 역시 한명의 회원에 관리되는, 이런 관계
  2. 일대일 관계는 주 테이블이나 대상 테이블 둘 중 어느곳이나 외래키를 갖을 수 있다.

주 테이블에 외래키

주 객체가 대상 객체를 참조하는 것처럼, 주 테이블에 외래키를 두고 대상 테이블을 참조한다.
객체지향 개발자들이 주 테이블에 외래키 방식ㅇㄹ 선호한다.

대상 테이블에 외래키

전통적 데베 개발자들은 대상 테이블에 외래 키를 두는 것을 선호한다.
또 테이블 관계를 일대일엣 일대다로 변경할 때 테이블 구조를 유지할 수 있다

주 테이블에 외래키

단방향

@Entity
public class Member {
    @Id
    @Column(name = "MEMBER_ID")
    private Long id;

    private String name;

    @OneToOne
    @JoinTable(name = "LOCKER_ID")
    private Locker locker;
}


@Entity
public class Locker {
    @Id
    @Column("LOCKER_ID")
    private Long id;

    private String name;
}

위의 관계에서 데베는 LOCKER_ID 외래키에 유니크 제약 조건을 추가한다.

양방향

@Entity
public class Member {
    @Id
    @Column(name = "MEMBER_ID")
    private Long id;

    private String name;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;
}


@Entity
public class Locker {
    @Id
    @Column("LOCKER_ID")
    private Long id;

    private String name;
    
    @OneToOne(mappedBy = "locker")
    Member member ;
}

대상 테이블에 외래키

단방향

단방향 방법 없다.

양방향

@Entity
public class Member {
    @Id
    @Column(name = "MEMBER_ID")
    private Long id;

    private String name;

    @OneToOne(mappedBy= "member")
    private Locker locker;
}


@Entity
public class Locker {
    @Id
    @Column("LOCKER_ID")
    private Long id;

    private String name;
  
    @OneToOne
    @JoinColumn(name = "MEMBER_ID")
    Member member ;
}

다대다

보통 데베는 정규화된 테이블 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")
    )
    List<Product> products = new ArrayList<>();

}


@Entity
public class Product {
    @Id
    @Column(name = "PRODUCT_ID")
    private String id;
}

위의 코드는 @매니투매니와 @조인테이블을 사용해서 연결 테이블을 바로 매핑했다.
(자동적으로 MEMBER_ID, PRODUCT_ID 두개의 컬럼을 갖고 있는 MEMBER_PRODUCT 엔티티를 만들어준다)

다대다 관계 저장 코드

public void save(){
	Product productA = new Product("productA" ,"상품A");
    em.persist(productA);
    
    Member member1 =new Member("member1","회원1");
    member1.getProducts().add(productA); // 연관관계 설정.
    em.persist(member1);

회원1과 상품A의 연관관계를 설정했음으로 회원1을 저장할때 연결테이블에도 값이 저장된다.

INSERT INTO PRODUCT ...
INSERT INTO MEMBER ...
INSERT INTO MEMBER_PRODCUT...
public void find(){
	Member member = em.find(Member.class ,"member1");
    
    List<Product> products = member.getProducts();
    
    for(Product product : products ){
    	System.out.printlne(product);
    }
    
SELECT * FROM MEMBER_PRODUCT MP
	INNER JOIN PRODUCT P ON P.PRODUCT_ID  = MEMBER_PRODUCT.PRODUCT_ID
    WHERE MP.MEMBER_ID = ?

양방향

역방향도 @매니투매니를 사용한다.
연관관계 주인 역시 설정 해 줘야한다.

@Entity
public class Product {
    @Id
    @Column(name = "PRODUCT_ID")
    private String id;
    
    @ManyToMany(mappedBy = "products")
    List<Member> members = new ArrayList<>();
}

양방향 편의 메서드

public void addProduct(Product product){
	if(!product.getMembers().contains(this)){
		product.getMembers().add(this);    	
    }
    if(!products.contains(product)){
    	products.add(product)l
    
    }
}

다대다 : 매핑의 한계와 극복 연결 엔티티 사용

@다대다 매핑을 실무에서 사용하기엔 한계가 있다.
연결테이블에 각 연결된 테이블의 외래키 컬럼 외 다른 컬럼이 필요하다면 @다대다 애노테이션은 사용 불가능하다.

@다대다 말구 연결테이블 엔티티를 따로 정의해서 회원과 상품의 다대다 관계를 맺은 코드가 아래의 코드다


@Entity
public class Member {
    @Id
    @Column(name ="MEMBER_ID")
    private String id;

    private String username;

    @OneToMany(mappedBy ="member")
    List<MemberProduct> memberProducts;

}


@Entity
public class Product {
    @Id
    @Column(name = "PRODUCT_ID")
    private String id;
    
    private String name ;
    
    // 상품엔티티에서 회원상품 엔티티로 객체 그래프 탐색이 필요하지 않아서 연관관계를 만들지 않았
}

@Entity
@IdClass(MemberProductId.class)
public class MemberProduct{
	@Id
    @ManyToOne
    @JoinColummn(name ="MEMBER_ID")
    Member member ; // MemberProductId.member 과 연결

	@Id
    @ManyToOne
    @JoinColummn(name ="PRODUCT_ID")
    Product product ; // MemberProductId.product 과 연결
    
    private int orderAmount;

}

public class  MemberProductId implements Serializable {
	private String member;
    private String product;
    
    // hash code and equals ..
}

MemberProduct 를 보면 기본키와 외래키를 사용해서 기본키 + 외래키를 함께 매핑했다. 그리고 @IdClass 를 사용해 복합키를 매핑했다.

복합 기본키

JPA는 복합키를 사용하려면 별도의 식별자 클래스를 만들어야한다.

식별자 클래스의 특징
1. 복합키는 별도의 식별자 클래스로 만들어야함.
2. Serializable 를 구현해야함
3. 이퀄 , 헤쉬코드 메소드 구현해야함
4. 기본 생성자 필수
5. 퍼블릭 이여야함

식별관계

회원 상품은 회원과 상품의 기본키를 받아 자신의 기본키를 사용한다,
데베에서 부모테이블의 기본키를 받아 자신의 기본키 + 외래키로 사용하는 것을 식별관계 라고 부른다.

public void save(){

		Member member1 = new Member();
        member1.setId("member1");
        member1.setUsername("회원1");

        em.persist(member1);

        Product productA = new Product();
        productA.setId("productA");
        productA.setName("상품A");

        em.persist(productA);

        MemberProduct memberProduct = new MemberProduct();
        memberProduct.setMember(member1);
        memberProduct.setProduct(productA);
        memberProduct.setOrderAmount(2);

        em.persist(memberProduct);
}

회원 상품 엔티티를 데베에 저장할 때 연관된 회원의 식별자와 상품의 식별자를 가져와 자신의 기본키값으로 사용한다.

public void find(){

		MemberProductId memberProductId = new MemberProductId();
        memberProductId.setMember("member1"); // member's primary key
        memberProductId.setProduct("productA");

        MemberProduct memberProduct = em.find(MemberProduct.class, memberProductId);

        Member member = memberProduct.getMember();
        Product product = memberProduct.getProduct();
}

복합키를 사용하면 매우 복잡하다.

새로운 기본키 사용

복합키를 사용하지 않고 간단한 다대다 구현.

연결테이블의 기본키를 지정하는 방식 중 데베에서 자동으로 생성해주는 대리키를 사용하는 거이다.
대리키를 연결테이블의 기본키로 사용하고, 회원과 상품의 관계는 외래키로 설정한다.

@Entity
public class MemberProduct {
	@Id
    @GeneratedValue
    @Column(name ="MEMBER_PRODUCT_ID")
    Long id; // 데베에서 생성해주는 기본키
    
    @ManyToOne
    @JoinColumn(name="MEMBER_ID")
	List<Member> members;
    
    @ManyToOne
	JoinColumn(name ="PRODUCT_ID")
    List<Product> products;
    
    int productAmount;
}

저장 코드

		Member member =new Member("member1","회원1");
        em.persist(member);

        Product product = new Product("productA", "상품1");
        em.persist(product);

        MemberProduct memberProduct = new MemberProduct();
        memberProduct.setMember(member);
        memberProduct.setProduct(product);
        memberProduct.setOrderAmount(2);
        em.persist(memberProduct);

조회 코드


        Long memberProductId = 1L;

        MemberProduct memberProduct1 = em.find(MemberProduct.class, memberProductId);

        Member member = memberProduct1.getMember();
        Product product = memberProduct.getProduct();

즉 다대다 연관관계를 정의할때는 연결테이블의 식별키를 어떻게 구성할지 결정해야한다.
1. 부모로부터 받아온 기본키를 기본키 + 외래키로 사용하는 식별관계
2. 받아온 식별자는 외래키로만 사용하고 새로운 식별자를 추가한다 비식별관계

0개의 댓글