@Repository
public class MemberRepository {
@PersistenceContext
private EntityManager em;
public Long save(Member member) {
em.persist(member);
return member.getId();
}
public Member find(Long id) {
return em.find(Member.class, id);
}
}
@SpringBootTest
public class MemberRepositoryTest {
@Autowired
MemberRepository memberRepository;
@Test
@Transactional
@Rollback(value = false)
public void testMember() throws Exception {
//given
Member member = new Member();
member.setUsername("memberA");
//when
Long savedId = memberRepository.save(member);
//then
Member findMember = memberRepository.find(savedId);
Assertions.assertThat(findMember.getId()).isEqualTo(member.getId());
Assertions.assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
System.out.println("findMember == member" + (findMember == member)); // 엔티티 동일성 보장
}
}

하나의 회원은 여러주문을 할 수 있다.(회원과 주문은 일대다 관계)
하나의 주문에 여러개의 상품이 있을 수 있다. 하나의 상품이 여러개의 주문에 있을 수 있다.(주문과 상품은 다대다 관계)
==> 다대다 관계를 중간에 엔티티를 추가
하나의 주문에 여러개의 주문상품이 있을 수 있다.(OneToMany)
하나의 주문상품은 하나의 주문에 있을 수 있다.(하나의 주문상품은 하나의 주문이다)(ManyToOne)
하나의 주문상품은 하나의 상품에 있을 수 있다.(하나의 주문상품은 하나의 상품이다)(ManyToOne)
하나의 상품이 여러개의 주문상품에 있을 수 있다.(OneToMany)
참고로, 다대다 관계를 중간에 엔티티를 추가하면 항상 해당 중간 엔티티에서는 각각을 ManyToOne으로 연관관계를 해야한다.
하나의 카테고리에 여러개의 상품이 있을 수 있고, 하나의 상품이 여러개의 카테고리를 가질 수 도있다.(ManyToMany)
논리적으로 설명한것보다, DB관점에서 보면 이해하기 쉽다.
자기 자신과의 연관관계
Category는 계층구조(트리구조)를 가질 수 있는 엔티티이다.
즉, 어떤 카테고리는 부모 카테고리를 가질 수도 있고, 자식 카테고리들을 가질 수도 있다.
카테고리와 카테고리의 관계는 다대일 양방향 연관관계이다.
예를들어 Member와 Team처럼 각각의 엔티티가 있으면 각각의 클래스에서 다대일 양방향 연관관계를 설정해주면 되지만, 카테고리는 자기자신과의 연관관계를 맺는 구조이므로, Category클래스안에서 다대일 양방향 연관관계를 맺어준것이다. 즉, Member–Team 관계를 하나의 클래스 안에서 구현한 것이라고 생각하면된다.
public class Category {
@ManyToOne
@JoinColumn(name = "parent_id")
private Category parent; // 현재 카테고리 엔티티의 부모 카테고리를 가리킨다.
@OneToMany(mappedBy = "parent")
private List<Category> child = new ArrayList<>(); // 현재 카테고리 엔티티의 자식 카테고리를 가리킨다.
}
| category_id | name | parent_id |
|---|---|---|
| 1 | 전자제품 | NULL |
| 2 | 냉장고 | 1 |
| 3 | 세탁기 | 1 |
위와 같은 데이터가 있다고 했을때,
Category category = em.find(Category.class, 2L); // 냉장고
Category parent = category.getParent(); // 전자제품
category.getParent()를 호출하면, JPA는 내부적으로 CATEGORY 테이블에서 현재 category의 JoinColumn으로 명시했던 parent_id값(=외래키값)을 가져와서 CATEGORY테이블의 기본키를 참조하여 해당 Category 데이터를 조회하고, Category 객체를 생성해서 반환한다.
1. category.getParent()가 호출되면 내부적으로 CATEGORY테이블에서 현재 category의 JoinColumn으로 명시했던 parent_id값(=외래키값)을 가져온다.
2. Category테이블의 기본키가 이 외래키와 일치하는 Category 데이터를 찾는 SQL쿼리를 실행한다.
3. 조회된 데이터로 Category객체를 생성해서 반환한다.
Category category = em.find(Category.class, 1L); // 전자제품
List children = category.getChild(); // 냉장고, 세탁기
category.getChild()를 호출하면, JPA는 내부적으로 CATEGORY테이블에서 현재 category의 기본키값을 가져와서 CATEGORY테이블의 외래키를 참조하여 해당 Category데이터를 조회하고, Category객체를 생성해서 기존 비어있는 리스트에 추가한다.
1. category.getChild()가 호출되면 내부적으로 CATEGORY테이블에서 현재 category의 기본키값을 가져온다.
2. Category테이블의 외래키인 parent_id가 이 기본키와 일치하는 Category 데이터를 찾는 SQL쿼리를 실행한다.
ex) SELECT * FROM CATEGORY WHERE parent_id = 1;
3. 조회된 데이터로 Category객체를 생성해서 기존 비어있는 리스트에 추가한다
참고로, 부모 카테고리의 parent_id는 항상 NULL이다. parent_id는 현재 카테고리의 부모 카테고리의 pk를 가리키는 외래키이다. 하지만 그 위의 부모가 없으므로 참조할 pk값이 없다. 그래서 NULL이다.
Category parent = new Category(); // 부모 카테고리 생성
parent.setName("전자제품");
Category child1 = new Category(); // 자식 카테고리 생성
child1.setName("냉장고");
Category child2 = new Category();
child2.setName("세탁기");
parent.addChildCategory(child1);
parent.addChildCategory(child2);
em.persist(parent); // 부모 먼저 저장
em.persist(child1); // 자식 저장
em.persist(child2); // 자식 저장
그리고 셋다 아무 생성자도 명시적으로 작성하지않으면, 내부적으로 자동으로 기본생성자를 추가해주고, 기본생성자가 아닌 다른생성자를 하나라도 작성하면 기본생성자는 자동으로 추가되지않는다.
Item과 Book이 상속관계에 있다고 했을 때,
public Item findOne(Long id) {
return Book객체;
}
이게 성립되는 이유는 다형성 때문이다.
즉, Item item = new Book();이 되는것과 Item item = findOne();을 하는것이 같은 원리라고 생각하면된다.
return 되는 객체는 자식객체이지만, 메서드의 반환타입을 부모타입으로 지정할 수 있다. ==> 상속, 다형성의 원리
그래서 Item item = findOne();을 하게되면 item이 참조하는 실제 객체는 자식객체이다.