SPRING/INFLEARN

[๊ฐ•์˜] ์‹ค์ „! ์Šคํ”„๋ง ๋ถ€ํŠธ์™€ JPA ํ™œ์šฉ2 - API ๊ฐœ๋ฐœ๊ณผ ์„ฑ๋Šฅ ์ตœ์ ํ™” 1

ozllzL 2022. 7. 25. 18:24

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-JPA-API%EA%B0%9C%EB%B0%9C-%EC%84%B1%EB%8A%A5%EC%B5%9C%EC%A0%81%ED%99%94/dashboard

 

์‹ค์ „! ์Šคํ”„๋ง ๋ถ€ํŠธ์™€ JPA ํ™œ์šฉ2 - API ๊ฐœ๋ฐœ๊ณผ ์„ฑ๋Šฅ ์ตœ์ ํ™” - ์ธํ”„๋Ÿฐ | ๊ฐ•์˜

์Šคํ”„๋ง ๋ถ€ํŠธ์™€ JPA๋ฅผ ํ™œ์šฉํ•ด์„œ API๋ฅผ ๊ฐœ๋ฐœํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  JPA ๊ทนํ•œ์˜ ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ฐฉ๋ฒ•์„ ํ•™์Šตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค., - ๊ฐ•์˜ ์†Œ๊ฐœ | ์ธํ”„๋Ÿฐ...

www.inflearn.com

1. API ๊ฐœ๋ฐœ ๊ธฐ๋ณธ

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

 

version 1

//MemberApiController.java
@RestController
@RequiredArgsConstructor
public class MemberApiController {

    private final MemberService memberService;

    @PostMapping("/api/v1/members")
    public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member){
        //Json์œผ๋กœ ์˜จ ๋ฐ์ดํ„ฐ๋ฅผ member๋กœ ์‹น ๋ฐ”๊ฟ”์ฃผ๋Š” @RequestBody
        //์œ ํšจ์„ฑ ๊ฒ€์‚ฌ @Valid
        Long id = memberService.join(member);
        return new CreateMemberResponse(id);

    }

    @Data
    static class CreateMemberResponse {
        private Long id;

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

์ž˜ ๋จ

=> ๊ฐ’์ด ์—†์–ด๋„ ์ €์žฅ์ด ๋œ๋‹ค. 

๋ฐฉ์ง€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด?

Member.java์˜ name์— @NotEmpty๋ฅผ ๋ถ™์—ฌ์ค€๋‹ค.

@Valid ๋ถ™์ธ ํšจ๊ณผ๋กœ name ์ด ๋น„์–ด์žˆ์œผ๋ฉด ๊ฑธ๋Ÿฌ์คŒ!

 

=> ์œ„ ๋ฐฉ๋ฒ•์€ ๋ฌธ์ œ๊ฐ€ ๋งŽ๋‹ค.

presentation(ํ™”๋ฉด)์„ ์œ„ํ•œ ๊ฒ€์ฆ ๋กœ์ง์ด Entity ์ฝ”๋“œ์— ๋“ค์–ด์™€ ์žˆ์Œ.

1. ์–ด๋–ค API๋Š” @NotEmpty๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์Œ

2. Entity ์˜ name -> userName์œผ๋กœ ๋ฐ”๊พผ ๊ฒฝ์šฐ, Entity์˜ ์ŠคํŽ™์ด ๋ณ€๊ฒฝ๋จ

    ํด๋ผ์ธก์—์„œ๋Š” ๊ฐ‘์ž๊ธฐ API๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š์Œ

Entity๋ฅผ ์†๋Œ€์„œ ์ŠคํŽ™ ์ž์ฒด๊ฐ€ ๋ณ€๊ฒฝ๋œ๋‹ค๋Š” ๊ฒƒ์ด ๋ฌธ์ œ์ž„

-> ์ด๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” DTO๋ฅผ ๋งŒ๋“ค์–ด์„œ API ์š”์ฒญ ์ŠคํŽ™์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋„๋ก ํ•ด์•ผ ํ•จ

     ์—”ํ‹ฐํ‹ฐ ์™ธ๋ถ€ ๋…ธ์ถœ ์•ˆ๋จ. ์—”ํ‹ฐํ‹ฐ๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์ง€ ๋งˆ

 

version2

@RestController
@RequiredArgsConstructor
public class MemberApiController {

    private final MemberService memberService;

	/*์ƒ๋žต*/

    @PostMapping("/api/v21/members")
    public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request){
        Member member = new Member();
        member.setName(request.getName());
        Long id = memberService.join(member);
        return new CreateMemberResponse(id);
    }

    @Data
    private class CreateMemberRequest {
        private String name;
    }
}

