JPA API and Performance Optimization

์˜ํ•œ๋‹˜์˜ ์‹ค์ „! ์Šคํ”„๋ง ๋ถ€ํŠธ์™€ JPA ํ™œ์šฉ2 - API ๊ฐœ๋ฐœ๊ณผ ์„ฑ๋Šฅ ์ตœ์ ํ™” ๊ฐ•์˜๋ฅผ ์š”์•ฝํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.

Project

API Basic

API์™€ Template Engin์€ ๊ณตํ†ต ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์•ผ ํ•˜๋Š” ์š”์†Œ๊ฐ€ ๋‹ค๋ฅด๋ฏ€๋กœ ํŒจํ‚ค์ง€๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Œ.

ใ„ด src.main.java
    ใ„ด example
        ใ„ด api
        ใ„ด controller

ํšŒ์› ๋“ฑ๋ก API

์—”ํ‹ฐํ‹ฐ ๋Œ€์‹  API ์š”์ฒญ ์ŠคํŽ™์— ๋งž๋Š” ๋ณ„๋„ DTO๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ.

  • ์—”ํ‹ฐํ‹ฐ์™€ ํ”„๋ ˆ์  ํ…Œ์ด์…˜(API) ๊ณ„์ธต์„ ์œ„ํ•œ ๋กœ์ง ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ

  • ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด๋„ API ์ŠคํŽ™์ด ๋ณ€ํ•˜์ง€ ์•Š์Œ

    • ์—”ํ‹ฐํ‹ฐ ํ•„๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋”๋ผ๋„ ์ปดํŒŒ์ผ ์—๋Ÿฌ๋กœ ๋ฐ”๋กœ ์ฒดํฌ ๊ฐ€๋Šฅ

  • ์—”ํ‹ฐํ‹ฐ์™€ API ์ŠคํŽ™์„ ๋ช…ํ™•ํ•˜๊ฒŒ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ

  • ์‹ค๋ฌด์—์„œ๋Š” ์ ˆ๋Œ€ ์—”ํ‹ฐํ‹ฐ๋ฅผ API ์ŠคํŽ™์— ๋…ธ์ถœํ•˜์ง€ ๋ง์ž!

@PostMapping("/api/members")
public CreateMemberResponse saveMember(@RequestBody @Valid CreateMemberRequest request) {
    Member member = new Member();
    member.setName(request.getName());
    member.setAddress(request.getAddress());

    Long id = memberService.join(member);
    return new CreateMemberResponse(id);
}

@Data
static class CreateMemberRequest {
    @NotEmpty
    private String name;

    @Embedded
    private Address address;
}

@Data
static class CreateMemberResponse {
    private Long id;

    public CreateMemberResponse(Long id) {
        this.id = id;
    }
}

ํšŒ์› ์ˆ˜์ • API

๋“ฑ๋ก๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋ณ„๋„ DTO ์‚ฌ์šฉํ•˜๊ธฐ

  • ๋ณ€๊ฒฝ๊ฐ์ง€๋ฅผ ํ™œ์šฉํ•ด์„œ ๋ฐ์ดํ„ฐ ์ˆ˜์ •ํ•˜๊ธฐ

  • CQS(Command-Query Separation) : ๊ฐ€๊ธ‰์ ์ด๋ฉด Command์™€ Query๋ฅผ ๋ถ„๋ฆฌํ•˜์ž.

Controller

@PutMapping("/api/members/{id}")
public UpdateMemberResponse updateMember(@PathVariable("id") Long id, @RequestBody @Valid UpdateMemberRequest request) {

    memberService.update(id, request.getName()); // Command
    Member findMember = memberService.findOne(id); // Query
    return new UpdateMemberResponse(findMember.getId(), findMember.getName());
}

@Data
static class UpdateMemberRequest {
    private String name;
}

@Data
@AllArgsConstructor
static class UpdateMemberResponse {
    private Long id;
    private String name;
}

Service

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberService {

    //...

    @Transactional
    public void update(Long id, String name) {
        Member member = memberRepository.findOne(id);
        member.setName(name);
        // Transactional commit -> flush (๋ณ€๊ฒฝ๊ฐ์ง€)
    }
}

ํšŒ์› ์กฐํšŒ API

๋“ฑ๋ก, ์ˆ˜์ •๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์—”ํ‹ฐํ‹ฐ๋ฅผ API ์‘๋‹ต ์ŠคํŽ™ ๋งž๋Š” ๋ณ„๋„ DTO๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•˜๊ธฐ

  • ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด๋„ API ์ŠคํŽ™์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Œ

  • Result ํด๋ž˜์Šค๋กœ ์ปฌ๋ ‰์…˜์„ ๊ฐ์‹ธ์ฃผ๋ฉด์„œ ํ–ฅํ›„ ํ•„์š”ํ•œ ํ•„๋“œ๋ฅผ ์ž์œ ๋กญ๊ฒŒ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ

  • API ์š”์ฒญ์— ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ๋…ธ์ถœ (์šฉ๋„์— ๋”ฐ๋ผ DTO๋ฅผ ์ƒ์„ฑ)

@GetMapping("/api/members")
public Result member() {
    List<Member> findMembers = memberService.findMembers();
    List<MemberDto> collect = findMembers.stream()
            .map(m -> new MemberDto(m.getName()))
            .collect(Collectors.toList());

    return new Result(collect.size(), collect);
}

@Data
@AllArgsConstructor
static class Result<T> {
    private int count;
    private T data;
}

@Data
@AllArgsConstructor
static class MemberDto {
    private String name;
}

์ง€์—ฐ ๋กœ๋”ฉ๊ณผ ์กฐํšŒ ์„ฑ๋Šฅ ์ตœ์ ํ™”

์ง€์—ฐ ๋กœ๋”ฉ์œผ๋กœ ๋ฐœ์ƒํ•˜๋Š” ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ๋‹จ๊ณ„์ ์œผ๋กœ ํ•ด๊ฒฐํ•ด๋ณด์ž.

.

