요구사항 분석
+ 상품 주문시 배송 정보를 입력할 수 있다.
도메인 모델과 테이블 설계
회원 엔티티 분석
회원 테이블 분석
엔티티 클래스 개발
- 실무에서는 가급적 Getter는 열어두고, Setter는 꼭 필요한 경우에만 사용하는 것을 추천
- 엔티티를 변경할 때는 Setter 대신에 변경 지점이 명확하도록 변경을 위한 비즈니스 메서드를 제공해야 함
- 테이블은 타입이 없으므로 구분이 어려워 관례상 "테이블명 + id" 를 많이 사용한다.
- 실무에서는 @ManyToMany 를 사용 x
- 중간 테이블( CATEGORY_ITEM )에 컬럼을 추가할 수 없고, 세밀하게 쿼리를 실행하기 어렵기 때문에 실무에서 사용하기에는 한계 존재
- 일대다, 다대일 매핑으로 풀어내서 사용
- 값 타입은 변경 불가능하게 설계해야 한다.
- JPA 스펙상 엔티티나 임베디드 타입( @Embeddable )은 자바 기본 생성자(default constructor)를 public 또는 protected 로 설정해야 한다.
- JPA가 이런 제약을 두는 이유는 JPA 구현 라이브러리가 객체를 생성할 때 리플랙션 같은 기술을 사용할 수 있도록 지원해야 하기 때문
엔티티 설계시 주의점
- 엔티티에는 가급적 Setter를 사용하지 말자
- 모든 연관관계는 지연로딩으로 설정!
- 즉시로딩( EAGER )은 예측이 어렵고, 어떤 SQL이 실행될지 추적하기 어렵다. 특히 JPQL을 실행할 때 N+1 문제가 자주 발생한다.
- 실무에서 모든 연관관계는 지연로딩( LAZY )으로 설정해야 한다.
- 연관된 엔티티를 함께 DB에서 조회해야 하면, fetch join 또는 엔티티 그래프 기능을 사용한다.
- @XToOne(OneToOne, ManyToOne) 관계는 기본이 즉시로딩이므로 직접 지연로딩으로 설정해야 한다.
- 컬렉션은 필드에서 초기화, 필드레벨에서 생성하는 것이 가장 안전하고, 코드도 간결
테이블, 컬럼명 생성 전략
스프링 부트에서 하이버네이트 기본 매핑 전략을 변경해서 실제 테이블 필드명은 다름
- 스프링 부트 신규 설정 (엔티티(필드) 테이블(컬럼))
- 카멜 케이스 -> 언더스코어(memberPoint member_point)
- (점) -> _(언더스코어)
- 대문자 -> 소문자
- 적용 2 단계
- 논리명 생성: 명시적으로 컬럼, 테이블명을 직접 적지 않으면 ImplicitNamingStrategy 사용 spring.jpa.hibernate.naming.implicit-strategy : 테이블이나, 컬럼명을 명시하지 않을 때 논리명 적용,
- 물리명 적용: spring.jpa.hibernate.naming.physical-strategy : 모든 논리명에 적용됨, 실제 테이블에 적용 (username usernm 등으로 회사 룰로 바꿀 수 있음)
- 연관관계 편의 메서드 작성!
엔티티 클래스 개발
- 연관관계 매핑
//Member 클래스
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
//Order 클래스
@ManyToOne(fetch = FetchType.LAZY) // @XXToOne은 항상 지연로딩 LAZY로 설정
@JoinColumn(name = "member_id")
private Member member;
- 어노테이션
@Entity
//@Inheritance(strategy = InheritanceType.SINGLE_TABLE) 싱글 테이블 전략
//@DiscriminatorColumn(name = "dtype")
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED) // 생성자 사용 제한 -> 유지보수를 위함
- ex) Order 클래스
@Entity
@Table(name = "orders")
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED) // 직접 생성하면 안 되므로 제약
public class Order {
@Id @GeneratedValue
@Column(name = "order_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
private LocalDateTime orderDate;
@Enumerated(EnumType.STRING) // Enum 타입 status
private OrderStatus status; // 주문상태 [ORDER, CANCEL]
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "delivery_id")
private Delivery delivery;
//==연관관계 메서드==//
public void setMember(Member member) {
this.member = member;
member.getOrders().add(this);
}
public void addOrderItem(OrderItem orderItem) {
orderItems.add(orderItem);
orderItem.setOrder(this);
}
public void setDelivery(Delivery delivery) {
this.delivery = delivery;
delivery.setOrder(this);
}
//==생성 메서드==//
public static Order createOrder(Member member, Delivery delivery, OrderItem... orderItems) {
Order order = new Order();
order.setMember(member);
order.setDelivery(delivery);
for(OrderItem orderItem : orderItems) {
order.addOrderItem(orderItem);
}
order.setStatus(OrderStatus.ORDER);
order.setOrderDate(LocalDateTime.now());
return order;
}
//==비즈니스 로직==//
/**
* 주문 취소
*/
public void cancel() {
if(delivery.getStatus() == DeliveryStatus.COMP) {
throw new IllegalStateException("이미 배송 완료된 상품은 취소가 불가능합니다.");
}
this.setStatus(OrderStatus.CANCEL);
for(OrderItem orderItem : orderItems) {
orderItem.cancel();
}
}
//==조회 로직==/
/**
* 전체 주문 가격 조회
*/
public int getTotalPrice() {
int totalPrice = 0;
for (OrderItem orderItem: orderItems) {
totalPrice += orderItem.getTotalPrice(); // 주문시 가격과 수량 존재하기 때문
}
return totalPrice;
}
}
참고 강의 :
실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 - 인프런 | 강의
실무에 가까운 예제로, 스프링 부트와 JPA를 활용해서 웹 애플리케이션을 설계하고 개발합니다. 이 과정을 통해 스프링 부트와 JPA를 실무에서 어떻게 활용해야 하는지 이해할 수 있습니다., - 강
www.inflearn.com
'JPA > 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발' 카테고리의 다른 글
[스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발] 7. 웹 계층 개발 (0) | 2023.01.06 |
---|---|
[스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발] 3~6. 도메인 개발 (0) | 2023.01.04 |
[스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발] 1. 프로젝트 환경설정 (0) | 2023.01.02 |