version2์˜ ์žฅ์ 

ํด๋ž˜์Šค๋ฅผ ํ•˜๋‚˜ ๋” ๋งŒ๋“ค์–ด์•ผํ•˜๋Š” ๋‹จ์ ์ด ์žˆ๊ธด ํ•˜์ง€๋งŒ,

1. ์ด๋ฆ„์„ ๋ณ€๊ฒฝํ•˜๋ฉด ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ๋‚˜์คŒ(setter)

2. DTO๋Š” ์–ด๋–ค ๊ฐ’์„ ์–ด๋–ป๊ฒŒ ๋ฐ›๋Š”์ง€ ํ•œ ๋ˆˆ์— ๋ณด์ด๊ฒŒ ํ•ด์คŒ, ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ๋งค์šฐ ํŽธ๋ฆฌํ•จ

-> ์—ฌํŠผ ๋ฌด์กฐ๊ฑด 2 ์จ์•ผํ•จ. DTO ์•ˆ๋งŒ๋“ค๋ฉด ์•ˆ๋จ

 

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

@RestController
@RequiredArgsConstructor
public class MemberApiController {

    /*์ƒ๋žต*/

    //Update๋Š” ๋ณ€๊ฒฝ์„ฑ์ด๋‹ค
    //Member๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด, ์˜์†์„ฑ์ด ๋Š๊ธด member๊ฐ€ ๋ฐ˜ํ™˜์ด ๋œ๋‹ค.
    //id๋กœ ๋‹ค์‹œ ์กฐํšŒํ•ด์„œ ์ฐพ์•„์˜ค๋Š” ๋А๋‚Œ
    //์ปค๋งจ๋“œ์™€ ์ฟผ๋ฆฌ๊ฐ€ ๊ฐ™์ด ์žˆ๋Š” ๊ผด์ด ๋จ
    //๊ทธ๋ƒฅ ์•„์˜ˆ ์กฐํšŒ๋ฅผ ๋‹ค์‹œ ํ•ด์„œ member๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜, id๋งŒ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ผด์ด ๋‚˜์Œ
    //์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ์ฆ๋Œ€๋จ
    @PutMapping("/api/v2/members/{id}")
    public UpdateMemberResponse updateMemberV2(@PathVariable("id") Long id,
                                               @RequestBody @Valid UpdateMemberRequest request){
        // ์ƒˆ ๋ฉ”์†Œ๋“œ ์ž‘์„ฑํ•  ๋•Œ๋Š” ๋ณ€๊ฒฝ๊ฐ์ง€๊ฐ€ ์ข‹๋‹ค
        memberService.update(id, request.getName());
        Member findMember = memberService.findOne(id);
        return new UpdateMemberResponse(findMember.getId(), findMember.getName());
    }


    //TIP)) ๊ฐ•์‚ฌ๋‹˜๊ป˜์„œ๋Š” ์—”ํ‹ฐํ‹ฐ์—๋Š” Lombok ์‚ฌ์šฉ์„ ์ž์ œํ•˜๋Š” ํŽธ์ด์‹œ์ง€๋งŒ
    //      DTO์—๋Š” ๋ง‰ ์“ฐ์‹œ๋Š” ํŽธ์ด๋‹ค๋‹ค
    @Data
    static class UpdateMemberRequest {
        @NotEmpty
        private String name;
    }

    @Data
    @AllArgsConstructor
    static class UpdateMemberResponse {
        private Long id;
        private String name;
    }
}
//MemberService.java
@Service
@Transactional(readOnly = true)//์ฝ๊ธฐ
@RequiredArgsConstructor
public class MemberService {

