작성 개요
현재, 인프런에서 김영한님의 실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 강의를 듣고 있다. 자바 ORM 표준 JPA 프로그래밍 - 기본편 에서 들은 내용을 기본으로 하는 강의라서 복습할 겸, 내가 제대로 내용을 이해하고 있는지 정리할 겸 작성을 시작하게 되었다.
01. @Getter 와 @Setter에 관하여
예제에서는 엔티티 클래스에 Getter, Setter를 모두 열어두었다. 실무에서는 Getter 열어두고, Setter는 꼭 필요한 경우에만 사용하는 것을 추천한다고 한다. 실무에서 엔티티 데이터는 조회할 일이 많으므로 Getter는 열어두는 것이 편리하다. Getter는 단순히 데이터를 호출하는 것이므로 호출하는 것 자체만으로 어떤 일이 발생하지 않는다. 하지만 Setter를 호출하면 데이터가 변한다. Setter를 열어두면 엔티티의 변경 내역을 추적하기가 힘들다.
02. 기본키
엔티티 식별자로는 id를 사용한다. 하지만 PK 컬럼명은 "테이블명" + "_id"를 사용한다. 엔티티는 타입(클래스명)이 있기 때문에 id 필드로 쉽게 구분할 수 있지만, 테이블은 타입이 없으므로 구분이 어렵다. 예시로, Member 클래스에서 다음과 같이 작성한다.
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
03. 정리할 내용의 기반이 되는 것
하위에 정리할 내용들은 해당 이미지들을 기반으로 한다.
도메인 모델과 테이블 설계
엔티티 분석
테이블 분석
04. 연관관계 매핑 분석
연관관계의 주인은 외래키를 갖고 있는 쪽이다.
객체의 관점에서 양방향 연관관계에서 값을 변경하고자 하면 두 객체의 값 모두를 변경해야 하지만 DB 관점에서 값을 변경하고자 하면 하나의 값만 바꿔도 되도록 JPA에서 규약을 정했다. 그 하나의 값을 주인 이라는 개념으로 정의한다. 주인이 아닌 클래스에서 값을 추가하거나 변경한다고 해서 FK의 값이 변경되는 것이 아니다.
1. 회원과 주문 : 일대다 양방향 관계이다.
- Member 클래스 : Order 테이블의 member 필드에 매핑한다.
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
- Order 클래스 (주인) : FK의 필드명을 member_id로 한다.
@ManyToOne
@JoinColumn(name = "member_id")
private Member member;
2. 주문상품과 주문 : 다대일 양방향 관계이다.
- OrderItem 클래스 (주인) : FK의 필드명을 order_id로 한다.
@ManyToOne
@JoinColumn(name = "order_id")
private Order order;
- Order 클래스 : OrderItem 클래스의 order 필드에 매핑한다.
@OneToMany(mappedBy = "order")
private List<OrderItem> orderItems = new ArrayList<>();
3. 주문상품과 상품 : 다대일 단방향 관계이다. 주문상품만 상품을 알고 있다.
- OrderItem 클래스 : FK의 필드명을 item_id로 한다.
@ManyToOne
@JoinColumn(name = "item_id")
private Item item;
4. 주문과 배송 : 일대일 양방향 관계이다.
- Order 클래스 (주인) : FK의 필드명을 delivery_id로 한다.
@OneToOne
@JoinColumn(name = "delivery_id")
private Delivery delivery;
- Delivery 클래스 : Order 클래스의 delivery 필드에 매핑한다.
@OneToOne(mappedBy = "delivery")
private Order order;
5. 카테고리와 상품 : 다대다 관계이다. !! 실무에서는 사용하지 말자. @ManyToMany 를 사용해서 매핑한다. 중간 테이블을 만들어서 매핑해준다.
- Category 클래스
@ManyToMany
@JoinTable(name = "category_item",
joinColumns = @JoinColumn(name = "category_id"),
inverseJoinColumns = @JoinColumn(name = "item_id"))
private List<Item> items = new ArrayList<>();
- Item 클래스
@ManyToMany(mappedBy = "items")
private List<Category> categories = new ArrayList<Category>();
+ 추가
참고로, Order 클래스에서 테이블명이 Orders인 것은 데이터베이스가 order by를 예약어로 갖고 있기 때문에 관례상 orders를 많이 사용한다.
@Entity
@Table(name = "orders")
@Getter @Setter
public class Order {
// ..
}
05. 값 타입(임베디드 타입) 정의 및 사용
값 타입
Member 테이블에 city, street, zipcode 필드가 존재하는데, 이는 Delivery 테이블에서도 사용된다. 공통되는 필드를 묶어 Address 라는 값 타입 (임베디드 타입) 으로 지정하고, 해당 타입을 Member 테이블과 Delivery 테이블에서 사용하도록 하자.
값 타입은 변경이 불가능하게 설계해야 한다.
1. @Setter를 작성하지 말자.
2. 생성자에서 값을 모두 초기화 해서 변경이 불가능한 클래스로 만들자.
3. JPA 스펙상 값 타입(임베디드 타입)은 자바 기본 생성자를 public 또는 protected로 설정해야 한다.
Address 클래스 : 공통 속성을 정의한다.
- @Embeddable : 내장 타입임을 명시한다.
@Embeddable
@Getter
public class Address {
private String city;
private String street;
private String zipcode;
protected Address() {
}
public Address(String city, String street, String zipcode) {
this.city = city;
this.street = street;
this.zipcode = zipcode;
}
}
Member 클래스 : 값 타입을 사용한다.
- @Embebbed : 내장 타입을 포함했다는 어노테이션이다.
@Embedded
private Address address;
06. CascadeType.ALL
CascadeType.ALL 를 사용하지 않는 경우
다음 코드와 같이 Order 클래스에 CasecadeType.ALL 를 작성하지 않는 코드이다.
@OneToMany(mappedBy = "order")
private List<OrderItem> orderItems = new ArrayList<>();
이 경우에, 아래와 같이 엔티티 각각을 저장해야 한다.
em.persist(orderItemA)
em.persist(orderItemB)
em.persist(orderItemC)
em.persist(order)
CascadeType.ALL 를 사용하는 경우
CasecadeType.ALL은 persist를 전파한다. 다음 코드와 같이 Order 클래스에 CascadeType.ALL를 작성하면, Order를 저장할 때 OrderItem도 자동으로 저장된다.
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
이 경우, 아래와 같이 작성하면 된다.
em.persist(order);
07. 즉시로딩과 지연로딩
즉시 로딩(EAGER), fetch = FetchType.EAGER
예측이 어렵고, 어떤 SQL이 실행될지 추적하기 어렵다.
JPQL을 실행할 때, N+1 문제가 자주 발생한다.
예시를 들면, Member를 조회할 때 관련된 Order를 조회하는 것이다.
Order 클래스에 다음과 같이 작성되어 있다면, Order를 가져올 때 !!무조건!! Member도 같이 가져와진다.
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "member_id")
private Member member;
지연 로딩(LAZY), fetch = FetchType.LAZY
실무에서 모든 연관관계는 지연로딩으로 설정해야 한다.
연관된 엔티티를 함께 DB에서 조회하고 싶으면 fetch join 또는 엔티티 그래프 기능을 사용한다.
@XToOne은 기본이 즉시로딩이고, @XToMany는 기본이 지연로딩이다. 따라서 @XToOne은 직접 지연로딩으로 설정해야 한다.
Order 클래스에 다음과 같이 작성하면 된다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
08. Enum 클래스의 작성과 사용
Enum 클래스와 작성 방법
enum이란 열거형으로, 요소, 멤버라 불리는 명명된 값의 집합을 이루는 자료형이다.
작성 방법은 다음과 같다.
public enum DeliveryStatus {
READY, COMP
}
Enum 클래스 사용하기
@Enumerated를 사용한다. @Enumerated의 EnumType은 ORDINARY와 STRING이 있다.
EnumType의 기본은 ORDINARY 인데, 우리는 STRING을 사용해야 한다.
ORDINARY는 컬럼이 1, 2, 3, 4 와 같이 숫자로 들어간다. 예를 들어, 초기 설계 시 READY, COMP 이렇게 두 가지의 상태가 있다고 가정하여 서비스를 만들었는데 추후에 READY와 COMP 사이에 XXX 라는 다른 상태가 생기게 되면 순서가 밀려 꼬이게 된다.
@Enumerated(EnumType.STRING)
private DeliveryStatus status;
'JPA' 카테고리의 다른 글
페치 조인 1 - 기본 (1) | 2024.02.10 |
---|---|
JPQL - 경로 표현식 (0) | 2024.02.09 |
객체지향 쿼리 언어1 - 프로젝션 (SELECT) (0) | 2024.01.18 |
객체지향 쿼리 언어1 - 기본 문법과 쿼리 API (0) | 2024.01.18 |
값 타입 컬렉션 (0) | 2024.01.18 |