SpringBoot와 JPA, Thymeleaf를 사용하여 간단한 쇼핑몰을 구현하려 합니다. 프로젝트 생성이나 Gradle 빌드는 깃허브 BincoShop을 참고해주세요! 포스팅은 도메인&테이블 설계 -> 엔티티 개발 -> 회원 서비스 -> 상품 서비스 -> 주문 서비스 순으로 진행됩니다. 포스팅의 잘못된 부분은 언제든 댓글로 남겨주시면 수정하겠습니다😀
개요
이전 포스팅에 이어서 Category Entity를 개발합니다. 또한, 내장 타입으로 개발했던 Address를 변경 불가능하게 수정할 예정입니다. 모든 개발 과정은 김영한님의 강의를 통해 포스팅하였습니다😀
Category Entity🔆
@Entity
@Getter
@Setter
public class Category {
@Id
@GeneratedValue
@Column(name = "category_id")
private Long id;
private String name;
@ManyToMany
@JoinTable(name = "category_item",
joinColumns = @JoinColumn(name = "category_id"),
inverseJoinColumns = @JoinColumn(name = "item_id")
)
private List<Item> items = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private Category parent;
@OneToMany(mappedBy = "parent")
private List<Category> child = new ArrayList<>();
}
Category Entity는 Item Entity와 다대다(@ManyToMany) 관계를 맺고 있습니다. 객체끼리의 매핑이 아니기 때문에, 가운데에 테이블(CATEGORY_ITEM)을 하나 두어서 일대다, 다대일로 풀어내야합니다.
📌사실 실무에서는 @ManyToMany를 사용하지 않습니다. @ManyToMany는 편리한 것 같지만 중간 테이블에 컬럼을 추가할 수 없고, 세밀하게 쿼리를 실행하기 어렵기 때문에 실무에서 사용하기에는 한계가 있습니다.
Item Entity와는 다대다 매핑이므로 @JoinTable을 사용하여 관계를 맺습니다. 연관관계의 주인을 카테고리로 설정하였기 때문에, @JoinColumn을 category_id로, 반대편 @JoinColumn을 item_id로 설정하였습니다.
@ManyToMany(mappedBy = "items")
private List<Category> categories = new ArrayList<>();
반대로 Item Entity에서는 mappedBy를 통해 Category Entity에 의존하고 있음을 명시해줍니다.
그리고 Category Entity는 같은 Entity 내부에서 양방향 관계를 맺을 수 있습니다. 소위 한 카테고리가 다른 카테고리에 속할 수 있는 경우를 일컫습니다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private Category parent;
@OneToMany(mappedBy = "parent")
private List<Category> child = new ArrayList<>();
parent(부모 카테고리)와 child(자식 카테고리)와의 연관관계에서 parent가 주인이 되고, 여러 자식이 있을 수 있기 때문에 List로 풀어냈습니다.
Address 내장 타입🏡
@Embeddable
@Getter
public class Address {
private String city;
private String street;
private String zipcode;
}
지난 포스팅에서는 Addres 내장 타입은 컬럼만 선언하였습니다. 지금 위 코드는 @Setter 어노테이션도 없기 때문에, 값을 넣을 방법이 없어서 쓸 수 없는 상태입니다. 하지만 그렇다고 @Setter를 넣는 방법도 좋은 방법이 아닙니다. 그럼 어떻게 해야 할까요?
protected Address() {
}
public Address(String city, String street, String zipcode) {
this.city = city;
this.street = street;
this.zipcode = zipcode;
}
가장 좋은 방법은 값 타입은 변경 불가능하게 설계해야 합니다. @Setter를 사용하지 않고, 생성자에서 값을 모두 초기화해서 변경 불가능한 클래스를 만드는 것입니다.
JPA 스펙상 엔티티나 임베디드 타입(@Embeddable)은 자바 기본 생성자를 public 또는 protected로 설정해야 합니다. public 보다는 protected로 설정하는 것이 그나마 더 안전합니다.
JPA가 이런 제약을 두는 이유는 JPA 구현 라이브러리가 객체를 생성할 때 리플랙션 같은 기술을 사용할 수 있도록 지원해야 하기 때문입니다.
엔티티 설계시 주의점❗
엔티티에는 가급적 Setter를 사용하면 안 된다
- Setter가 모두 열려있으면 변경 포인트가 너무 많아서 유지보수가 어렵다.
모든 연관관계는 지연 로딩으로 설정하자
- 즉시 로딩(Eager)은 예측이 어렵고, 어떤 SQL이 실행될지 추측하기 어렵다. 특히 JPQL을 실행할 때 N+1 문제가 자주 발생한다.
- 실무에서 모든 연관관계는 지연 로딩(LAZY)으로 설정해야 한다.
- 연관된 엔티티를 함께 DB에서 조회해야 하면, fetch join 또는 엔티티 그래프 기능을 사용한다.
- @XToOne(OneToOne, ManyToOne) 관계는 기본이 즉시 로딩이므로 직접 지연 로딩으로 설정해야 한다.
컬렉션은 필드에서 초기화 하자
- 컬렉션은 필드에서 바로 초기화하는 것이 안전한다.
- null 문제에서 안전한다.
- 하이버네이트는 엔티티를 영속화할 때, 컬렉션을 감싸서 하이버네이트가 제공하는 내장 컬렉션으로 변경한다. 만약 getOrders()처럼 임의의 메서드에서 컬렉션을 잘못 생성하면 하이버네이트 내부 메커니즘에 문제가 발생할 수 있다.
Member member = new Member();
System.out.println(member.getOrders().getClass());
em.persist(team);
System.out.println(member.getOrders().getClass());
//출력 결과
class java.util.ArrayList
class org.hibernate.collection.internal.PersistentBag
👨👩👦👦 오픈채팅방 운영
취업을 준비하는 예비 개발자분들을 위한 질문&답변할 수 있는 공간을 만들었습니다. 취업과 이직을 하기 위해서 어떤 걸 중점적으로 준비해야 하는지부터 포트폴리오&이력서 작성법 등 다양한 질문들을 받고 답변을 드립니다. 참여하셔서 다양한 정보 얻고 가시면 좋을 것 같네요😁
참여코드 : 456456
https://open.kakao.com/o/gVHZP8dg
👨💻 전자책 출간
아울러 제가 🌟비전공자에서 2년만에 보안 전문 중견기업으로 이직 한 방법들을 정리한 전자책을 출간 하게 되었습니다. 어떤 걸 공부해야 하는지, 이직을 위해서 무엇을 준비해야 하는지, 제가 받았던 기술 면접 리스트 등 다양한 목차로 구성되어 있습니다. 또한, 구매 시 1:1 채팅을 이용하여 포트폴리오 첨삭을 도와드리고 있습니다. 🐕전자책으로 얻은 모든 수익은 유기견 센터 '팅*벨 입양센터'에 후원될 예정입니다. 관심 있으신 분들은 아래 링크를 참고해주세요😁
마치며
엔티티 설계는 이 포스팅으로 마무리됩니다. 다음 포스팅부터는 실제 비즈니스 로직을 구현을 할 예정이고, 회원 서비스부터 진행할 예정입니다. 혹여나 궁금하신 점이 있으시다면 댓글로 남겨주세요😄
관련 포스팅
Reference
'JPA' 카테고리의 다른 글
SpringBoot JPA 쇼핑몰 상품 서비스 개발 (0) | 2022.09.14 |
---|---|
SpringBoot JPA 쇼핑몰 회원 서비스 개발 (1) | 2022.09.14 |
SpringBoot JPA 쇼핑몰 엔티티 개발Ⅰ (0) | 2022.09.13 |
SpringBoot JPA 쇼핑몰 도메인&테이블 설계 (0) | 2022.09.07 |
JPA 영속성 관리 Dirty Checking & Merge (0) | 2022.09.06 |