    /*์ƒ๋žต*/
    
    //Transactional์— ์˜ํ•ด
    //Spring AOP๊ฐ€ ๋๋‚˜๋Š” ์‹œ์ ์— commit์ด ๋˜๊ณ 
    //๊ทธ ๋•Œ JPA๋Š” flushํ•˜๊ณ  ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ ์ปค๋ฐ‹๋„ ๋‹ค ํ•ด๋ฒ„๋ฆผ
    @Transactional
    public void update(Long id, String name) {
        Member member = memberRepository.findOne(id);
        member.setName(name);
    }

}

๊ตฟ ์ž˜๋จ

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

๋ฌด์‹ํ•œ version1

@RestController
@RequiredArgsConstructor
public class MemberApiController {

    /*์ƒ๋žต*/

    @GetMapping("/api/v1/members")
    public List<Member> membersV1(){
        return memberService.findMembers();
    }
    
    /*์ƒ๋žต*/
}

์ด๋ ‡๊ฒŒ ๋˜๋ฉด ํ•„์š” ์—†๋Š” ์ฃผ๋ฌธ ์ •๋ณด๊นŒ์ง€ ๊ฐ™์ด ๋ฐ›์•„์ง

์ด๊ฑธ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ orders์— @JsonIgnore์„ ๋ถ™์ด๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Œ

-> ํ•˜์ง€๋งŒ ๋‹ค๋ฅธ ๋ฐ์— ํ•„์š”ํ•œ ๊ฒฝ์šฐ๋„ ์žˆ๊ณ  

    presentation ๊ณ„์ธต์„ ์œ„ํ•œ ๋กœ์ง์ด Entity์— ์ถ”๊ฐ€๋จ (์–‘๋ฐฉํ–ฅ ์˜์กด ๊ด€๊ณ„)

   ๋˜ ๋ณ€์ˆ˜ ์ด๋ฆ„ ๋ฐ”๊พธ๋ฉด(์—”ํ‹ฐํ‹ฐ ์ŠคํŽ™์ด ๋ณ€๊ฒฝ๋˜๋ฉด) response์˜ ๋ณ€์ˆ˜ ์ด๋ฆ„์ด ๋ฐ”๋€Œ์–ด์„œ API ์ŠคํŽ™์ด ๋ฐ”๋€œ

=> ์—ญ์‹œ ๊ทธ๋Œ€๋กœ Entity๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๋ฉด ์•ˆ๋จ, Array๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ๋„ ๋ฌธ์ œ๊ฐ€ ๋จ(๋‹ค๋ฅธ ๊ฒƒ๊ณผ ํ•จ๊ป˜ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์—†์Œ)

 

@RestController
@RequiredArgsConstructor
public class MemberApiController {

    /*์ƒ๋žต*/

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

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

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

    /*์ƒ๋žต*/
}

1. ๊ฒ‰์ด ๋ฐฐ์—ด([])์ด ์•„๋‹Œ ์˜ค๋ธŒ์ ํŠธ ({}) ํ˜•ํƒœ์ž„

    ์œ ์—ฐ์„ฑ์ด ์ƒ๊ธด๋‹ค, ํ•„์š”ํ•œ ํ•„๋“œ๊ฐ€ ์žˆ๋‹ค๋ฉด ์ถ”๊ฐ€ํ•ด์„œ ๋ฆฌํ„ดํ•˜๊ธฐ ํŽธํ•จ

2. ํ•„์š”ํ•œ ์ •๋ณด๋งŒ ๋‹ด๊ฒจ ์žˆ์Œ

 

2. API ๊ฐœ๋ฐœ ๊ณ ๊ธ‰ - ์ค€๋น„

- ์กฐํšŒ์šฉ ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ์ž…๋ ฅ

์•„๋ž˜ ์ฝ”๋“œ๋กœ ๋ฐ์ดํ„ฐ ์ž…๋ ฅ

