JPA ์ฑ๋ฅ ๊ฐ์ ํ
by JiwonDev๋๋ถ๋ถ ์ฑ๋ฅ ๋ฌธ์ ๋ ์กฐํ์์ ๋ฐ์ํ๋ค. ์ฑ๊ณผ JPA๋จ์์ ํ ์ ์๋ ์ต์ ํ๋ฅผ ์์๋ณด์.
๐ญ ์์ฝ
1. ์ฐ์ Entity๋ก ๊ฐ์ฒด์งํฅ์ ์ผ๋ก ์กฐํํ๋ค.
- ์ฐ๊ด ๊ฐ์ฒด ์กฐํ๋ Lazy๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ๊น๊ณ , ์กฐํํ ๋ join fetch๋ก ์ฌ์ฉํ๋ ์ฐ๊ด ๊ฐ์ฒด๋ฅผ ํ๋ฐฉ์ฟผ๋ฆฌ๋ก ๊ฐ์ ธ์จ๋ค.
2. Entity์์ ์ฐ๊ด๊ด๊ณ์ธ ์ปฌ๋ ์
(List, Set)์ ๊ฐ์ ธ์ค๊ณ ์ถ๋ค๋ฉด
- ํ์ด์ง์ด ํ์์๋ค๋ฉด join fetch๋ก ๊ฐ์ ธ์จ๋ค. (๋จ ์ปฌ๋ ์ ์ด 1๊ฐ์ผ ๊ฒฝ์ฐ์๋ง ํจ์น์กฐ์ธ ๊ฐ๋ฅ)
- ํ์ด์ง์ด ํ์ํ๋ค๋ฉด ๊ทธ๋ฅ Lazy๋ก ๋ ๋๊ณ @BatchSize (hibernate.default_batch_fetch_size ์ถ์ฒ)๋ฅผ ์ฌ์ฉํ๋ค.
- ์ฌ์ค ์ด์ ๋๋ง ํ๋๋ผ๋ ๋๋ถ๋ถ ์๋น์ค์์๋ ๋ฌธ์ ์๋ค.
3. ์ด ๋จ๊ณ๋ก ํด๊ฒฐ์ด ์๋๋ ๋ํ ์๋น์ค๋ผ๋ฉด ๊ทธ๋ฅ Redis ๊ฐ์ ์บ์๋ฅผ ์ฐ์.
- ์ฐธ๊ณ ๋ก DTO ๋ฅผ ์บ์ํด์ผํ๋ค. Entity๋ฅผ ์บ์(2์ฐจ์บ์)ํ๋๊ฑด ์ ํฉ์ฑ ๋ฌธ์ ๋ก ์ค๋ฌด์์ ์ฌ์ฉํ๊ธฐ ๋๋ฌด ๊น๋ค๋กญ๋ค.
- ์ฌ์ค ๊ทธ์ ๋์ ๋ํ์๋น์ค๋ผ๋ฉด, ์กฐํ์์๋ JPA๋ฅผ ํฌ๊ธฐํ๊ณ MyBatis๋ Native SQL ์ ์ฟผ๋ฆฌ๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ค.
4. OSIV๋ ์ฐ์ง๋ง์. ์ฐธ๊ณ ๋ก ๊ธฐ๋ณธ๊ฐ์ด true๋ผ์ ์ง์ ์ค์ ํด์ค์ผํ๋ค.
spring.jpa.open-in-view : true // ๊ธฐ๋ณธ๊ฐ
๐ญ API์์ Entity๋ฅผ ์ง์ ์กฐํํ์ง ๋ง๋ผ.
์ด ์ ๋๋ ๋น์ฐํ ์์์ด์ง๋ง, ์๋์ ๊ฐ์ด ๋์ด๊ฐ๋ฉด ์๋๋ค. ์๋๋ ์ด์ ๋ฅผ ์๊ณ ์์ด์ผ ํ๋ค.
๐คฆโ๏ธ : ์ DTO๋ Service๋ฅผ ์ฌ์ฉํ๋๊ณ ..? ๊ทธ๋ฅ ๋จ๋ค์ด ๋ค ๊ทธ๋ ๊ฒ ํ๋๊น?"
- ๋๋ฉ์ธ์ด API์ ์์กด์ฑ์ด ์๊ธด๋ค. ๋์ค์ ๋ณ๊ฒฝ, ์ ์ง๋ณด์ํ๊ธฐ ๋งค์ฐ ์ด๋ ต๋ค.
- ์บก์ํ๊ฐ ๊นจ์ง๋ค. ๊ฐ์ ์ด๋์ ๊บผ๋๋์ง, ์ด๋์ ๋ณ๊ฒฝ๋์๋์ง ์๊ธฐ ์ด๋ ต๋ค.
- JPA ์ฐ๊ด ๊ด๊ณ ๋๋ฌธ์ 1+N ๋ฌธ์ ๊ฐ ๋ฐ์ํ๊ธฐ ์ฝ๋ค. ( ํ์ 1๊ฑด ์กฐํ -> ์ฐ๊ด๋ ์ฃผ๋ฌธ ๊ฐ์ฒด N๊ฑด ์กฐ์ธํด์ ๊ฐ์ด ์กฐํ)
@GetMapping("/api/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;
}
๐ ํด๊ฒฐ์ฑ - Lazy์ ํจ์น์กฐ์ธ
1. ์ฐ๊ด๊ด๊ณ๋ Lazy๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉํ๋ค.
- ์คํดํ๋ฉด ์๋๋๊ฒ, Lazy๋ ๋ง๋ฅ์ด ์๋๋ค. ์ฐ๊ด ๊ด๊ณ๋ฅผ ์ฌ์ฉํ๋ ์์ ์ ๊ฒฐ๊ตญ 1+N๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
2. ์ ๋๋ก Entity ๋ฅผ ์ง์ ๋ ธ์ถ์ํค์ง ์๋๋ค. DTO๋ฅผ ์ฌ์ฉํ๋ค.
3. ์ฐ๊ด๊ด๊ณ๋ฅผ ์ฌ์ฉํ ๋์๋ ํจ์น์กฐ์ธ(join fetch)์ผ๋ก ๊ฐ์ ธ์จ๋ค
@GetMapping("/api/orders")
public List<SimpleOrderDto> ordersV3() {
List<Order> orders = orderRepository.findAllWithMemberDelivery();
List<SimpleOrderDto> result = orders.stream()
.map(o -> new SimpleOrderDto(o))
.collect(toList());
return result;
}
// orderRepository.findAllWithMemberDelivery
public List<Order> findAllWithMemberDelivery() {
return em.createQuery(
"select o from Order o" +
" join fetch o.member m" + // Inner Join ์ผ๋ก o.member๋ฅผ ๊ฐ์ ธ์จ๋ค.
" join fetch o.delivery d", Order.class) // o.delivery๋ ๊ฐ์ ธ์จ๋ค.
.getResultList();
}
JPQL ์กฐ์ธ์ fetch๋ฅผ ์ฌ์ฉํ๋ฉด๋ ์ฐ๊ด ๊ฐ์ฒด(๋๋ ์ปฌ๋ ์ )๊น์ง Inner Join์ ์ฌ์ฉํด์ ํ ์ฟผ๋ฆฌ๋ก ๊ฐ์ ธ์จ๋ค.
์ฐธ๊ณ ๋ก Lazy์ ๊ทธ๋ฅ ์กฐ์ธ๋ง ์ฌ์ฉ(select m form Member m join m.team)์ ์ฐ๊ด ๊ฐ์ฒด(team) ๊ฐ์ ๋ฏธ๋ฆฌ ๊ฐ์ ธ์ค์ง ์๋๋ค.
4. (๊ทธ๋๋ ์ฑ๋ฅ์ด ์๋์จ๋ค๋ฉด) JPA๋ฅผ ํฌ๊ธฐํ๊ณ , ํด๋น DB์ SQL์ ์ง์ ์์ฑํ๋ค.
// JPA Native Query๋ฅผ ์ด์ฉํด๋ ๋๊ณ , ๊ทธ๋ฅ JDBC Template ์ ์ฌ์ฉํด๋ ๋๋ค. (Mybatis ๋ฑ)
String sql = "SELECT ID, AGE, NAME, TEAM_ID" +
" FROM MEMBER WHERE AGE > ?";
Query nativeQuery = em.createNativeQuery(sql, Member.class)
.setParameter(1, 20);
List<Member> resultList = nativeQuery.getResultList();
๐ : ๊ทธ๋ฅ ์ํ๋ ๊ฐ๋ง ์ฟผ๋ฆฌ๋ก ๋ฝ์์ DTO๋ก ๋ฐํํ๋ฉด ์๋๋์?
Entity๋ฅผ ๊ฑฐ์น์ง ์๊ณ , ์ผ๋ฐ ์ฟผ๋ฆฌ๋ฌธ์ฒ๋ผ ๊ฐ์ ์ ํํด์ ๊ฐ์ ธ์ฌ ์ ์๋ค. DB I/O ์ฑ๋ฅ์ด ์กฐ๊ธ ์ข์์ง๊ธด ํ๋ค.
๋จ ์ฌ์ฌ์ฉ์ฑ์ด ๋จ์ด์ง๊ณ DTO๊ฐ ๋ฆฌํฌ์งํ ๋ฆฌ์ ๋ค์ด๊ฐ์ ์ถ์ฒํ๋ ๋ฐฉ๋ฒ์ ์๋๋ค. (API์ DB์ ์์กด์ฑ์ด ์๊ธด๋ค.)
@GetMapping("/api/orders")
public List<OrderSimpleQueryDto> ordersV4() {
return orderSimpleQueryRepository.findOrderDtos();
}
public List<OrderSimpleQueryDto> findOrderDtos() {
return em.createQuery(
"select new package.repository.order
.simplequery.OrderSimpleQueryDto(o.id, m.name, o.orderDate, o.status, d.address)" +
" from Order o" +
" join o.member m" +
" join o.delivery d", OrderSimpleQueryDto.class)
.getResultList();
}
๋ชจ๋ ๊ฑด ํธ๋ ์ด๋ ์คํ์ด๋ค. ๋๋ถ๋ถ์ ๋น์ฆ๋์ค ์๋น์ค๋ ์ด๋ฐ ์ฝ๋๊ฐ ์์ข๋ค.
ํ์ง๋ง ๋ณ๊ฒฝํ ์ผ์ด ์๊ณ , ํต๊ณ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๋ API ํธ๋ํฝ์ด ๋ง๋ค๋ฉด, ์ด๋ฐ ๋ฐฉ๋ฒ๋ ๊ณ ๋ฏผ ๋์์ ๋ค์ด๊ฐ ์ ์๋ค.
๐ญ ์ฐ๊ด๊ด๊ณ ์ปฌ๋ ์ ์กฐํ(OneToMany)
@OneToOne, @ManyToOne ๊ด๊ณ๋ ํ์ํ ๋ ํจ์น์กฐ์ธ์ผ๋ก ๊ฐ์ด ๊ฐ์ ธ์ค๋ฉด ๋๋ค.
๊ทธ๋ ๋ค๋ฉด @OneToMany, ์ฆ ์ฐ๊ด ์ปฌ๋ ์ ์ ์ด๋ป๊ฒ ํด์ผํ ๊น. ๊ทธ๋ฅ ํจ์น์กฐ์ธ์ผ๋ก ํต์งธ๋ก ๋ค ๊ฐ์ ธ์ค๋ฉด ๋๋๊ฑธ๊น?
๐ : ?? ๊ทธ๋ฅ ์ฐ๊ด๊ด๊ณ์ธ ์ปฌ๋ ์ ๋ join fetch๋ก ๊ฐ์ ธ์ค๋ฉด ๋๋๊ฑฐ ์๋๊ฐ์?
๋ง๋ ๋ง์ด๋ค. ํ์ง๋ง ์๋ฌด ์๊ฐ์์ด ์ฌ์ฉํ๋ฉด ํฐ์ผ๋๋ค.
@GetMapping("/api/orders")
public List<OrderDto> ordersV3() {
List<Order> orders = orderRepository.findAllWithItem();
List<OrderDto> result = orders.stream()
.map(o -> new OrderDto(o))
.collect(toList());
return result;
}
// orderRespoitory.findAllWithItem()
public List<Order> findAllWithItem() {
return em.createQuery(
"select o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d" +
" join fetch o.orderItems oi" + // List<OrderItem> ํจ์น์กฐ์ธ
" join fetch oi.item i", Order.class) // OrderItem.Item ํจ์น์กฐ์ธ
.getResultList();
}
์ด ํจ์น์กฐ์ธ์, ์๋์ SQL๋ฌธ์ ๋ง๋ ๋ค.
Select * from orders o
join order_item oi on o.order_id = oi.order_id
DB๊ณต๋ถ๋ฅผ ํ๋ค๋ฉด ์๊ฒ ์ง๋ง ์ด๋ฐ ํ์์ ์กฐ์ธ ์ฟผ๋ฆฌ๋ ๋๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐฐ๋ก ๋ปฅํ๊ธฐ์์ผ๋ฒ๋ฆฐ๋ค.
Order๊ฐ ๋จ 1๊ฐ ๋ฐ์ ์๋๋ผ๋, OrderItem์ด N๊ฐ๋ผ๋ฉด
์กฐ์ธํ์ ๋ ์ค๋ณต๋ Order ๋ฐ์ดํฐ๋ก N๊ฐ๊ฐ ์ฐํ๊ฒ๋๋ฒ๋ฆฐ๋ค. (์ฌ๋ฌ ๊ฐ๋ผ๋ฉด ๊ทธ๋งํผ ํ ์๊ฐ ๊ณฑํด์ง๋ค.)
์ค์ ๋ก ํ์ด๋ฒ๋ค์ดํธ์ 2๊ฐ์ด์์ ํจ์น์กฐ์ธ(entity -> toMany -> toMany)์ ์๋ํ๋ฉด `MultipleBagFetchException` ๋ฐ์์ํจ๋ค.
๐ ํด๊ฒฐ์ฑ - ํจ์น์กฐ์ธ์ distinct ๋ฅผ ๋ฃ์ด์ ์ค๋ณต์ ์ ๊ฑฐํ๋ค.
์ค์ ๋ก JPQL ์์ distinct ํค์๋๋ DB SQL์ ๊ทธ๋๋ก ๋ถ์ฌ์ฃผ๋ ํจ๊ณผ ์ธ์ ํ๊ฐ์ง ๊ธฐ๋ฅ์ ๋ํด์ค๋ค.
๋ฐ๋ก Entity ID๋ฅผ ์ด์ฉํด ์ค๋ณต๋ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ๋ค๋ฉด, JPA ์ฑ ๋ฉ๋ชจ๋ฆฌ์์์ ์ค๋ณต์ ์ ๊ฑฐํด์ค๋ค.
// orderRespoitory.findAllWithItem()
public List<Order> findAllWithItem() {
return em.createQuery(
"select distinct o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d" +
" join fetch o.orderItems oi" + // List<OrderItem> ํจ์น์กฐ์ธ
" join fetch oi.item i", Order.class) // OrderItem.Item ํจ์น์กฐ์ธ
.getResultList();
}
๐งจ ์ฃผ์ (ํ์ด์ง) - ์ฟผ๋ฆฌ๋ ํ๋์ง๋ง, ๋ฐ์ดํฐ ์ค๋ณต์ ๊ทธ๋๋ก๋ค.
์ฌ๊ธฐ์ ์ค์ํ์ ์, ์ฑ ์์์๋ ์ค๋ณต์ด ํด๊ฒฐ๋ ๊ฒ ๊ฐ์ง๋ง ์์ฒญ ์ฟผ๋ฆฌ๋ฌธ์ ๊ทธ๋๋ก ๋ผ๋ ์ ์ด๋ค.
์กฐ์ธ๋ ํ ์ด๋ธ์ Order ๋ง ์ค๋ณต์ผ ๋ฟ, Item์ ๋ํ ์ ๋ณด๋ ์ค๋ณต์ด ์๋๋ค.
1. ์๋์ ๊ฐ์ ํ์ด์ง ์ฟผ๋ฆฌ๊ฐ ์๋ค๊ณ ์๊ฐํด๋ณด์.
public List<Order> findAllWithMemberDelivery() {
return em.createQuery(
"select o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d", Order.class)
.setFirstResult(1) // ์์์ง์
.setMaxResults(100) // ํ์ด์ง ๊ฐ์
.getResultList();
}
ํ ์คํธํ ๋์๋ ์ฑ ์์์ ์ค๋ณต์ด ์ ๊ฑฐ๋๋๊น ์ ์์๋ ํ๋ ๊ฒ์ฒ๋ผ ๋ณด์ธ๋ค.
ํ์ง๋ง ์ค์ ๋ก๋? ๋ง๋ ์๋๋ ๊ฐ์์ ์ฟผ๋ฆฌ๊ฐ ๋ปฅํ๊ธฐ๋์ด ์ฐํ๊ณ ์๋ค. ์ฆ, ์ปฌ๋ ์ ์ ํจ์น์กฐ์ธ ํด๋ฒ๋ฆฌ๋ฉด ํ์ด์ง์ด ์ฌ์ค ์ ๋ถ๊ฐ๋ฅํด์ง๋ค.
2. ํ ์ฟผ๋ฆฌ์์ ์ปฌ๋ ์ ์ ๋ํ ํจ์น์กฐ์ธ์ ๋จ ํ ์ค๋ง ์ฌ์ฉํ ์ ์๋ค.
ํ๋์ Order์์ List<OrderItems> , List<SpecialItems> ์ด๋ ๊ฒ 2๊ฐ ์ด์์ ์ปฌ๋ ์
์ ํจ์น์กฐ์ธํ๋ฉด ์ ๋ ์๋๋ค.
์๋ํ๋ฉด ๋ฐ์ดํฐ๊ฐ ์์ฒญ๋๊ฒ ๋ปฅํ๊ธฐ ๋์ด [ ํ์์ * ์์ดํ
์ * ์์ดํ
์ ] ์ ๋ฏธ์น ํ๋ฐฉ ์ฟผ๋ฆฌ๊ฐ ๋์ค๊ธฐ ๋๋ฌธ์ด๋ค.
์ค์ ๋ก ์ด๋ฐ์์ ํจ์น์กฐ์ธ์ ์ฌ์ฉ์ ์ ํฉ์ฑ์ ๋ณด์ฅํ ์ ์๊ธฐ์ ์๋์ ๊ฐ์ ํ์ด๋ฒ๋ค์ดํธ ๊ฒฝ๊ณ ๋ก๊ทธ๊ฐ ์ฐํ๋ค.
๐ ํ์ด์ง ํด๊ฒฐ์ฑ - @BatchSize
1. ๋จผ์ ToOne ๊ด๊ณ๋ง ๋ฐ๋ก ํจ์น์กฐ์ธ์ผ๋ก ๊ฐ์ ธ์จ๋ค.
2. ์ปฌ๋ ์ ์ ํจ์น์กฐ์ธ ํ์ง์๊ณ , ๊ธฐ์กด Lazy๋ก ๊ทธ๋๋ก ๋๋ค. (์ด๋๋ก ๋๋ฉด 1+N์ด ๋ฐ์ํ๋ค.)
@GetMapping("/api/orders")
public List<OrderDto> orders_page(
@RequestParam(value = "offset", defaultValue = "0") int offset,
@RequestParam(value = "limit", defaultValue = "100") int limit) {
List<Order> orders = orderRepository.findAllWithMemberDelivery(offset, limit);
// order.OrderItems ๋ฅผ ๊บผ๋ผ ๋ 1+N ๋ฐ์
// order.OrderItems.Item ์ ๊บผ๋ด๋ฉด์ 1(Order) + N(OrderItems) + N(Items) ๊ฐ์ ์ฟผ๋ฆฌ ์์ฑ
List<OrderDto> result = orders.stream().
.map(o -> new OrderDto(o))
.collect(toList());
return result;
}
public List<Order> findAllWithMemberDelivery(int offset, int limit) {
return em.createQuery(
"select o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d", Order.class) // ์ผ๋จ ์ปฌ๋ ์
์ ํจ์น์กฐ์ธ ์ํ๋ค.
.setFirstResult(offset) // ์์์ง์
.setMaxResults(limit) // ํ์ด์ง ๊ฐ์
.getResultList();
}
3. @BatchSize ๋ฅผ ์ฌ์ฉํ๋ค. (๋๋ hibernate.default_batch_fetch_size ์ค์ ์ ์ด์ฉํด ์ ์ญ์ ์ผ๋ก ์ค์ ํ๋ค.)
์ด ์ต์ ์ ์ฌ์ฉํ๋ฉด ์ปฌ๋ ์ ์ด๋ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์ค์ ํ ๊ฐ์๋งํผ [ Where IN (pk1, pk2 ...) ]๋ก ์กฐํํ๋ค.
- ํ ์ด๋ธ๋ง๋ค key๋ก ์ฐ์ด์ ์กฐํํ๋๊ฑฐ๋ผ, ์ค๋ณต ๋ฐ์ดํฐ๊ฐ ์๋ค. (SQL ์ ๊ทํ)
- Where in (key)๋ ๋๋ฑ๋น๊ต(=)์ ๋์ผํ ์ฑ๋ฅ์ด๋ผ DB์๋ ์ต์ ํ ๋์๋ค.
@BatchSize(size = 100) // ์ํฐํฐ์ ์ค์
@Entity
public class Item {
...
}
@Entity
public class Parent {
@BatchSize(size = 100) // ์ปฌ๋ ์
ํ๋์ ์ค์
@OneToMany(mappedBy = "parent")
private List<Child> children = new ArrayList<>();
}
spring: # resource/application.yml
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
format_sql: true
default_batch_fetch_size: 1000 # ๊ธฐ๋ณธ ๋ฐฐ์น์ฌ์ด์ฆ (limit 1000)
@BatchSize๋ฅผ ์ฌ์ฉํ๋ฉด ์ปฌ๋ ์ ์ด๋ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์ค์ ํ ๊ฐ์๋งํผ [ WHERE IN ์ฟผ๋ฆฌ ]๋ก ํ์ํ ์ฐ๊ด ๊ฐ์ ๊ฐ์ ธ์จ๋ค.
์ฟผ๋ฆฌ๋ฅผ ํ๋๋ก๋ ๋ชป๋ง๋ค์ง๋ง, 1+N+N ์ฟผ๋ฆฌ๋ฅผ 1+1+1 ์ฟผ๋ฆฌ๋ก ๋ฐ๊ฟ ์ ์๋ค.
์ฐธ๊ณ ๋ก 100๊ฐ๋ฅผ ์กฐํํ๋๋ฐ, @BatchSize(20)์ด๋ผ๋ฉด JPA๋ ์ฟผ๋ฆฌ๋ฅผ 5๋ฒ (100/20 = 5๊ฐ) ๋ ๋ฆฐ๋ค.
ํจ์น์กฐ์ธ์ ์ค๋ณต์ ๋ฐ์ํ์ง๋ง, ์ฟผ๋ฆฌ๋ ๋จ 1๊ฐ๊ฐ ์ฐํ๋ค. (ํ์ด์ง์ ์ฌ์ค์ ๋ถ๊ฐ๋ฅ)
๊ธฐ์กด์ ์ฝ๋๋ Order * OrderItems * Items ๊ฐ์๋งํผ ์ฟผ๋ฆฌ๊ฐ ์ฐํ๋ค.
๐ : ์ฟผ๋ฆฌ๊ฐ ์ ๊ฒ ์ฐํ๋๋ก BatchSize๋ฅผ ํฌ๊ฒํ๋๊ฒ ์ข์๊น์?
์ฌ์ค DB์ ํ์ฌ๋ง๋ค ๋ฌ๋ผ์ ์ ๋ต์ ์์ง๋ง, ์ต๋ 1000๊ฐ๋ฅผ ์๋๋๋ก ์ค์ ํด์ฃผ๋๊ฒ ์ข๋ค.
- Where In ์ฟผ๋ฆฌ๊ฐ 1000๊ฐ๊ฐ ๋์ด๊ฐ๋ฉด DB ์ค๋ฅ๋ฅผ ์ผ์ผํค๋ ๊ฒฝ์ฐ๊ฐ ์ข ์ข ์์ด์ ๊ทธ ์ด์์ ๊ถ์ฅํ์ง ์๋๋ค.
- ๊ฐ์๊ฐ ๋๋ฌด ๋ง์ผ๋ฉด ํ๋ฒ์ ์ฒ๋ฆฌํด์ผํ ์์ฒญ ๋ฐ์ดํฐ๊ฐ ์๋นํ ์ปค์ง๋ค. ์ด๋ DB์ ์ฑ ์๋ฒ์ ๋ถ๋ด์ ์ค ์ ์๋ค.
- ๊ฐ์๊ฐ ์์ผ๋ฉด DB, ์ฑ์๋ฒ์ ๋ถํ๋ ์ค๊ฒ ์ง๋ง, ๊ทธ๋งํผ ์์ฒญ ํ์๊ฐ ๋ง์์ ธ์ ๋คํธ์ํฌ ๋๊ธฐ์๊ฐ์ด ๊ธธ์ด์ง๋ค.
์ฑ์๋ฒ(WAS), DB์ ๋ถ๋ด์ ์ฃผ์ง์๊ฒ 100์ผ๋ก ์ค์ ํด๋๊ณ , ๋์ค์ ๋ณ๊ฒฝํ๋๊ฑธ ์ถ์ฒํ๋ค.
์ฐธ๊ณ ๋ก ๋๋ฌด ํฌ๋ฉด ์ฑ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ง์ด ์ฌ์ฉํด์ ์๋๋ค๋ ๋ง๋ ํ๋๋ฐ, ์ด๋ ์ ํ ์๊ด์๋ ์ด์ผ๊ธฐ์ด๋ค.
List<OrderItem>์ ์ฟผ๋ฆฌ๋ฅผ ์ฌ๋ฌ๋ฒ ์ฐ๋๋ค๊ณ ํด์, ์ ๋ฐ๋ง ์ฑ์ฐ๊ณ API๋ฅผ ์ฌ๋ฌ๋ฒ ์ ์กํ์ง๋ ์๋๋ค. ๊ฒฐ๊ตญ ์ฟผ๋ฆฌ๊ฐ ํ๋ฐฉ์ด๋ ์ฌ๋ฌ๋ฐฉ์ด๋ ์ฑ์์ List๊ฐ ๊ฐ๋ ์ฐฐ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๊ธฐ ๋๋ฌธ์ ์ฑ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋๊ณผ๋ ์๊ด์๋ค.
+ ํน๋ณํ ์ด์ ๊ฐ ์๋ค๋ฉด @BatchSize๋ก ํ๋ํ๋ ์ค์ ํ๊ธฐ ๋ณด๋ค๋, ์๋์ฒ๋ผ ์ ์ญ์ ์ผ๋ก ์ค์ ํด๋๋ ๊ฑธ ์ถ์ฒํ๋ค.
spring: # resource/application.yml
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
format_sql: true
default_batch_fetch_size: 1000 # ๊ธฐ๋ณธ ๋ฐฐ์น์ฌ์ด์ฆ (limit 1000)
๐ ํธ๋ ์ด๋ ์คํ [ ์ปฌ๋ ์ ํจ์น์กฐ์ธ vs @BatchSize ]
๊ทธ๋ฅ @BatchSize๋ฅผ ์ ์ญ์ ์ผ๋ก ์ฌ์ฉํ๋๊ฑธ ๊ถ์ฅํ์ง๋ง, ์ฑ๋ฅ ํ๋์ด ํ์ํ ์์ ์๋ ์ปฌ๋ ์ ํจ์น์กฐ์ธ๋ ์๊ฐํด์ผํ๋ค.
๋ฌผ๋ก ํ์ด์ง์์ ์ฌ์ฉ ๋ถ๊ฐ๋ฅํ์ง๋ง, ๊ทธ๊ฒ ์๋๋ผ๋ฉด ์ปฌ๋ ์ ํจ์น์กฐ์ธ์ด ํ๋ฐฉ ์ฟผ๋ฆฌ๊ฐ ๋์ค๊ธฐ์ ์ฑ๋ฅ์ด ๋ ์ข์ ์ ์๋ค.
๊ฒฐ๊ตญ [DB ๋คํธ์ํฌ ํธ์ถ ํ์]์ [์ ์ก ๋ฐ์ดํฐ๋]์ ํธ๋ ์ด๋ ์คํ๋ฅผ ๊ณ ๋ คํด์ผํ๋ค.
์ปฌ๋ ์ ์ด ๋ฑ ํ๋์ด๊ณ ๋ฐ์ดํฐ๊ฐ ๋ช ๊ฐ ์๋๋ค๋ฉด ํจ์น์กฐ์ธ ํ๋ฐฉ์ฟผ๋ฆฌ๋ก ๊ฐ์ ธ์ค๋๊ฒ ์ฑ๋ฅ์ด ์ข๋ค.
ํ์ง๋ง ๋ฐ์ดํฐ๊ฐ 1000๊ฑด์ด ๋๋๋ค๋ฉด, ๊ธด ํ๋ฐฉ ์ฟผ๋ฆฌ๋ณด๋ค ๋ช๋ฒ ๋ ํธ์ถํ๋๋ผ๋ @BatchSize ๋ฅผ ์ฐ๋๊ฒ ์ฑ๋ฅ์ด ์ข๋ค.
๐ ๋ค๋ฅธ๋ฐฉ๋ฒ - ๋ง์ฝ ์ฐ๊ด ์ปฌ๋ ์ ์ DTO๋ก ์ง์ ์กฐํํ๊ณ ์ถ๋ค๋ฉด
Entity๋ฅผ ๋ง๋ค์ง ์๊ณ DTO๋ก ๋ฐ๋ก ์กฐํํ๋ ๊ฒฝ์ฐ @BatchSize๋ฅผ ์ฌ์ฉํ ์ ์๋๋ฐ, ์ด๋๋ ์ด๋ป๊ฒ ํด์ผํ ๊น?
List<OrderDto> ์์ List<OrderItemDto> ๊ฐ ์กด์ฌํ๋ค.
1. ToOne ๊ด๊ณ๋ฅผ ๋จผ์ ์กฐํํ๋ค. OrderDto์์ ์ปฌ๋ ์ ๋ถ๋ถ์ ์ผ๋จ ๋น์๋๋ค.
2. ๋ฐ๋ณต๋ฌธ์ผ๋ก ToMany ๊ด๊ณ๋ฅผ ๋ณ๋๋ก ์กฐํํด์ ํ๋์ฉ ๋ผ์๋ฃ๋๋ค. (๋ฐ๋ณต ํ์๋งํผ ์ฟผ๋ฆฌ ๋ฐ์, ์ฌ์ค์ 1+N ๋ฌธ์ ์ ๋์ผ)
public List<OrderQueryDto> findOrderQueryDtos() {
//๋ฃจํธ ์กฐํ(toOne ์ฝ๋๋ฅผ ๋ชจ๋ ํ๋ฒ์ ์กฐํ)
List<OrderQueryDto> result = findOrders();
//๋ฃจํ๋ฅผ ๋๋ฉด์ ์ปฌ๋ ์
์ถ๊ฐ(์ถ๊ฐ ์ฟผ๋ฆฌ ์คํ)
result.forEach(o -> {
List<OrderItemQueryDto> orderItems = findOrderItems(o.getOrderId());
o.setOrderItems(orderItems);
});
return result;
}
private List<OrderQueryDto> findOrders() {
return em.createQuery(
"select new package.OrderQueryDto(o.id, m.name, o.orderDate, o.status, d.address)" +
" from Order o" +
" join o.member m" +
" join o.delivery d", OrderQueryDto.class)
.getResultList();
}
private List<OrderItemQueryDto> findOrderItems(Long orderId) {
return em.createQuery(
"select new package.OrderItemQueryDto(oi.order.id, i.name, oi.orderPrice, oi.count)" +
" from OrderItem oi" +
" join oi.item i" +
" where oi.order.id = : orderId", OrderItemQueryDto.class)
.setParameter("orderId", orderId)
.getResultList();
}
์ด๋ฐ ๊ฒฝ์ฐ์๋ ์ฟผ๋ฆฌ๋ฅผ ๋ฐ๋ณตํ์ง๋ง๊ณ , List<OrderId>๋ฅผ ๋ง๋ค์ด์ Where in ํ๋ฐฉ์ฟผ๋ฆฌ๋ก ์กฐํํ์.
๊ทธ๋ฆฌ๊ณ OrderDto์ OrderItemDto๋ฅผ ํ๋์ฉ ๋งคํํด์ฃผ๋ฉด ๋๋๋ฐ, ์ด๋ Map ์๋ฃํ์ ์ด์ฉํ๋ฉด ๊ฐ๋จํ๊ฒ ์ฒ๋ฆฌํ ์ ์๋ค.
public List<OrderQueryDto> findAllByDto_optimization() {
//๋ฃจํธ ์กฐํ(toOne ์ฝ๋๋ฅผ ๋ชจ๋ ํ๋ฒ์ ์กฐํ)
List<OrderQueryDto> result = findOrders();
//orderItem ์ปฌ๋ ์
์ MAP ํ๋ฐฉ์ ์กฐํ
Map<Long, List<OrderItemQueryDto>> orderItemMap = findOrderItemMap(toOrderIds(result));
//๋ฃจํ๋ฅผ ๋๋ฉด์ ์ปฌ๋ ์
์ถ๊ฐ(์ถ๊ฐ ์ฟผ๋ฆฌ ์คํX)
result.forEach(o -> o.setOrderItems(orderItemMap.get(o.getOrderId())));
return result;
}
private Map<Long, List<OrderItemQueryDto>> findOrderItemMap(List<Long> orderIds) {
List<OrderItemQueryDto> orderItems = em.createQuery(
"select new package.OrderItemQueryDto(oi.order.id, i.name, oi.orderPrice, oi.count)" +
" from OrderItem oi" +
" join oi.item i" +
" where oi.order.id in :orderIds", OrderItemQueryDto.class)
.setParameter("orderIds", orderIds)
.getResultList();
return orderItems.stream() // ์กฐํํ List<OrderItemDto> ๋ฅผ Map<id, OrderItemDto> ๋ก ๋ณํ
.collect(Collectors.groupingBy(orderItemQueryDto -> orderItemQueryDto.getOrderId()));
}
๐ญ OSIV (Open Session In View) ๋๊ธฐ
์ฌ๋ด์ผ๋ก JPA์์๋ Open EntityManager In View๋ผ๊ณ ๋ถ๋ฅธ๋ค.
๊ทผ๋ฐ ๊ธฐ์กด์ ํ์ด๋ฒ๋ค์ดํธ์์ Open Session In View๋ผ๊ณ ์ฌ์ฉํ๊ธฐ์, OSIV๋ผ๊ณ ๋ง์ด ์ฌ์ฉํ๋ค.
์ด๋ฅผ ์คํ๋ง ์งํ์์ ์ธ์งํ๋์ง, ์ค์ ๊ฐ์ open-in-view๋ผ๊ณ ์ด๋ฆ์ ์ง์๋ค.
spring.jpa.open-in-view : true // ๊ธฐ๋ณธ๊ฐ
๐ OSIV๋ฅผ ์ ์ฌ์ฉํ๋๊ฑฐ์ฃ ?
JPA ์ ์ง์ฐ๋ก๋ฉ์ ํธ๋์ญ์ ๋ฒ์๋ฅผ ๋ฒ์ด๋๋ฉด (View์ ์ ๋ฌ๋ ์ดํ์๋) ์ฟผ๋ฆฌ ํธ์ถ์ด ๋ถ๊ฐ๋ฅํ๋ค.
๊ทธ๋์ ํธ๋์ญ์ ์ด ๋๋๊ธฐ์ , ์ง์ฐ๋ก๋ฉ๋ ๊ฐ๋ค์ด ํ์ํ๋ค๋ฉด ๋ฏธ๋ฆฌ ํธ์ถํด๋ฌ์ผํ๋ค.
ํ์ง๋ง View์์ ๊ฐ์ด ํ์์๋์ง ์๋์ง๋ ๋ฏธ๋ฆฌ ์๊ธฐ ์ด๋ ต๋ค. ๊ทธ๋์ OSIV๋ผ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
OSIV๋ ์ฝ๊ฒ๋งํด ํธ๋์ญ์ ์ ์์(DB ์ปค๋ฅ์ ์ด ์์ฑ)๋ถํฐ API ์๋ต์ด ๋๋ ๋ ๊น์ง ์์์ฑ ์ปจํ ์คํธ์ DB ์ปค๋ฅ์ ์ ๋ซ์ง์๊ณ ์ ์ง์์ผ์ฃผ๋ ๊ธฐ๋ฅ์ด๋ค.
๐ ์ด ๊ทธ๋ผ ์ข์๊ฑฐ ์๋๊ฐ์? ์ฑ๋ฅ์ ๋ฌธ์ ๊ฐ ์๊ธธ๋ ค๋?
๊ทธ๋ ๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค๋ฅ์ ์ ๋๋ฌด ์ค๋์๊ฐ ๋์ ์ฌ์ฉํ๋ค.
๋ง์ฝ ์ปจํธ๋กค๋ฌ์์ ์ธ๋ถ API๋ฅผ ํธ์ถํ๋ค๋ฉด, ๊ทธ API์ ๋๊ธฐ์๊ฐ๋งํผ ์ปค๋ฅ์ ์ด ์ ์ง๊ฐ ๋์ด์๋ค.
์ด๋ ์ฑ๋ฅ์์ ๋ฌธ์ ๋ ์๊ฒ ์ง๋ง, ๊ฐ๋ฐ์๊ฐ ์ฟผ๋ฆฌ๊ฐ ์คํ๋๋ ์์ ์ ์์ํ๊ธฐ ์ด๋ ต๋ค.
๊ทธ๋์ ๋๋๋ก์ด๋ฉด OSIV๋ฅผ ๋๋๊ฑธ ์ถ์ฒํ๋ค. ํนํ ์์ฆ ์ ํํ๋ SPA - Rest API ๋ฐฉ์์ด๋ฉด ๋ฑํ ํ์์๊ธฐ๋ ํ๋ค.
(์ฐธ๊ณ ๋ก ์ปค๋ฅ์ ์ด ์๋๋ฐ ์ง์ฐ๋ก๋ฉ์ ์๋ํ๋ฉด LazyInitializationException ์์ธ๊ฐ ๋ฐ์ํ๋ค.)
OSIV๊ฐ ์์ด๋ DTO, Application ๊ณ์ธต (Facade), ์ปค๋งจ๋์ ์ฟผ๋ฆฌ๋ถ๋ฆฌ (CQS)๋ฑ์ผ๋ก ํด๊ฒฐํ ์ ์๋ค.
๋ฌผ๋ก ์ด๋๋ฏผ ํ์ด์ง์ฒ๋ผ ๋ฑํ ์ปค๋ฅ์ ๊ด๋ฆฌ๊ฐ ์ค์ํ์ง์๋ค๋ฉด, OSIV๋ฅผ ์ฌ์ฉํ๊ณ ์ฝ๋๋ฅผ ๊น๋ํ๊ฒ ๋๋๊ฒ ๋์ ์ ์๋ค.
'๐ฑBackend > JDBC & JPA' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
QueryDSL + JPA (0) | 2022.02.02 |
---|---|
Spring Data JPA (0) | 2022.01.31 |
JPA 10 # ๊ฐ์ฒด ์ฟผ๋ฆฌ ์ธ์ด, JPQL (0) | 2021.11.16 |
JPA #9 ๊ฐ ํ์ , ์ปฌ๋ ์ , ์๋ฒ ๋๋ ํ์ (0) | 2021.11.09 |
JPA #8 ํ๋ก์์ ์ง์ฐ๋ก๋ฉ (join fetch) (0) | 2021.11.09 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev