단방향 매핑에서 나아가 반대 방향으로 조회 (객체 그래프 탐색) 기능이 추가된 것 뿐이다.
단방향 매핑을 잘하고 필요할 때 추가하는 방식으로 진행하면 된다.
사진과 글의 출처는 김영한님의 JPA 특강
양방향 매핑 규칙
- 객체의 두 관계 중 하나를 연관관계의 주인으로 지정
- 연관관계의 주인만이 외래 키를 관리 (등록, 수정)
- 주인이 아닌쪽은 읽기만 가능 (주의❗❗ 값을 변경해도 반영이 안된다.)
- 내가 과제를 구현하며 마주했던 NullPointerException도 sub쪽에 값을 넣어줘서 반영이 안되었던 것이다..!
- 주인은 mappedBy 속성 사용 X
- 주인이 아니면 mappedBy 속성으로 주인을 명시 (mappedBy = "main에 넣은 sub 변수 명")
- +) 양방향 매핑 시 연관관계의 주인에 값을 입력하되, sub에도 주인을 넣어주자
team.getMembers().add(memeber);
member.setTeam(team);
그렇다면 주인은 어떻게 정할까?
- 외래키가 있는 곳을 주인으로 정해라
- 주인의 값을 바꾸면 sub에도 update문이 날아간다.
결론: To 뒤에 One이 오는 지 Many가 오는 지를 기준으로 보면 된다.
@~ToOne
: @JoinColumn 없이도 FK column이 주인 테이블에 생성된다.
@~ToMany
: ToMany는 해당 테이블에 여러 FK들이 담겨야 하므로 따로 연관관계 테이블이 생성된다. (객체 테이블에 FK 컬럼값은 따로 생성 안된다.)
ex) ORDERS_FOODS (N:N), MENU_FOODS (1:N)
1:N의 경우 @OneToMany(mappedBy = " ")가 없다면 자동으로 연관관계 테이블을 생성한다.
N:N의 경우 연관관계 테이블은 무조건 생성된다. 두 객체 모두 @~ToMany 이므로 FK 값이 여러개일 수 있기 때문이다.
@ManyToMany
@JoinTable( //생성될 연관관계 테이블
name = "ORDER_FOOD", // 테이블 이름
joinColumns = @JoinColumn(name = "ORDER_ID"), // 해당 객체의 PK값 저장할 column 명
inverseJoinColumns = @JoinColumn(name = "FOOD_ID") // 상대 객체의 PK값(해당 객체의 FK) 저장할 column 명
)
private List<Food> foods = new ArrayList<>();
@Entity
@IdClass(MemberProductId.class)
public class MemberProduct {
@Id
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member3 member; // MemberProduct.member와 연결
@Id
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product2 product; // MemberProduct.product와 연결
private int orderAmount;
@Temporal(TemporalType.DATE)
private Date orderDate;
}
@ManyToOne
// @JoinColumn(name="MENU_ID") -> 안해줘도 ToOne이기 때문에 자동으로 FK 컬럼이 생성된다.
private Menu menu;
public void setMenu(Menu menu) {
this.menu = menu;
menu.getFoods().add(this);
}
public class OrderServiceImpl implements OrderService{
// OCP, DIP 위반!!!!!!!!!!!!!!!
private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
public class AppConfig {
// AppConfig는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.
// AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결) 해준다.
public MemberService memberService(){
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService(){
return new OrderServiceImpl(new MemoryMemberRepository(), new RateDiscountPolicy());
}
}
공연 기획자
가 구현체 디카프리오에게 '너의 줄리엣은 xxx야' 라고 정해준다면, 디카프리오는 본인의 역할에만 충실하고, 나머지 기능은 외부에서 수행하는 것이 된다.Appconfig
가 담당한다.MemberServiceImpl
은 MemberRepository
인 추상에만 의존하면 된다.MemberServiceImpl
의 입장에서 보면 의존 관계를 마치 외부에서 주입해주는 것 같다고 해서 DI (의존성 주입, 의존 관계 주입) 이라고 한다. public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
// 생성자를 반환하는 메서드로 리팩터링
private MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
// 생성자를 반환하는 메서드로 리팩터링
public DiscountPolicy discountPolicy(){
return new FixDiscountPolicy();
}
-IoC
위와 같이 프로그램에 대한 제어 흐름에 대한 권한이 외부에 있는 것을 IoC 라고 한다.
프레임워크 vs 라이브러리
DI
IoC/DI Container
DI 컨테이너
라고 한다.AppConfig
를 사용해서 직접 객체를 생성하고 DI를 했지만, 스프링 컨테이너를 활용할 수도 있다. ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
ApplicationContext
를 스프링 컨테이너라고 한다.@Configuration
이 붙은 AppConfig
를 설정 정보로 사용한다.@Bean
이 붙은 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다. (Key는 메서드명, Value는 반환 값)스프링 빈
이라고 한다.위와 같이 Config class를 생성한 후, applicationContext의 생성자 파라미터로 넘겨주면 해당 class에 @Bean이 붙은 메서드를 스프링 빈으로 등록한다.
이 때 @Bean(name="")으로 빈의 이름을 직접 부여할 수도 있다. (디폴트는 메서드명)
단, 빈의 이름은 unique 해야한다!
스프링 빈을 등록하는 과정에서 설정 정보를 참고하여 빈 간 의존관계를 주입한다.
상속관계일 경우: 부모 타입으로 조회하면 자식 타입도 함께 조회한다.
BeanFactory
ApplicationContext
BeanFactory 기능을 모두 상속받아서 제공한다.
빈을 관리하고 검색하는 기능을 BeanFactory가 제공해주는데, 그러면 둘의 차이가 뭘까?
애플리케이션을 개발할 때는 빈을 관리하고 조회하는 기능은 물론이고, 수 많은 부가기능이 필요하다.
ApplicationContext는 빈 관리기능 + 편리한 부가 기능을 제공한다!