@Component
@RequiredArgsConstructor
public class InitDb {
    private final InitService initService;
    @PostConstruct
    public void init() {
        initService.dbInit1();
        initService.dbInit2();
    }
    @Component
    @Transactional
    @RequiredArgsConstructor
    static class InitService {
        private final EntityManager em;
        public void dbInit1() {
            Member member = createMember("userA", "์„œ์šธ", "1", "1111");
            em.persist(member);
            Book book1 = createBook("JPA1 BOOK", 10000, 100);
            em.persist(book1);
            Book book2 = createBook("JPA2 BOOK", 20000, 100);
            em.persist(book2);
            OrderItem orderItem1 = OrderItem.createOrderItem(book1, 10000, 1);
            OrderItem orderItem2 = OrderItem.createOrderItem(book2, 20000, 2);
            Order order = Order.createOrder(member, createDelivery(member),
                    orderItem1, orderItem2);
            em.persist(order);
        }
        public void dbInit2() {
            Member member = createMember("userB", "์ง„์ฃผ", "2", "2222");
            em.persist(member);
            Book book1 = createBook("SPRING1 BOOK", 20000, 200);
            em.persist(book1);
            Book book2 = createBook("SPRING2 BOOK", 40000, 300);
            em.persist(book2);
            Delivery delivery = createDelivery(member);
            OrderItem orderItem1 = OrderItem.createOrderItem(book1, 20000, 3);
            OrderItem orderItem2 = OrderItem.createOrderItem(book2, 40000, 4);
            Order order = Order.createOrder(member, delivery, orderItem1,
                    orderItem2);
            em.persist(order);
        }
        private Member createMember(String name, String city, String street,
                                    String zipcode) {
            Member member = new Member();
            member.setName(name);
            member.setAddress(new Address(city, street, zipcode));
            return member;
        }
        private Book createBook(String name, int price, int stockQuantity) {
            Book book = new Book();
            book.setName(name);
            book.setPrice(price);
            book.setStockQuantity(stockQuantity);
            return book;
        }
        private Delivery createDelivery(Member member) {
            Delivery delivery = new Delivery();
            delivery.setAddress(member.getAddress());
            return delivery;
        }
    }
}

 

3. API ๊ฐœ๋ฐœ ๊ณ ๊ธ‰ - ์ง€์—ฐ ๋กœ๋”ฉ๊ณผ ์กฐํšŒ ์„ฑ๋Šฅ ์ตœ์ ํ™”

- ๊ฐ„๋‹จํ•œ ์ฃผ๋ฌธ ์กฐํšŒ V1 : ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ง์ ‘ ๋…ธ์ถœ

/*
Order๋ฅผ ์กฐํšŒํ•˜๊ณ ,
Order -> Member m : 1
Order -> Delivery ์—ฐ๊ด€ 1 : 1
๊ฒฐ๋ก  : toOne ๊ด€๊ณ„์—์„œ์˜ ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ์–ด๋–ป๊ฒŒ ํ•  ๊ฒƒ์ธ๊ฐ€

ํ•˜์ง€๋งŒ OrderItem์€ toMany ๊ด€๊ณ„์ž„ -> ์ปฌ๋ ‰์…˜ ์‚ฌ์šฉ, ๋ณต์žก, ๋‹ค์Œ์žฅ์—์„œ
 */
@RequiredArgsConstructor
@RestController
public class OrderSimpleController {
    private final OrderRepository orderRepository;

    @GetMapping("/api/v1/sample-orders")
    public List<Order> orderV1(){
        List<Order> all = orderRepository.findAllByString(new OrderSearch());
        return all;
    }

}

=> ๋ฌธ์ œ : ๊บ„์•„์•… ๋ฌดํ•œ๋ฃจํ”„!

ํ•ด๊ฒฐ๋ฒ• - ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€ ๊ด€๊ณ„๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ, ๋‘˜ ์ค‘ ํ•˜๋‚˜๋Š” @JsonIgnore์„ ๋ถ™์—ฌ์ค˜์•ผ ํ•œ๋‹ค

=> ๋ฌธ์ œ : Proxy ์–ด์ฉŒ๊ตฌ ์—๋Ÿฌ ๋ฐœ์ƒ