์—”ํ‹ฐํ‹ฐ๋ฅผ ์ง์ ‘ ๋…ธ์ถœ

  • ์ˆœํ™˜ ์ฐธ์กฐ ๋ฌธ์ œ ๋ฐœ์ƒ StackOverflowError

    • @JsonIgnore ์„ค์ •์œผ๋กœ ํ•ด๊ฒฐ ๊ฐ€๋Šฅ

  • @JsonIgnore ์„ ์ถ”๊ฐ€ํ•˜๋”๋ผ๋„ ์ง€์—ฐ๋กœ๋”ฉ์œผ๋กœ ์ธํ•œ proxy(bytebuddy) ๊ฐ์ฒด๋ฅผ jackson ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ฝ์„ ์ˆ˜ ์—†๋Š” ๋ฌธ์ œ ๋ฐœ์ƒ Type definition error

    • Hibernate5Module ์„ Spring Bean ์œผ๋กœ ๋“ฑ๋กํ•˜๋ฉด ํ•ด๊ฒฐ ๊ฐ€๋Šฅ

    • ๋‹จ, ์ง€์—ฐ ๋กœ๋”ฉ ๊ฐ์ฒด๋Š” null ์ถœ๋ ฅ, ๊ฐ•์ œ ์ง€์—ฐ ๋กœ๋”ฉ๋„ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ์„ฑ๋Šฅ ์•…ํ™” ๋ฐœ์ƒ

// OrderSimpleApiController.java

@GetMapping("/api/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;
}

์—”ํ‹ฐํ‹ฐ๋ฅผ DTO๋กœ ๋ณ€ํ™˜

  • ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ง์ ‘ ๋…ธ์ถœํ•˜์ง€ ์•Š๊ณ , ์—”ํ‹ฐํ‹ฐ๋ฅผ DTO๋กœ ๋ณ€ํ™˜

  • 1 + N ๋ฌธ์ œ ๋ฐœ์ƒ (์—”ํ‹ฐํ‹ฐ ์ง์ ‘ ๋…ธ์ถœ๊ณผ ๋™์ผ)

    • ์ฒซ ๋ฒˆ์งธ ์ฟผ๋ฆฌ์˜ ๊ฒฐ๊ณผ N๋ฒˆ ๋งŒํผ ์ฟผ๋ฆฌ๊ฐ€ ์ถ”๊ฐ€๋กœ ์‹คํ–‰๋˜๋Š” ๋ฌธ์ œ

    • ex) Order ์กฐํšŒ ์‹œ Member - N๋ฒˆ, Delivery - N ๋ฒˆ, ์ด 1 + N + N ๊ฐœ์˜ ์ฟผ๋ฆฌ ๋ฐœ์ƒ

// OrderSimpleApiController.java

@GetMapping("/api/simple-orders")
public List<SimpleOrderDto> ordersV2() {
    List<SimpleOrderDto> result = orderRepository.findAllByString(new OrderSearch()).stream()
            .map(SimpleOrderDto::new)
            .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(); // LAZY ์ดˆ๊ธฐํ™”
        orderDate = order.getOrderDate();
        orderStatus = order.getStatus();
        address = order.getDelivery().getAddress(); // LAZY ์ดˆ๊ธฐํ™”
    }
}

ํŽ˜์น˜ ์กฐ์ธ ์ตœ์ ํ™”

  • ํŽ˜์น˜ ์กฐ์ธ์„ ์‚ฌ์šฉํ•ด์„œ 1 + N ๋ฌธ์ œ๋ฅผ ์ฟผ๋ฆฌ 1๋ฒˆ ๋งŒ์— ์กฐํšŒ

  • ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐํ•˜๊ณ , ๋‹ค๋ฅธ API์—์„œ ์žฌ์‚ฌ์šฉ์ด ์‰ฌ์›€

// OrderRepository.java

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();
}

DTO๋กœ ๋ฐ”๋กœ ์กฐํšŒ

์กฐํšŒ๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ DTO๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ณผ์ • ํ•„์š” ์—†์ด, ๋ฐ”๋กœ DTO ์กฐํšŒํ•ด์„œ ์„ฑ๋Šฅ ์ตœ์ ํ™”ํ•˜๊ธฐ

  • ์›ํ•˜๋Š” ํ•„๋“œ๋งŒ ์„ ํƒ(SELECT)ํ•ด์„œ ์กฐํšŒ

    • DB <-> ๋„คํŠธ์›Œํฌ ์šฉ๋Ÿ‰ ์ตœ์ ํ™” (์ƒ๊ฐ๋ณด๋‹ค ๋ฏธ๋น„ํ•œ ์ฐจ์ด)

  • new ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•ด์„œ JPQL์˜ ๊ฒฐ๊ณผ๋ฅผ DTO๋กœ ์ฆ‰์‹œ ๋ณ€ํ™˜

  • API ์ŠคํŽ™์— ๋งž์ถ”๋‹ค๋ณด๋‹ˆ ๋ณ€๊ฒฝ์ด ์–ด๋ ค์šฐ๋ฏ€๋กœ, ๋‹ค๋ฅธ API์—์„œ Repository ์žฌ์‚ฌ์šฉ์ด ์–ด๋ ค์›€

  • ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ์ˆœ์ˆ˜ํ•œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•˜๋Š” ๋ ˆํŒŒ์ง€ํ† ๋ฆฌ์™€ ํ™”๋ฉด ์ข…์†์ ์ธ ๋ ˆํŒŒ์ง€ํ† ๋ฆฌ๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ์ถ”์ฒœ

// OrderRepository.java

public List<OrderSimpleQueryDto> findOrderDtos() {
    return em.createQuery(
                    "select new jpabook.jpashop.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();
}

์ปฌ๋ ‰์…˜ ์กฐํšŒ ์ตœ์ ํ™”

toOne(OneToOne, ManyToOne)๊ด€๊ณ„์— ์ด์–ด์„œ ์ปฌ๋ ‰์…˜์ธ ์ผ๋Œ€๋‹ค ๊ด€๊ณ„(OneToMany)๋ฅผ ์ตœ์ ํ™”ํ•ด๋ณด์ž.

toOne ๊ด€๊ณ„์™€ ๋™์ผํ•œ ๋ถ€๋ถ„๋“ค์ด ํฌํ•จ๋˜์–ด ์žˆ๋‹ค.

.

์—”ํ‹ฐํ‹ฐ๋ฅผ ์ง์ ‘ ๋…ธ์ถœ

  • ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋ณ€ํ•˜๋ฉด API ์ŠคํŽ™์ด ๋ณ€ํ•จ

  • ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ ์ง€์—ฐ ๋กœ๋”ฉ(LAZY) ๊ฐ•์ œ ์ดˆ๊ธฐํ™” ํ•„์š”

  • ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„ ๋ฌธ์ œ (@JsonIgnore ์„ค์ •์œผ๋กœ ํ•ด๊ฒฐ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ์ง€์—ฐ๋กœ๋”ฉ ๊ฐ์ฒด๋ฅผ ์ฝ์„ ์ˆ˜ ์—†๋Š” ๋ฌธ์ œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Œ)

// OrderApiController.java

@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 ๊ฐ•์ œ ์ดˆ๊ธฐํ™˜
        List<OrderItem> orderItems = order.getOrderItems();
        orderItems.stream().forEach(o -> o.getItem().getName()); // Lazy ๊ฐ•์ œ ์ดˆ๊ธฐํ™”
    }

    return all;
}

์—”ํ‹ฐํ‹ฐ๋ฅผ DTO๋กœ ๋ณ€ํ™˜

  • ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ ์ง€์—ฐ ๋กœ๋”ฉ ํ•„์š” (์ง€์—ฐ ๋กœ๋”ฉ์œผ๋กœ ๋„ˆ๋ฌด ๋งŽ์€ SQL ์‹คํ–‰)

  • ์ง€์—ฐ ๋กœ๋”ฉ์€ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ์žˆ๋Š” ์—”ํ‹ฐํ‹ฐ ์‚ฌ์šฉ์„ ์‹œ๋„ํ•˜๊ณ  ์—†์œผ๋ฉด SQL์„ ์‹คํ–‰

  • ex) Order ์กฐํšŒ ์‹œ Member - N๋ฒˆ, Address - N ๋ฒˆ, OrderItem - N ๋ฒˆ, item M๋ฒˆ

    • N : order ์กฐํšŒ ์ˆ˜, M : orderItem ์กฐํšŒ ์ˆ˜

    • ์ด 1 + N + N + N + M ๊ฐœ์˜ ์ฟผ๋ฆฌ ๋ฐœ์ƒ

@GetMapping("/api/orders")
public List<OrderDto> ordersV2() {
    List<Order> orders = orderRepository.findAllByString(new OrderSearch());
    List<OrderDto> result = orders.stream()
            .map(OrderDto::new)
            .collect(toList());
    return result;
}

@Data
static class OrderDto {
    private Long orderId;
    private String name;
    private LocalDateTime orderDate;
    private OrderStatus orderStatus;
    private Address address;
    private List<OrderItemDto> orderItems;

    public OrderDto(Order order) {
        this.orderId = order.getId();
        this.name = order.getMember().getName();
        this.orderDate = order.getOrderDate();
        this.orderStatus = order.getStatus();
        this.address = order.getDelivery().getAddress();
        this.orderItems = order.getOrderItems().stream() // ์—”ํ‹ฐํ‹ฐ ํ•„๋“œ ๋˜ํ•œ DTO๋กœ ๋ณ€ํ™˜
                .map(OrderItemDto::new)
                .collect(toList());
    }
}

@Data
static class OrderItemDto {
    private String itemName;
    private int orderPrice;
    private int count;

    public OrderItemDto(OrderItem orderItem) {
        this.itemName = orderItem.getItem().getName();
        this.orderPrice = orderItem.getOrderPrice();
        this.count = orderItem.getCount();
    }
}

ํŽ˜์น˜ ์กฐ์ธ ์ตœ์ ํ™”

  • ํŽ˜์น˜ ์กฐ์ธ์œผ๋กœ SQL 1๋ฒˆ๋งŒ ์‹คํ–‰

  • ๋‹จ, ์ปฌ๋ ‰์…˜ ํŽ˜์น˜ ์กฐ์ธ ์‚ฌ์šฉ ์‹œ ํŽ˜์ด์ง•์ด ๋ถˆ๊ฐ€๋Šฅํ•œ ๋‹จ์ ์ด ์กด์žฌ

    • ํ•˜์ด๋ฒ„๋„ค์ดํŠธ๋Š” ๊ฒฝ๊ณ  ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋ฉด์„œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ DB์—์„œ ์ฝ์–ด์˜ค๊ณ , ๋ฉ”๋ชจ๋ฆฌ์—์„œ ํŽ˜์ด์ง• ์ž‘์—… - OOM ๋ฐœ์ƒ ์œ„ํ—˜

  • ์ปฌ๋ ‰์…˜ ํŽ˜์น˜ ์กฐ์ธ์€ 1๊ฐœ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

    • ๋‘˜ ์ด์ƒ์˜ ์ปฌ๋ ‰์…˜์— ํŽ˜์น˜ ์กฐ์ธ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ๋ถ€์ •ํ•ฉํ•˜๊ฒŒ ์กฐํšŒ๋  ์ˆ˜ ์žˆ์Œ (1 * N * N..)

  • JPA distinct๋Š” SQL์— distinct ์ถ”๊ฐ€ ๋ฐ ๊ฐ™์€ ์—”ํ‹ฐํ‹ฐ(=id)๊ฐ€ ์กฐํšŒ๋˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ค‘๋ณต์„ ์ œ๊ฑฐ

    • 1:N ์กฐ์ธ์ด ์žˆ์œผ๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค row๊ฐ€ ๋ปฅํŠ€๊ธฐ๋˜์–ด distinct ํ•„์š”

// OrderApiController.java

@GetMapping("/api/orders")
public List<OrderDto> ordersV3() {
    List<Order> orders = orderRepository.findAllWithItem();
    List<OrderDto> result = orders.stream()
            .map(OrderDto::new)
            .collect(toList());

    return result;
}

// OrderRepository.java

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" +
                            " join fetch oi.item i", Order.class)
            .getResultList();
}

ํŽ˜์ด์ง•

์ปฌ๋ ‰์…˜ ํŽ˜์น˜ ์กฐ์ธ์—์„œ ํŽ˜์ด์ง•์ด ๋ถˆ๊ฐ€๋Šฅํ•œ ๋‹จ์  ์กด์žฌ

  • ์ผ๋Œ€๋‹ค(1:N) ์กฐ์ธ์ด ๋ฐœ์ƒํ•˜๋ฏ€๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ์ผ(1) ๊ธฐ์ค€์ด ์•„๋‹Œ ๋‹ค(N)๋ฅผ ๊ธฐ์ค€์œผ๋กœ row๊ฐ€ ์˜ˆ์ธกํ•  ์ˆ˜ ์—†์ด ์ฆ๊ฐ€

  • ์ผ(1)์ธ Order ๊ธฐ์ค€์œผ๋กœ ํŽ˜์ด์ง• ํ•˜๊ณ  ์‹ถ์ง€๋งŒ, ๋‹ค(N)์ธ OrderItem์„ ์กฐ์ธํ•˜๋ฉด OrderItem์ด ๊ธฐ์ค€์ด ๋˜์–ด๋ฒ„๋ฆฌ๋Š” ๋ฌธ์ œ

  • ์ด ๊ฒฝ์šฐ ํ•˜์ด๋ฒ„๋„ค์ดํŠธ๋Š” ๊ฒฝ๊ณ  ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๊ณ  ๋ชจ๋“  DB ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์€ ํ›„ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ํŽ˜์ด์ง•์„ ์‹œ๋„ (์ตœ์•…์˜ ๊ฒฝ์šฐ OOM ์žฅ์•  ๋ฐœ์ƒ)

ํŽ˜์ด์ง• + ์ปฌ๋ ‰์…˜ ์—”ํ‹ฐํ‹ฐ ์กฐํšŒ ๋ฌธ์ œ ํ•ด๊ฒฐ

  • ToOne(OneToOne, ManyToOne) ๊ด€๊ณ„๋Š” ๋ชจ๋‘ ํŽ˜์น˜ ์กฐ์ธ์œผ๋กœ

  • ์ปฌ๋ ‰์…˜์€ ์ง€์—ฐ ๋กœ๋”ฉ์œผ๋กœ ์กฐํšŒ

  • ์ง€์—ฐ ๋กœ๋”ฉ ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด hibernate.default_batch_fetch_size(๊ธ€๋กœ๋ฒŒ ์„ค์ •) ๋˜๋Š” @BatchSize(๊ฐœ๋ณ„ ์ตœ์ ํ™”) ์ ์šฉ

    • ์ปฌ๋ ‰์…˜์ด๋‚˜, ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ํ•œ๊บผ๋ฒˆ์— ์„ค์ •ํ•œ size ๋งŒํผ IN ์ฟผ๋ฆฌ ์กฐํšŒ

default_batch_fetch_size ์‚ฌ์ด์ฆˆ ์„ ํƒ

  • ์ ๋‹นํ•œ ์‚ฌ์ด์ฆˆ๋Š” 100~1000 ์‚ฌ์ด ๊ถŒ์žฅ

  • IN ์ ˆ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ 1000 ์œผ๋กœ ์ œํ•œํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์žˆ์Œ(max size = 1,000)

  • ์‚ฌ์ด์ฆˆ๋ฅผ ๋†’๊ฒŒ ์„ค์ •ํ•  ๊ฒฝ์šฐ ํ•œ๊บผ๋ฒˆ์— DB์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ๋ถˆ๋Ÿฌ์˜ค๋ฏ€๋กœ DB์— ์ˆœ๊ฐ„ ๋ถ€ํ•˜๊ฐ€ ์ฆ๊ฐ€ํ•  ์ˆ˜ ์žˆ์Œ.

  • ํ•˜์ง€๋งŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์‚ฌ์ด์ฆˆ๊ฐ€ ์–ด๋–ป๊ฒŒ ์„ค์ •์ด ๋˜์–ด์žˆ๋“  ๊ฒฐ๊ตญ ์ „์ฒด ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋”ฉํ•ด์•ผ ํ•˜๋ฏ€๋กœ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ๊ฐ™๋‹ค.

  • 1000 ์œผ๋กœ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด ์„ฑ๋Šฅ์ƒ ๊ฐ€์žฅ ์ข‹์ง€๋งŒ, ๊ฒฐ๊ตญ DB๋“  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด๋“  ์ˆœ๊ฐ„ ๋ถ€ํ•˜๋ฅผ ์–ด๋””๊นŒ์ง€ ๊ฒฌ๋”œ ์ˆ˜ ์žˆ๋Š”์ง€ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•ด๋ณด๋ฉฐ ๊ฒฐ์ •ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”

์žฅ์ 

  • ์ฟผ๋ฆฌ ํ˜ธ์ถœ ์ˆ˜๊ฐ€ 1+N ์—์„œ 1+1 ๋กœ ์ตœ์ ํ™”

  • IN ์ฟผ๋ฆฌ ์‚ฌ์šฉ์œผ๋กœ ์ผ๋ฐ˜ ์กฐ์ธ๋ณด๋‹ค DB ๋ฐ์ดํ„ฐ ์ „์†ก๋Ÿ‰ ์ตœ์ ํ™”

  • ํŽ˜์น˜ ์กฐ์ธ ๋ฐฉ์‹๊ณผ ๋น„๊ตํ•ด์„œ ์ฟผ๋ฆฌ ํ˜ธ์ถœ ์ˆ˜๊ฐ€ ์•ฝ๊ฐ„ ์ฆ๊ฐ€ํ•˜์ง€๋งŒ(IN ์ฟผ๋ฆฌ), DB ๋ฐ์ดํ„ฐ ์ „์†ก๋Ÿ‰ ๊ฐ์†Œ(์ค‘๋ณต ์ œ๊ฑฐ)

  • ์ปฌ๋ ‰์…˜ ํŽ˜์น˜ ์กฐ์ธ์—์„œ ํŽ˜์ด์ง•์ด ๋ถˆ๊ฐ€๋Šฅํ•œ ๋‹จ์ ์„ ํ•ด๊ฒฐ

