2022. 6. 16. 10:17ใBackend/๐ฟ Spring
JPA ์ ์ง์ฐ๋ก๋ฉ ์ต์ ์ ์ฌ์ฉํ ๋ ๋ฐ์ํ๋ ์ฑ๋ฅ๋ฌธ์ ๋ฅผ ๋จ๊ณ์ ์ผ๋ก ํด๊ฒฐํด๋ณด์.
Version 1. ์ํฐํฐ๋ฅผ ์ง์ ๋ ธ์ถ (์ง์ ํด์ผํ๋ ๋ฒ์ )
์๋ ์ฝ๋๋ Order ์ํฐํฐ๋ฅผ ์ปฌ๋ ์ ์ ๋ด์ ๊ทธ๋๋ก ๋ ธ์ถํ๋ค. ์ด๋ด ๊ฒฝ์ฐ API ์คํ์ด ๋ณ๊ฒฝ๋๋ฉด ํด๋ผ์ด์ธํธ ๋จ์์๋ ๋ณ๊ฒฝ๋ ์คํ์ ๋์ํ์ง ๋ชปํ๊ณ ์๋ชป๋ ๊ฐ์ ์ฝ์ด๋ค์ฌ ์๋ฌ๊ฐ ๋ฐ์ํ ๊ฐ๋ฅ์ฑ์ด ์กด์ฌํ๋ค. ๋ํ ์ํฐํฐ๋ฅผ ๊ทธ๋๋ก ๋ ธ์ถํ๊ธฐ ๋๋ฌธ์ ๋ณด์์์ ๋ฌธ์ ๋ ๊ฐ๊ณผํ๋ค.
/**
*
* xToOne(ManyToOne, OneToOne) ๊ด๊ณ ์ต์ ํ
* Order
* Order -> Member
* Order -> Delivery
*
*/
@RestController
@RequiredArgsConstructor
public class OrderSimpleApiController {
private final OrderRepository orderRepository;
private final OrderSimpleQueryRepository orderSimpleQueryRepository; //์์กด๊ด๊ณ ์ฃผ์
/**
* V1. ์ํฐํฐ ์ง์ ๋
ธ์ถ
* - Hibernate5Module ๋ชจ๋ ๋ฑ๋ก, LAZY=null ์ฒ๋ฆฌ
* - ์๋ฐฉํฅ ๊ด๊ณ ๋ฌธ์ ๋ฐ์ -> @JsonIgnore
*/
@GetMapping("/api/v1/simple-orders")
public List<Order> ordersV1() {
List<Order> all = orderRepository.findAllByString(new OrderSearch());
for (Order order : all) {
order.getMember().getName(); //Lazy ๊ฐ์ ์ด๊ธฐํ
order.getDelivery().getAddress(); //Lazy ๊ฐ์ ์ด๊ธฐํ
}
return all;
}
}
Order ์ํฐํฐ๋ Member ์ Delivery ๋ผ๋ ์ํฐํฐ์ xToOne ๊ด๊ณ๋ฅผ ๋งบ๊ณ ์๋ค. ๋ฌด์จ ๋ป์ด๋ํ๋ฉด Order์ 1์ด๊ฑฐ๋ N์ด ๋ ์ ์์ง๋ง, Member ์ Delivery๋ ํญ์ 1์ธ ๊ฒฝ์ฐ๋ฅผ ์๋ฏธํ๋ค. ๊ฐ๊ฐ์ ์ํฐํฐ๋ฅผ ์ดํด๋ณด์. ์๋ก๊ฐ ์๋ก๋ฅผ ๋จ๋ฐฉํฅ์ผ๋ก ์ฐธ์กฐํ๊ธฐ ๋๋ฌธ์ Order-Member, Order-Delivery ๋ ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ๊ฐ ํ์ฑ๋๋ค.
Order
Member ์ Delivery ๋ฅผ ์ฐธ์กฐํ๋ฉฐ ์ต์
์ LAZY ๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค. ์ง์ฐ๋ก๋ฉ์ ์ฌ์ฉํ๋ค๋ ์๋ฏธ์ธ๋ฐ, ์ด๊ฒ์ EAGER ๋ก ๋ณ๊ฒฝํ๋ฉด Order ๋ฅผ ์กฐํํ๋ ์ฆ์ Member ์ Delivery ๋ ์กฐํํ๋ค. ๊ทธ๋ฌ๋ฉด ํธํ๊ณ ์ข์๊ฒ ์๋๋๊ณ ? ๊ทธ๋ ๊ฒ ์๊ฐํ ์๋ ์์ง๋ง EAGER ๋ฅผ ์ฌ์ฉํ๋ฉด 1) N + 1 ๋ฌธ์ ์ 2) ์ฑ๋ฅ ์ต์ ํ
2๊ฐ์ง ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค. ์๋ํ๋ฉด ์ฆ์ ๋ก๋ฉ์ ์ฌ์ฉ์, Order ๋ง ์กฐํํ๊ณ ์ถ์ ๊ฒฝ์ฐ์๋ Member, Delivery ๊ฐ ๊ณง๋ฐ๋ก ๋ฐ๋ผ์ ์กฐํ๋๊ธฐ ๋๋ฌธ์ ๋ถํ์ํ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ด ์ฑ๋ฅ์ด ์ ํ๋ ์ ์๋ค. ๋๋ฌธ์ LAZY ์ต์
์ ์ฌ์ฉํ๋๋ผ๋, ์ ์ปจํธ๋กค๋ฌ์ for๋ฌธ์ผ๋ก member ์ delivery ์ ๋ํด ๊ฐ์ ์ด๊ธฐํ
๋ฅผ ์งํํ๋ ๋ฐฉ๋ฒ์ ์งํฅํด์ผํ๋ค. ๊ทธ๋ผ ๊ฐ์ ์ด๊ธฐํ๋ ์ ํด์ผํ๋ ๊ฑธ๊น? Order -> Member ์ Order -> Delivery ๋ ์ง์ฐ๋ก๋ฉ์ด๋ค. ์ฆ ์ค์ ์ํฐํฐ ๋์ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒ์ด๊ณ , ์ด๋ฅผ ๊ทธ๋๋ก ์น์ ๋
ธ์ถํ ์, ํ๋ก์ ๊ฐ์ฒด๋ฅผ json ํํ๋ก ๋ณ๊ฒฝํ๋ ๋ฐฉ๋ฒ์ ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค. ๊ทธ๋์ ์ผ์ผ์ด ๊ฐ์ ์ด๊ธฐํ๋ฅผ ํด์ค์ผ ํ๋ค.
@Entity
@Table(name = "orders")
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Order {
@Id @GeneratedValue
@Column(name = "order_id")
private Long id;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "member_id")
private Member member;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
@OneToOne(fetch = LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "delivery_id")
private Delivery delivery;
}
Member
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String name;
@Embedded
private Address address;
@JsonIgnore
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
}
Delivery
@Entity
@Getter @Setter
public class Delivery {
@Id @GeneratedValue
@Column(name = "delivery_id")
private Long id;
@JsonIgnore
@OneToOne(mappedBy = "delivery", fetch = LAZY)
private Order order;
@Embedded
private Address address;
@Enumerated(EnumType.STRING)
private DeliveryStatus status; //READY, COMP
}
* V1 ์ฒ๋ผ ์ํฐํฐ๋ฅผ ์ง์ ๋ ธ์ถํ ๋๋ ์๋ฐฉํฅ๊ด๊ณ๊ฐ ๊ฑธ๋ฆฐ ๊ณณ์ ๊ผญ ํ๊ณณ์ @JsonIgnore ์ฒ๋ฆฌํด์ผ ํ๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด ์์ชฝ์ ์๋ก ํธ์ถํ๋ฉด์ ๋ฌดํ ๋ฃจํ๊ฐ ๋ฐ์ํ๋ค.
Version 2. ์ํฐํฐ๋ฅผ DTO๋ก ๋ณํ
@RestController
@RequiredArgsConstructor
public class OrderSimpleApiController {
private final OrderRepository orderRepository;
private final OrderSimpleQueryRepository orderSimpleQueryRepository; //์์กด๊ด๊ณ ์ฃผ์
/**
* V2. ์ํฐํฐ๋ฅผ ์กฐํํด์ DTO๋ก ๋ณํ(fetch join ์ฌ์ฉX)
* - ๋จ์ : ์ง์ฐ๋ก๋ฉ์ผ๋ก ์ฟผ๋ฆฌ N๋ฒ ํธ์ถ
*/
@GetMapping("/api/v2/simple-orders")
public List<SimpleOrderDto> ordersV2() {
List<Order> orders = orderRepository.findAll();
List<SimpleOrderDto> result = orders.stream()
.map(o -> new SimpleOrderDto(o))
.collect(toList());
return result;
}
@Data
static class SimpleOrderDto {
private Long orderId;
private String name;
private LocalDateTime orderDate; //์ฃผ๋ฌธ์๊ฐ
private OrderStatus orderStatus;
private Address address;
public SimpleOrderDto(Order order) {
orderId = order.getId();
name = order.getMember().getName();
orderDate = order.getOrderDate();
orderStatus = order.getStatus();
address = order.getDelivery().getAddress();
}
}
}
์ํฐํฐ๋ฅผ DTO ๋ก ๋ณํํ๋ ์ผ๋ฐ์ ์ธ ๋ฐฉ๋ฒ์ผ๋ก, V1 ๊ณผ ๋ฌ๋ฆฌ ์ํฐํฐ๋ฅผ ์ง์ ์ ์ผ๋ก ๋ ธ์ถํ์ง ์๋๋ค.
๊ทธ๋ผ ๋ค ํด๊ฒฐ๋๊ฒ์ธ๊ฐ? ์์ง ์ต์ ํ์ ๋ฌธ์ ๊ฐ ๋จ์๋ค.
์ ์ฝ๋์์๋ ์ฟผ๋ฆฌ๊ฐ ์ด 1 + N + N๋ฒ ์คํ๋๋ค.
์ฐ์ order ๋ฅผ 1๋ฒ ์กฐํํ๋ค. ์ด๋ order ์ ์กฐํ ๊ฒฐ๊ณผ๊ฐ N๊ฐ ๋ผ๊ณ ํ์.
๊ทธ๋ผ order ์์ member ๋ฅผ ์กฐํํ ๋ ์ง์ฐ๋ก๋ฉ์ด N ๋ฒ ๋ฐ์ํ๊ณ , ๋ง์ฐฌ๊ฐ์ง๋ก order ์์ delivery๋ฅผ ์กฐํํ ๋ ๋ ์ง์ฐ๋ก๋ฉ์ด N๋ฒ ๋ฐ์ํ๋ค. ์๋ฅผ ๋ค์ด N ์ด 10์ด๋ผ๋ฉด ์ด ๋ฐ์ํ๋ ์ฟผ๋ฆฌ ๊ฐฏ์๊ฐ 1 (order) + 10 (member) + 10 (delivery) ๋ก ์ด 21๋ฒ ์คํ๋๋ ๊ฒ์ด๋ค. ์ด๋ฐ N + 1 ๋ฌธ์ ๋ก ์ธํด ์ฑ๋ฅ ์ ํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์์ ์ธ์งํ๊ณ V3์์ ํ์น ์กฐ์ธ ์ต์ ํ๋ก ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด๋ณด์.
Version 3. ์ํฐํฐ๋ฅผ DTO๋ก ๋ณํ + ํ์น ์กฐ์ธ ์ต์ ํ
findAllWithMemberDelivery() ๋ผ๋ ๋ฉ์๋๋ง ์กฐํํ๋ฉด V2 ์ฝ๋์ ๋ณ๋ฐ ๋ค๋ฅผ๊ฒ ์์ด ๋ณด์ธ๋ค. findAllWithMemberDelivery() ๋ด๋ถ๋ฅผ ์ดํด๋ณด์.
@RestController
@RequiredArgsConstructor
public class OrderSimpleApiController {
private final OrderRepository orderRepository;
private final OrderSimpleQueryRepository orderSimpleQueryRepository; //์์กด๊ด๊ณ ์ฃผ์
/**
* V3. ์ํฐํฐ๋ฅผ ์กฐํํด์ DTO๋ก ๋ณํ(fetch join ์ฌ์ฉO)
* - fetch join์ผ๋ก ์ฟผ๋ฆฌ 1๋ฒ ํธ์ถ
* ์ฐธ๊ณ : fetch join์ ๋ํ ์์ธํ ๋ด์ฉ์ JPA ๊ธฐ๋ณธํธ ์ฐธ๊ณ (์ ๋ง ์ค์ํจ)
*/
@GetMapping("/api/v3/simple-orders")
public List<SimpleOrderDto> ordersV3() {
List<Order> orders = orderRepository.findAllWithMemberDelivery();
List<SimpleOrderDto> result = orders.stream()
.map(o -> new SimpleOrderDto(o))
.collect(toList());
return result;
}
@Data
static class SimpleOrderDto {
private Long orderId;
private String name;
private LocalDateTime orderDate; //์ฃผ๋ฌธ์๊ฐ
private OrderStatus orderStatus;
private Address address;
public SimpleOrderDto(Order order) {
orderId = order.getId();
name = order.getMember().getName();
orderDate = order.getOrderDate();
orderStatus = order.getStatus();
address = order.getDelivery().getAddress();
}
}
}
findAllWithMemberDelivery()
public List<Order> findAllWithMemberDelivery() {
return em.createQuery(
"select o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d", Order.class)
.getResultList();
}
JPQL ๋ฌธ๋ฒ์์ join fetch ๋ช ๋ น์ด๋ฅผ ์ฌ์ฉํ๋ค. ์ด๋ฅผ ํ์น ์กฐ์ธ์ด๋ผ ํ๋๋ฐ, SQL ๋ฌธ๋ฒ์ด ์๋ JPQL ์์ ์ฑ๋ฅ ์ต์ ํ๋ฅผ ์ํด ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ด๋ค. ํ์น ์กฐ์ธ์ ์ฌ์ฉํ๋ฉด ์ฐ๊ด๋ ์ํฐํฐ๋ ํด๋์ค๋ฅผ ํ๋ฒ์ ๊ฐ์ด ์กฐํํ ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค. ์ฆ Order ๋ฅผ ์กฐํํ ๋ ์ฐ๊ด๊ด๊ณ์ ์๋ member ์ delivery ๋ ํจ๊ป ์กฐํํ๋ ๊ฒ์ด๋ผ ์๊ฐํ๋ฉด ๋๋ค.
์ด๋ ๊ฒ ํ์น ์กฐ์ธ์ ์ฌ์ฉํ๋ฉด ๋ญ๊ฐ ์ข์๊น? ๋ฐ๋ก N + 1 ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋๋ค. ์ฆ ์ฟผ๋ฆฌ 1๋ฒ์ Order ๋ฟ๋ง ์๋๋ผ Member ์ Delivery ๊น์ง ์กฐํ๊ฐ ๊ฐ๋ฅํด์ง๋ค. ํ์น ์กฐ์ธ์ ์ฌ์ฉํ๋ฉด ์ง์ฐ ๋ก๋ฉ ์์ฒด๊ฐ ์คํ๋์ง ์์์ ๊ธฐ์ตํ์.
* ์ถ์ฒ
์ค์ !์คํ๋ง๋ถํธ์ JPA ํ์ฉ 2 ๋ฅผ ์๊ฐํ๊ณ ์ ๋ฆฌํ ํฌ์คํ
์์ ๋ฐํ๋๋ค.
'Backend > ๐ฟ Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Docker, Springboot] java.sql.SQLSyntaxErrorException: Unknown database (4) | 2022.08.03 |
---|---|
๊ฐ๋ฐํ๋ฉด์ ๋ฐฐ์ด Map vs DTO ๋ฐฉ์์ ์ฐจ์ด์ (0) | 2022.07.31 |
[Spring] @Aspect ์ AOP ํบ์๋ณด๊ธฐ (0) | 2022.06.05 |
[Spring] ๋น ํ์ฒ๋ฆฌ๊ธฐ, Bean Postprocessor (0) | 2022.06.04 |
[Spring] Pointcut, Advise, Advisor ๊ธฐ๋ณธ ๊ฐ๋ ์ ์์๋ณด์ (0) | 2022.06.03 |