@Entity
@Table(name = "orders")  // ํ…Œ์ด๋ธ” ์ด๋ฆ„๊ณผ class๋ช…์ด ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Order {

    /*์ƒ๋žต*/

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    @OneToMany(mappedBy = "order")
    private List<OrderItem> orderItems = new ArrayList<>();

    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "delivery_id")
    private Delivery delivery;

    /*์ƒ๋žต*/
}

fetch๊ฐ€ Lazy๋กœ ๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์—  (1:n ์€ ๊ธฐ๋ณธ์ด Lazy)

Order๋ฅผ ๊ฐ€์ง€๊ณ  ์˜ฌ ๋•Œ, null๋กœ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— Proxy Member๋ฅผ ๋Œ€์‹  ๋„ฃ์–ด๊ฐ€์ง€๊ณ  ๊ฐ€์ ธ์˜ด

Member ๊ฐ์ฒด์— ์†์„ ๋Œˆ ๋•Œ, sql ์ฟผ๋ฆฌ๋ฅผ ๋‚ ๋ ค ๋งž๋Š” ๊ฐ์ฒด๋ฅผ ๋„ฃ์Œ

json์ด member๋ฅผ ๋ฝ‘์„ ๋•Œ๋Š” proxy๊ฐ€ ์žˆ๊ธฐ์— ์˜ค๋ฅ˜๊ฐ€ ๋‚˜๊ฒŒ ๋จ

-> ์ง€์—ฐ ๋กœ๋”ฉ์ธ ๊ฒฝ์šฐ์—๋Š” ์•„์˜ˆ json์œผ๋กœ ๋ฟŒ๋ฆฌ์ง€ ์•Š๋„๋ก ํ•˜๋Š” hibernate module์ด ์žˆ์Œ

(์•„๋ž˜ ๋งํฌ์—์„œ ๋ณต์‚ฌํ•ด์„œ build.gradle์— ๋„ฃ๊ณ  ์‚ฌ์šฉ)

https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-hibernate5/2.9.9

@SpringBootApplication
public class JpashopApplication {

	public static void main(String[] args) {
		SpringApplication.run(JpashopApplication.class, args);
	}

	@Bean
	Hibernate5Module hibernate5Module(){
		Hibernate5Module hibernate5Module = new Hibernate5Module();
		return hibernate5Module;
	}
}

=> ์ง€์—ฐํ•˜๋Š” ์• ๋“ค์€ ์ „๋ถ€ null๋กœ json์œผ๋กœ ๋ฟŒ๋ ค์ง

์˜ต์…˜์„ ๋จน์—ฌ์„œ ๊ฐ•์ œ๋กœ ๋กœ๋”ฉ๋˜๊ฒŒ ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์Œ(์‚ฌ์‹ค ์ด๋Ÿฐ๊ฑฐ ์“ฐ๋ฉด ์•ˆ๋จ)

์˜ต์…˜ ์•ˆ์“ฐ๊ณ  ์ด๋Ÿฐ๊ฑฐ ๋ถ™์—ฌ์„œ ๊ฐ•์ œ ์ดˆ๊ธฐํ™” ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์Œ

for( Order order : all){

    order.getOrder().getName();

    order.getDelivery().getAddress();

}

 

=> 1. ๊ณ„์† ๋ฌธ์ œ์˜€๋˜ ์—”ํ‹ฐํ‹ฐ ๋…ธ์ถœ

     2. ์„ฑ๋Šฅ ์ €ํ•˜, ํ•„์š” ์—†๋Š” API ์ŠคํŽ™์ด ๋…ธ์ถœ๋จ

 

์ฃผ์˜

์ง€์—ฐ ๋กœ๋”ฉ(LAZY)์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด ์ฆ‰์‹œ ๋กœ๋”ฉ(EARGR)์œผ๋กœ ์„ค์ •ํ•˜๋ฉด ์•ˆ๋œ๋‹ค! 