// OrderApiController.java

@GetMapping("/api/orders")
public List<OrderDto> ordersV3_page(@RequestParam(value = "offset", defaultValue = "0") int offset,
                                    @RequestParam(value = "limit", defaultValue = "100") int limit) {
    List<Order> orders = orderRepository.findAllWithMemberDelivery(offset, limit);

    List<OrderDto> result = orders.stream()
            .map(o -> new OrderDto(o))
            .collect(toList());

    return result;
}

// OrderRepository.java

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();
}
// application.yml

spring:
  jpa:
    properties:
      hibernate:
        default_batch_fetch_size: 100

DTO ์ง์ ‘ ์กฐํšŒ

  • ToOne(N:1, 1:1) ๊ด€๊ณ„ ์กฐํšŒํ•œ ํ›„, ToMany(1:N) ๊ด€๊ณ„๋Š” ๋ณ„๋„ ์ฒ˜๋ฆฌ

    • ToOne ๊ด€๊ณ„๋Š” ์กฐ์ธ ์‹œ Row ์ˆ˜๊ฐ€ ์ฆ๊ฐ€ํ•˜์ง€ ์•Š์ง€๋งŒ, ToMany ๊ด€๊ณ„๋Š” ์กฐ์ธ ์‹œ Row ์ˆ˜๊ฐ€ ์ฆ๊ฐ€ํ•˜์—ฌ ์ตœ์ ํ™”๊ฐ€ ์–ด๋ ค์šฐ๋ฏ€๋กœ ๋ณ„๋„ ์กฐํšŒ

  • ๋ฃจํŠธ 1 ๋ฒˆ, ์ปฌ๋ ‰์…˜ N ๋ฒˆ, ์ด 1 + N ๋ฒˆ์˜ ์ฟผ๋ฆฌ ์‹คํ–‰

  • ์ฝ”๋“œ๊ฐ€ ๋‹จ์ˆœํ•˜๊ณ , ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์‰ฌ์šฐ๋ฉฐ, ๋‹จ๊ฑด ์กฐํšŒ์—์„œ๋Š” ์œ ์šฉํ•œ ๋ฐฉ๋ฒ•

commit log

public List<OrderQueryDto> findOrderQueryDtos() {
    // ๋ฃจํŠธ ์กฐํšŒ : ToOne ๊ด€๊ณ„๋ฅผ ํ•œ ๋ฒˆ์— ์กฐํšŒ (1 ๋ฒˆ์˜ ์ฟผ๋ฆฌ N ๊ฐœ์˜ Row)
    List<OrderQueryDto> result = findOrders();

    // ์ปฌ๋ ‰์…˜ ์กฐํšŒ : ์ปฌ๋ ‰์…˜์€ ๋ฃจํ”„๋ฅผ ๋Œ๋ฉด์„œ ๋ณ„๋„๋กœ ์กฐํšŒ (N ๋ฒˆ์˜ ์ฟผ๋ฆฌ)
    result.forEach(o -> {
        List<OrderItemQueryDto> orderItems = findOrderItems(o.getOrderId());
        o.setOrderItems(orderItems);
    });

    return result;
}

