[24.09.14] TIL

yy·2024년 9월 13일

개발일지

목록 보기
104/122

JAVA, JPA 공부 중

연관관계 매핑

양방향 매핑

  • 객체와 테이블에 패러다임의 차이가 있음.
  • 객체는 참조를 사용하고 (member.name식의 참조) 테이블은 외래키를 이용하여 조인하여 사용하는 차이가 있어 사용에 어려움이 있음.
  • 그래서 연관관계를 설정할 때 연관관계의 주인이 누구인지를 확인하고 사용해야 제대로 코드를 사용할 수 있음.

양방향 연관관계

  • 서로 왔다갔다 참조해서 쓸 수 있는 관계
  • 테이블의 경우 외래키를 이용해서 조인 후 양방향 조회가 가능함.
  • 하지만 객체의 경우는 양방향으로 조회가 불가능했음. 이를 가능하게 하기 위해서 양쪽 객체(테이블)에 객체를 넣어줘야함.

그래서 코드로 보면 아래와 같다.

// Member
@Entity
public class Member { 
	@Id @GeneratedValue
	private Long id;
	
    @Column(name = "USERNAME") private String name;
    private int age;
    
    @ManyToOne
    @JoinColumn(name = "TEAM_ID") private Team team; //mappedBy의 주인공
}

// Team
@Entity
public class Team {
	@Id @GeneratedValue
	private Long id;
    
    private String name;
	
    @OneToMany(mappedBy = "team") 
    List<Member> members = new ArrayList<Member>();
}

@OneToMany의 속성인 mappedBy는 반대편 객체(테이블)에는 어떤게 매칭이 되어있는가를 표시한 것.

위와 같이 한다면 team에서 member를 조회할 수 있고, member.getTeam().getMember() 와 같이 객체간의 양방향 접근 코드도 가능해진다.

이때 등장한 것이 연관관계의 주인이라는 개념.


연관관계의 주인 (OWNER)

  • 객체의 양방향 관계는 사실 양방향관계가 아니라 서로 다른 단방향 관계 2개임. 그래서 각 클래스에 객체를 서로 만들어준거임.
  • 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리함.

어라?!

member의 team을 업뎃해서 테이블의 team_id를 업뎃해야하는지, team의 members를 업뎃해서 테이블의 team_id를 업뎃해야하는지 헷갈림.
➡️ 둘 중 하나만으로 외래키를 관리를 하자고 정함 : 연관관계의 주인의 등장

  • 연관관계의 주인만이 외래 키를 관리(등록, 수정)
  • 주인이 아닌 쪽은 읽기만 가능
  • 주인은 mappedBy 속성 사용 X
  • 주인이 아니면 mappedBy 속성으로 주인을 지정해줌
  • 위의 코드 의 경우 주인은 Member.team으로 등록, 수정가능. Team.member에서는 읽기만 가능
  • 외래 키가 있는 곳을 주인으로 정하면 됨. (@OneToMany가 쓰여지는 클래스의 객체)

순수 객체 상태를 고려해서 항상 양쪽에 값을 설정해야한다.
연관관계 편의 메소드를 생성해서 사용

public class Member {
    @ManyToOne
    @JoinColumn(name= "TEAM_ID")
    private Team team;

//연관관계 편의 메소드
    public void changeTeam(Team team) {
        this.team = team;
        team.getMembers().add(this);
    }
}

//혹은 

public class Team {
    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();

    public void addMember(Member member) {
	member.setTeam(this);
		members.add(member);
    }
}

//둘이 같이 쓰진 않고, 한 개만 골라서 쓰도록 해야한다.



위와 같은 구조를 가진 엔티티를 아래와 같이 만들어봄. 양방향 관계는 굳이 처음부터 만들 필요는 없고, 우선 단방향(1:N관계에서 N테이블에만 명시)만 우선 설정하고, 개발을 하면서 필요하면 양방향으로 추가로 관계를 설정해줘도 된다고 한다.

1:N 하면 무조건 양측에 코드를 넣어줘야한다고 생각이 들었는데 실무에서는 필요없는건 과감히 삭제한다는 말을 들었다.

package jpabook.jpashop.domain;

import jakarta.persistence.*;

import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "MEMBER")
public class Member {
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    private String name;
    private String city;
    private String street;
    private String zipcode;

    @OneToMany(mappedBy = "member")
    private List<Order> order = new ArrayList<>();

}
// getter, setter 생략
package jpabook.jpashop.domain;