์ฆ‰์‹œ ๋กœ๋”ฉ ๋•Œ๋ฌธ์— ์—ฐ๊ด€๊ด€๊ณ„๊ฐ€ ํ•„์š” ์—†๋Š” ๊ฒฝ์šฐ์—๋„ ๋ฐ์ดํ„ฐ๋ฅผ ํ•ญ์ƒ ์กฐํšŒํ•ด์„œ ์„ฑ๋Šฅ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค. 

์ฆ‰์‹œ ๋กœ๋”ฉ์œผ๋กœ ์„ค์ •ํ•˜๋ฉด ์„ฑ๋Šฅ ํŠœ๋‹์ด ๋งค์šฐ ์–ด๋ ค์›Œ ์ง„๋‹ค.
-> ํ•ญ์ƒ ์ง€์—ฐ ๋กœ๋”ฉ์„ ๊ธฐ๋ณธ์œผ๋กœ ํ•˜๊ณ , ์„ฑ๋Šฅ ์ตœ์ ํ™”๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋Š” ํŽ˜์น˜ ์กฐ์ธ(fetch join)์„ ์‚ฌ์šฉํ•ด๋ผ!(V3)

 

- ๊ฐ„๋‹จํ•œ ์ฃผ๋ฌธ ์กฐํšŒ V2 : ์—”ํ‹ฐํ‹ฐ๋ฅผ DTO๋กœ ๋ณ€ํ™˜

@RequiredArgsConstructor
@RestController
public class OrderSimpleController {

	/*์ƒ๋žต*/

    @GetMapping("/api/v2/sample-orders")
    public List<SimpleOrderDto> orderV2(){
        List<Order> orders = orderRepository.findAllByString(new OrderSearch());
        return orders.stream()
                .map(o-> new SimpleOrderDto(o))
                .collect(Collectors.toList()); //static import๊นŒ์ง€ ํ•˜๋ฉด ์ฝ”๋“œ๋ฅผ ๋” ์ค„์ผ ์ˆ˜ ์žˆ์Œ
    }

    private 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๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ์ผ๋ฐ˜์ ์ธ ๋ฐฉ๋ฒ•

๋กœ๊ทธ์˜ ๋„˜์–ด๊ฐ„ ์ฟผ๋ฆฌ๋ฅผ ํ™•์ธํ•ด๋ณด์ž

์ฟผ๋ฆฌ๊ฐ€ 1 + N + N ๋ฒˆ ์‹คํ–‰๋œ๋‹ค (์ตœ์•…์˜ ๊ฒฝ์šฐ)

์ง€์—ฐ๋กœ๋”ฉ์€ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์—์„œ ์กฐํšŒํ•˜๋ฏ€๋กœ, ์ด๋ฏธ ์กฐํšŒ๋œ ๊ฒฝ์šฐ ์ฟผ๋ฆฌ๋ฅผ ์ƒ๋žตํ•˜๊ธฐ๋„ ํ•˜๊ธฐ ๋•Œ๋ฌธ

 

- ๊ฐ„๋‹จํ•œ ์ฃผ๋ฌธ ์กฐํšŒ V3 : ์—”ํ‹ฐํ‹ฐ๋ฅผ DTO๋กœ ๋ณ€ํ™˜ - ํŒจ์น˜ ์กฐ์ธ ์ตœ์ ํ™”

@Repository
@RequiredArgsConstructor
public class OrderRepository {
	/*์ƒ๋žต*/
    public List<Order> findAllWithMemberDelivery(){

        //fetch join์€ ๋งค์šฐ ์ค‘์š”. 100% ์ดํ•ดํ•ด์•ผ ํ•จ
        return em.createQuery(
                "select o from Order o" +
                        " join fetch o.member m" +
                        " join fetch o.delivery d", Order.class
        ).getResultList();
    }
}

fetch join์œผ๋กœ ํ•˜๋‚˜์˜ ์ฟผ๋ฆฌ๋กœ ๋ชจ๋“  ๋‚ด์šฉ์„ ๋‹ค ๊ฐ€์ง€๊ณ  ์˜จ๋‹ค -> ์„ฑ๋Šฅ ์ตœ์ ํ™”

@RequiredArgsConstructor
@RestController
public class OrderSimpleController {
    
    /*์ƒ๋žต*/