private List<OrderQueryDto> findOrders() {
    return em.createQuery(
                    "select new jpabook.jpashop.repository.order.query.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 jpabook.jpashop.repository.order.query.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();
}

์ปฌ๋ ‰์…˜ ์กฐํšŒ ์ตœ์ ํ™”

IN ์ ˆ์„ ํ™œ์šฉํ•ด์„œ ๋ฉ”๋ชจ๋ฆฌ์— ๋ฏธ๋ฆฌ ์กฐํšŒ ํ›„ ์ตœ์ ํ™”

  • ๋ฃจํŠธ 1 ๋ฒˆ, ์ปฌ๋ ‰์…˜ 1 ๋ฒˆ ์กฐํšŒ

    • Map์„ ์‚ฌ์šฉํ•˜์—ฌ ๋งค์นญ ์„ฑ๋Šฅ ๊ฐœ์„  - O(1)

  • ToOne ๊ด€๊ณ„๋ฅผ ๋จผ์ € ์กฐํšŒํ•œ ํ›„, ์–ป์€ ์‹๋ณ„์ž Id๋กœ ToMany ๊ด€๊ณ„๋ฅผ ํ•œ๊บผ๋ฒˆ์— ์กฐํšŒ

  • ์œ„ ๋ฐฉ๋ฒ•๊ณผ ๋น„๊ตํ•˜๋ฉด

    • ๋ฐœ์ƒํ•˜๋Š” N + 1 ๋ฌธ์ œ๋ฅผ 1 + 1 ๋กœ ํ•ด๊ฒฐ

    • ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์ง€๊ธด ํ•˜์ง€๋งŒ, ๋‹ค์ˆ˜์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ์— ์กฐํšŒ ํ•  ๊ฒฝ์šฐ ํ™˜๊ฒฝ์— ๋”ฐ๋ผ 100๋ฐฐ ์ด์ƒ ์„ฑ๋Šฅ ์ตœ์ ํ™” ๊ฐ€๋Šฅ

    • ๋ณดํ†ต ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

public List<OrderQueryDto> findAllByDto_optimization() {
    // ๋ฃจํŠธ ์กฐํšŒ : ToOne ๊ด€๊ณ„๋ฅผ ํ•œ ๋ฒˆ์— ์กฐํšŒ (1 ๋ฒˆ์˜ ์ฟผ๋ฆฌ)
    List<OrderQueryDto> result = findOrders();

    // ์ปฌ๋ ‰์…˜ ์กฐํšŒ : IN ์ ˆ์„ ํ™œ์šฉํ•˜์—ฌ ํ•œ ๋ฒˆ์— ์กฐํšŒ (1๋ฒˆ์˜ ์ฟผ๋ฆฌ)
    Map<Long, List<OrderItemQueryDto>> orderItemMap = findOrderItemMap(toOrderIds(result));

    // ๋ฃจํ”„๋ฅผ ๋Œ๋ฉด์„œ ์ปฌ๋ ‰์…˜ ์„ธํŒ…
    result.forEach(o -> o.setOrderItems(orderItemMap.get(o.getOrderId())));

    return result;
}

private List<Long> toOrderIds(List<OrderQueryDto> result) {
    return result.stream()
            .map(o -> o.getOrderId())
            .collect(Collectors.toList());
}

private Map<Long, List<OrderItemQueryDto>> findOrderItemMap(List<Long> orderIds) {
    List<OrderItemQueryDto> orderItems = em.createQuery(
                    "select new jpabook.jpashop.repository.order.query.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()
            .collect(Collectors.groupingBy(OrderItemQueryDto::getOrderId));
}

ํ”Œ๋žซ ๋ฐ์ดํ„ฐ ์ตœ์ ํ™”

JOIN ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋Œ€๋กœ ์กฐํšŒํ•œ ํ›„, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์›ํ•˜๋Š” ์ŠคํŒฉ์œผ๋กœ ์ง์ ‘ ๋ณ€ํ™˜

  • ์ฟผ๋ฆฌ๋ฅผ ํ•œ ๋ฒˆ ์‹คํ–‰ํ•˜๋Š” ์žฅ์ ์ด ์žˆ์ง€๋งŒ,

  • ์กฐ์ธ์œผ๋กœ ์ƒ๊ธฐ๋Š” ์ค‘๋ณต ๋ฐ์ดํ„ฐ๊ฐ€ DB์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ์ „๋‹ฌ๋˜์–ด ์ƒํ™ฉ์— ๋”ฐ๋ผ ์œ„ ๋ฐฉ๋ฒ•๋ณด๋‹ค ๋Š๋ฆด ์ˆ˜ ์žˆ์Œ

  • ๋ฐ˜ํ™˜ Dto ์ŠคํŽ™์œผ๋กœ ๋ณ€ํ™˜์„ ์œ„ํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ถ”๊ฐ€ ์ž‘์—…(๋ณ€ํ™˜ ๋กœ์ง)์ด ํ•„์š”

  • ํŽ˜์ด์ง• ๋ถˆ๊ฐ€๋Šฅ

  • ์œ„ ๋ฐฉ๋ฒ•๊ณผ ๋น„๊ตํ•˜๋ฉด

    • ์ฟผ๋ฆฌ ์‹คํ–‰์ด ํ•œ ๋ฒˆ์œผ๋กœ ์ตœ์ ํ™”๊ฐ€ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ํŽ˜์ด์ง•์ด ๋ถˆ๊ฐ€๋Šฅ

    • ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ์•„์ง€๋ฉด ์ค‘๋ณต ์ „์†ก์ด ์ฆ๊ฐ€ํ•˜์—ฌ ์œ„ ๋ฐฉ๋ฒ•๊ณผ ์„ฑ๋Šฅ ์ฐจ์ด๋„ ๋ฏธ๋น„

// OrderApiController.java

@GetMapping("/api/orders")
public List<OrderQueryDto> ordersV6() {
    List<OrderFlatDto> flats = orderQueryRepository.findAllByDto_flat();

    return flats.stream()
            .collect(groupingBy(o -> new OrderQueryDto(o.getOrderId(), o.getName(), o.getOrderDate(), o.getOrderStatus(), o.getAddress()),
                    mapping(o -> new OrderItemQueryDto(o.getOrderId(), o.getItemName(), o.getOrderPrice(), o.getCount()), toList())
            )).entrySet().stream()
            .map(e -> new OrderQueryDto(e.getKey().getOrderId(),
                    e.getKey().getName(), e.getKey().getOrderDate(), e.getKey().getOrderStatus(),
                    e.getKey().getAddress(), e.getValue()))
            .collect(toList());
}

// OrderQueryRepository.java

public List<OrderFlatDto> findAllByDto_flat() {
    return em.createQuery(
                    "select new jpabook.jpashop.repository.order.query.OrderFlatDto(o.id, m.name, o.orderDate, o.status, d.address, i.name, oi.orderPrice, oi.count)" +
                            " from Order o" +
                            " join o.member m" +
                            " join o.delivery d" +
                            " join o.orderItems oi" +
                            " join oi.item i", OrderFlatDto.class)
            .getResultList();
}

์กฐํšŒ ์ฟผ๋ฆฌ ๊ถŒ์žฅ ์ˆœ์„œ

โญ๏ธ ์กฐํšŒ ์ฟผ๋ฆฌ ๋ฐฉ์‹ ์„ ํƒ ๊ถŒ์žฅ ์ˆœ์„œ โญ๏ธ

  1. ์—”ํ‹ฐํ‹ฐ๋ฅผ DTO๋กœ ๋ณ€ํ™˜

  2. ํ•„์š” ์‹œ ํŽ˜์น˜ ์กฐ์ธ์œผ๋กœ ์„ฑ๋Šฅ ์ตœ์ ํ™” (๋Œ€๋ถ€๋ถ„์˜ ์„ฑ๋Šฅ ์ด์Šˆ๊ฐ€ ํ•ด๊ฒฐ)

  3. ๊ทธ๋ž˜๋„ ์•ˆ๋˜๋ฉด DTO๋กœ ์ง์ ‘ ์กฐํšŒ

  4. ์ตœํ›„์˜ ๋ฐฉ๋ฒ•์€ JPA๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋„ค์ดํ‹ฐ๋ธŒ SQL ํ˜น์€ Spring JDBC Template์„ ์‚ฌ์šฉํ•ด์„œ SQL์„ ์ง์ ‘ ์‚ฌ์šฉ

.

โญ๏ธ ์ปฌ๋ ‰์…˜ ์กฐํšŒ ์ฟผ๋ฆฌ ๋ฐฉ์‹ ์„ ํƒ ๊ถŒ์žฅ ์ˆœ์„œ โญ๏ธ

  1. ์—”ํ‹ฐํ‹ฐ ์กฐํšŒ ๋ฐฉ์‹์œผ๋กœ ์ ‘๊ทผ

    • ํŽ˜์ด์ง• ํ•„์š” ์—†์„ ์‹œ ํŽ˜์น˜์กฐ์ธ์œผ๋กœ ์ฟผ๋ฆฌ ์ˆ˜ ์ตœ์ ํ™”

    • ํŽ˜์ด์ง• ํ•„์š” ์‹œ hibernate.default_batch_fetch_size, @BatchSize ๋กœ ์ปฌ๋ ‰์…˜ ์ตœ์ ํ™”

  2. ์—”ํ‹ฐํ‹ฐ ์กฐํšŒ ๋ฐฉ์‹์œผ๋กœ ํ•ด๊ฒฐ์ด ์•ˆ๋˜๋ฉด DTO ์กฐํšŒ ๋ฐฉ์‹ ์‚ฌ์šฉ

  3. DTO ์กฐํšŒ ๋ฐฉ์‹์œผ๋กœ ํ•ด๊ฒฐ์ด ์•ˆ๋˜๋ฉด NativeSQL or Spring JdbcTemplate ์‚ฌ์šฉ

.

์—”ํ‹ฐํ‹ฐ ์กฐํšŒ ๋ฐฉ์‹๊ณผ DTO ์กฐํšŒ ๋ฐฉ์‹

  • ์—”ํ‹ฐํ‹ฐ ์กฐํšŒ ๋ฐฉ์‹์€ fetch join, default_batch_fetch_size, @BatchSize ๋“ฑ์œผ๋กœ ์ฝ”๋“œ๋ฅผ ๊ฑฐ์˜ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ , ์˜ต์…˜ ์„ค์ •๋งŒ์œผ๋กœ ๋‹ค์–‘ํ•œ ์„ฑ๋Šฅ ์ตœ์ ํ™” ์‹œ๋„๊ฐ€ ๊ฐ€๋Šฅ

  • ๋ฐ˜๋ฉด, DTO ์กฐํšŒ ๋ฐฉ์‹์€ ๋งŽ์€ ์ฝ”๋“œ ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•˜๋ฏ€๋กœ ๋‘˜ ์‚ฌ์ด์— ์ค„ํƒ€๊ธฐ๊ฐ€ ํ•„์š”

OSIV์™€ ์„ฑ๋Šฅ ์ตœ์ ํ™”

  • OSIV(Open Session In View): hibernate

    • JPA ์—์„œ๋Š” Open EntityManager In View

  • ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ปค๋„ฅ์…˜์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๋ฒ”์œ„

OSIV ON

spring.jpa.open-in-view: true (default)

  • OSIV๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, (View Template์ด๋‚˜ API ์ปจํŠธ๋กค๋Ÿฌ์—์„œ์˜) ์ง€์—ฐ๋กœ๋”ฉ์„ ์œ„ํ•ด View rendering | API response ์™„๋ฃŒ ์‹œ์ ๊นŒ์ง€ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ปค๋„ฅ์…˜์„ ์œ ์ง€ํ•ด์•ผ ํ•œ๋‹ค.

    • Service Layer์—์„œ ํŠธ๋žœ์ ์…˜์ด ๋๋‚˜๋”๋ผ๋„ rendering | API response ์™„๋ฃŒ ํ›„์—์•ผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ปค๋„ฅ์…˜์„ ๋ฐ˜ํ™˜ํ•˜๊ณ , ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ๋‹ซ๊ฒŒ ๋œ๋‹ค.

  • ์ง€์—ฐ ๋กœ๋”ฉ์€ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ์‚ด์•„์žˆ์–ด์•ผ ํ•˜๋ฏ€๋กœ, ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ปค๋„ฅ์…˜์„ ์œ ์ง€ํ•˜๋Š” ์žฅ์ 

  • ํ•˜์ง€๋งŒ! ๋„ˆ๋ฌด ์˜ค๋žœ์‹œ๊ฐ„๋™์•ˆ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ปค๋„ฅ์…˜ ๋ฆฌ์†Œ์Šค๋ฅผ ์œ ์ง€ํ•˜๋ฏ€๋กœ, ์‹ค์‹œ๊ฐ„ ํŠธ๋ž˜ํ”ฝ์ด ์ค‘์š”ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ์ปค๋„ฅ์…˜์ด ๋ถ€์กฑํ•˜์—ฌ ์žฅ์• ๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค.

    • ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์™ธ๋ถ€ API๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์™ธ๋ถ€ API ์‘๋‹ต ๋Œ€๊ธฐ ์‹œ๊ฐ„ ๋งŒํผ ์ปค๋„ฅ์…˜ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ๋ชปํ•˜๊ฒŒ ๋˜๋Š” ๋‹จ์ 

OSIV OFF

spring.jpa.open-in-view: false

  • OSIV๋ฅผ ๋„๋ฉด ํŠธ๋žœ์žญ์…˜์„ ์ข…๋ฃŒํ•  ๋•Œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ปค๋„ฅ์…˜์„ ๋ฐ˜ํ™˜ํ•˜๊ณ , ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ๋‹ซ์•„ ์ปค๋„ฅ์…˜ ๋ฆฌ์†Œ์Šค๋ฅผ ๋‚ญ๋น„ํ•˜์ง€ ์•Š์Œ

  • ๋‹จ, ๋ชจ๋“  ์ง€์—ฐ๋กœ๋”ฉ์„ ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜์—ฌ, ๊ธฐ์กด ์ง€์—ฐ ๋กœ๋”ฉ ์ฝ”๋“œ๋ฅผ ํŠธ๋žœ์žญ์…˜ ์•ˆ์œผ๋กœ ๋„ฃ๊ฑฐ๋‚˜ fetch join์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๋‹จ์ ์ด ์กด์žฌ

    • view template์—์„œ ์ง€์—ฐ๋กœ๋”ฉ์ด ๋™์ž‘ํ•˜์ง€ ์•Š์Œ.

    • ํŠธ๋žœ์žญ์…˜์ด ๋๋‚˜๊ธฐ ์ „, ์ง€์—ฐ ๋กœ๋”ฉ ๊ฐ•์ œ ํ˜ธ์ถœ ํŒ”์š”

CQS

CQS(Commandโ€“query separation)

  • ์ปค๋ฉ˜๋“œ์™€ ์ฟผ๋ฆฌ ๋ถ„๋ฆฌํ•˜์—ฌ OSIV๋ฅผ ๋ˆ ์ƒํƒœ๋กœ ๋ณต์žก์„ฑ์„ ๊ด€๋ฆฌ

  • ๋ณดํ†ต ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง(๋“ฑ๋ก/์ˆ˜์ •)์—์„œ๋Š” ์„ฑ๋Šฅ์ด ํฌ๊ฒŒ ๋ฌธ์ œ ์—†์ง€๋งŒ, ๋ณต์žกํ•œ ํ™”๋ฉด์„ ์ถœ๋ ฅํ•˜๊ธฐ ์œ„ํ•œ ์ฟผ๋ฆฌ๋Š” ์„ฑ๋Šฅ์„ ์ตœ์ ํ™” ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”

    • ํฌ๊ณ  ๋ณต์žกํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•œ๋‹ค๋ฉด, ์ด ๋‘˜์˜ ๊ด€์‹ฌ์‚ฌ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ๋ถ„๋ฆฌํ•˜์—ฌ ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์‰ฝ๊ฒŒ ๋งŒ๋“ค์ž.

      • OrderService: ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง

      • OrderQueryService: ํ™”๋ฉด์ด๋‚˜ API์— ๋งž์ถ˜ ์„œ๋น„์Šค (์ฃผ๋กœ ์กฐํšŒ ์ „์šฉ ํŠธ๋žœ์žญ์…˜)

    • ๋ณดํ†ต ์„œ๋น„์Šค ๊ณ„์ธต์—์„œ ํŠธ๋žœ์žญ์…˜์„ ์œ ์ง€ํ•˜๋ฏ€๋กœ, ๋‘ ์„œ๋น„์Šค ๋ชจ๋‘ ํŠธ๋žœ์žญ์…˜์„ ์œ ์ง€ํ•˜๋ฉด์„œ ์ง€์—ฐ ๋กœ๋”ฉ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

์ถ”์ฒœ: ๊ณ ๊ฐ ์„œ๋น„์Šค์˜ ์‹ค์‹œ๊ฐ„ API๋Š” OSIV OFF, ADMIN ๊ณผ ๊ฐ™์ด ์ปค๋„ฅ์…˜์„ ๋งŽ์ด ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ณณ์—์„œ๋Š” OSIV ON

์†Œ๊ฐœ

Spring Data JPA

  • Spring Data JPA๋Š” JPA ์‚ฌ์šฉ ์‹œ ๋ฐ˜๋ณต๋˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž๋™ํ™”

    • org.springframework.boot:spring-boot-starter-data-jpa

  • JpaRepository ์ธํ„ฐํŽ˜์ด์Šค์—์„œ ๊ธฐ๋ณธ์ ์ธ CRUD ๊ธฐ๋Šฅ์„ ๋ชจ๋‘ ์ œ๊ณต

  • ์ผ๋ฐ˜ํ™”ํ•˜๊ธฐ ์–ด๋ ค์šด ๊ธฐ๋Šฅ๋„ ๋ฉ”์„œ๋“œ ์ด๋ฆ„์œผ๋กœ ์ •ํ™•ํ•œ JPQL ์ฟผ๋ฆฌ ์‹คํ–‰ ๊ฐ€๋Šฅ

  • ๊ฐœ๋ฐœ์ž๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋งŒ ๋งŒ๋“ค๋ฉด ๊ตฌํ˜„์ฒด๋Š” Spring Data JPA๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰์‹œ์ ์— ์ฃผ์ž…

commit

QueryDSL

  • QueryDSL ๋กœ ์กฐ๊ฑด์— ๋”ฐ๋ผ ์‹คํ–‰๋˜๋Š” ๋™์  ์ฟผ๋ฆฌ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

  • ์ž๋ฐ” ์ฝ”๋“œ๋กœ ๋™์  ์ฟผ๋ฆฌ๋ฅผ SQL(JPQL)๊ณผ ์œ ์‚ฌํ•˜๊ฒŒ ์ƒ์„ฑ (JPQL์„ ์ฝ”๋“œ๋กœ ๋งŒ๋“œ๋Š” ๋นŒ๋” ์—ญํ• )

  • QueryDSL ์žฅ์ 

    • ์ง๊ด€์ ์ธ ๋ฌธ๋ฒ•

    • ์ปดํŒŒ์ผ ์‹œ์ ์— ๋น ๋ฅธ ๋ฌธ๋ฒ• ์˜ค๋ฅ˜ ๋ฐœ๊ฒฌ

    • ์ฝ”๋“œ ์ž๋™์™„์„ฑ

    • ์ฝ”๋“œ ์žฌ์‚ฌ์šฉ

    • JPQL new ๋ช…๋ น์–ด์™€ ๋ฐ˜๋Œ€๋กœ ์‹ฌํ”Œํ•œ DTO ์กฐํšŒ ์ง€์›

Querydsl์€ JPA๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœ ํ•  ๋•Œ ์„ ํƒ์ด ์•„๋‹Œ ํ•„์ˆ˜!

commit

Last updated