import jakarta.persistence.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "ORDERS")
public class Order {
    @Id
    @GeneratedValue
    @Column(name = "ORDER_ID")
    private Long id;

    private LocalDateTime orderDate;

    @Enumerated(EnumType.STRING)
    private OrderStatus status;

    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;

    @OneToMany(mappedBy = "order")
    private List<OrderItem> orderItems = new ArrayList<>();

}

// getter, setter 생략
package jpabook.jpashop.domain;

import jakarta.persistence.*;

@Entity
@Table(name = "ORDER_ITEM")
public class OrderItem {

    @Id
    @GeneratedValue
    @Column(name = "ORDER_ITEM_ID")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "ITEM_ID")
    private Item item;

    @ManyToOne
    @JoinColumn(name = "ORDER_ID")
    private Order order;

    private int orderPrice;
    private int count;

}
// getter, setter 생략
package jpabook.jpashop.domain;

import jakarta.persistence.*;

@Entity
@Table(name = "ITEM")
public class Item {
    @Id
    @GeneratedValue
    @Column(name = "ITEM_ID")
    private Long id;

    private String name;
    private int price;
    private int stockQuantity;

//    @OneToMany(mappedBy = "item")
//    private OrderItem orderItem;
}

// getter, setter 생략
package jpabook.jpashop.domain;

public enum OrderStatus {
    PENDING, ORDER, CANCEL
}


연관관계 매핑 시 고려사항

다중성

  • 다대일 : @ManyToOne
  • 일대다 : @OneToMany
  • 일대일 : @OneToOne
  • 다대다 : @ManyToMany (실무에서 X)

단방형, 양방향

  • 테이블 : 외래 키 하나로 양쪽 조인 가능. 방향이라는 개념이 없음
  • 객체 : 참조용 필드가 있는 쪽으로만 참조 가능. 한쪽만 참조하면 단방향. 양쪽이 서로 참조하면 양방향.

연관관계의 주인

  • 테이블은 외래 키 하나로 두 테이블이 연관관계르 ㄹ맺음
  • 객체 양방향 관계는 참조가 2군데
  • 객체 양방향 관계는 참조가 2군데 있음. 둘 중 테이블의 외래 키를 관리할 곳을 지정해야함
  • 연관관계의 주인 : 외래 키를 관리하는 참조
  • 주인의 반대편 : 외래 키에 영향을 안주고 단순 조회만 가능

다대일

  • 다대일 중 '다'의 입장에 연관관계의 주인이 있다는 가정
  • 단방향
  • 양방향

일대다

  • 일대다 중 '일'의 입장에 연관관계의 주인이 있다는 가정

  • 단방향
    - 권장X. Team에 members가 있고, 변경 시 member테이블을 수정을 해줘야함.

    - 테이블 일대다 관계는 항상 다 쪽에 외래 키가 있음
    - 객체와 테이블 차이 때문에 반대편 테이블의 외래 키를 관리하는 특이한 구조
    - @JoinColumn을 꼭 사용해야함. 그렇지 않으면 조인 테이블 방식을 사용함(중간에 테이블을 하나 추가됨)
    - 단점 : 엔티티가 관리하는 외래 키가 다른 테이블에 있고, 추가로 update문이 실행됨

➡️ 일대다 단방향 매핑보다 다대일 양방향 매핑을 사용하자

  • 양방향

    - 공식적으로 존재하는건 아님
    - @JoinColumn(insertable = false, updatable = false)을 넣으면 됨
    - 읽기 전용 필드를 사용해서 양방향처럼 사용하는 방법

➡️ 다대일 양방향을 사용하는게 낫다


일대일

  • 주테이블이나 대상 테이블 중 외래 키 선택 가능
    • 주 테이블에 외래 키
    • 대상 테이블에 외래 키
  • 외래 키에 DB 유니크 제약조건 추가


다대다

  • RDB는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없음
  • 연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야함
  • 객체는 컬렉션을 사용해서 객체 2개로 다대다 관계 가능
  • 편리해보이지만 실무에서 사용 X
  • 연결 테이블 용 엔티티 추가 (연결 테이블을 엔티티로 승격)
  • @ManyToMany -> @OneToMany, @ManyToOne으로 바꿔야한다.

profile
시간이 걸릴 뿐 내가 못할 건 없다.

0개의 댓글