    @GetMapping("/api/v3/sample-orders")
    public List<SimpleOrderDto> orderV3(){
        List<Order> orders = orderRepository.findAllWithMemberDelivery();

        return orders.stream()
                .map(o-> new SimpleOrderDto(o))
                .collect(Collectors.toList());
    }
    
	/*์ƒ๋žต*/
}

- ๊ฐ„๋‹จํ•œ ์ฃผ๋ฌธ ์กฐํšŒ V4 : JPA์—์„œ DTO๋กœ ๋ฐ”๋กœ ์กฐํšŒ

@Data
public class OrderSimpleQueryDto {
    private Long orderId;
    private String name;
    private LocalDateTime orderDate;
    private OrderStatus orderStatus;
    private Address address;

    public OrderSimpleQueryDto(Long orderId, String name, LocalDateTime orderDate, OrderStatus orderStatus, Address address) {
        this.orderId = orderId;
        this.name = name;
        this.orderDate = orderDate;
        this.orderStatus = orderStatus;
        this.address = address;
    }
}
@Repository
@RequiredArgsConstructor
public class OrderRepository {
	/*์ƒ๋žต*/
	public List<OrderSimpleQueryDto> findOrderDtos() {
        return em.createQuery(
                        "select new jpabook.jpashop.repository.order.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();
    }
}
@RequiredArgsConstructor
@RestController
public class OrderSimpleController {
	/*์ƒ๋žต*/
    @GetMapping("/api/v4/sample-orders")
    public List<SimpleOrderDto> orderV4(){
        List<Order> orders = orderRepository.findAllWithMemberDelivery();

        return orders.stream()
                .map(o-> new SimpleOrderDto(o))
                .collect(Collectors.toList());
    }
	/*์ƒ๋žต*/
}

V3์™€ V4๋Š” ์žฅ๋‹จ์ ์ด ์žˆ์Œ

V3์— ๋น„ํ•ด V4๋Š” ์›ํ•˜๋Š” ๊ฒƒ๋งŒ ์กฐํšŒ

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

ํ•˜์ง€๋งŒ,

์žฌ์‚ฌ์šฉ์„ฑ์ด ์—†์Œ. ์ด DTO๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋งŒ ์‚ฌ์šฉํ•˜๊ฒŒ ๋จ

์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋งŒ ์ง์ ‘ SELECT ์ ˆ์—์„œ ์„ ํƒํ•˜๋ฏ€๋กœ ๋„คํŠธ์›Œํฌ ์šฉ๋Ÿ‰ ์ตœ์ ํ™”(์š”์ฆ˜์—๋Š” ๋‹ค ๋นจ๋ผ์„œ ์ด์ •๋„๋Š” ๋ฏธ๋ฏธ)

๋ฐ์ดํ„ฐ ์‚ฌ์ด์ฆˆ๊ฐ€ ๋„ˆ๋ฌด ํฌ๋‹ค๋ฉด ๊ณ ๋ คํ•ด๋ณด๋Š” ๊ฒŒ ์ข‹์Œ

 

API ์ŠคํŽ™์— ๋งž์ถ˜ ์ฝ”๋“œ๊ฐ€ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ๋“ค์–ด๊ฐ€๋Š” ๋‹จ์ ์„ ๋ณด์™„ํ•˜๊ธฐ ์œ„ํ•ด,

Repository์—์„œ ์–˜ ์ „์šฉ์œผ๋กœ ์œ„ ์ฝ”๋“œ ๋ถ€๋ถ„๋งŒ ๋นผ์„œ ๋„ฃ๋Š” ๋ฐฉ๋ฒ•

์ผ๋‹จ V3๋ฅผ ์“ฐ๊ณ , ์ด๋ž˜๋„ ์•ฝํ•˜๋ฉด V4

์ด๋ž˜๋„ ์•ˆ๋˜๋ฉด JPA๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋„ค์ดํ‹ฐ๋ธŒ SQL์ด๋‚˜ ์Šคํ”„๋ง JDBC Template์„ ์‚ฌ์šฉํ•ด์„œ SQL์„ ์ง์ ‘
์‚ฌ์šฉ