3. API 개발 고급 - 지연 로딩과 조회 성능 최적화

shin·2024년 3월 10일

주문 + 배송정보 + 회원을 조회하는 API

  • 지연 로딩 때문에 발생하는 성능 문제를 단계적으로 해결
  • JPA를 활용하여 개발을 진행할 때 성능을 높일 수 있는 방법 학습

XToOne 연관관계 성능 최적화

Order
Order -> Member
Order -> Delivery

domain Order

@Entity
@Table(name = "orders")
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Order {
...
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;
    
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    private List<OrderItem> orderItems = new ArrayList<>();
    
    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "delivery_id")
    private Delivery delivery;
  • Order와 Member ManyToOne 관계 > ToOne
  • Order와 Delivery는 OneToOne 관계 > ToOne
  • Order와 OrderItem은 OneToMany 관계 > ToMany
    • ToMany 관계는 컬렉션이기 때문에 ToOne보다 더 복잡함 > 다음 섹션에서 진행


1. 간단한 주문 조회 V1 : 엔티티를 직접 노출

OrderSimpleApiController

package jpabook.jpashop.api;
...
@RestController
@RequiredArgsConstructor
public class OrderSimpleApiController {

    private final OrderRepository orderRepository;
    
    @GetMapping("/api/v1/simple-orders")
    public List<Order> ordersV1() {
        List<Order> all = orderRepository.findAll(new OrderSearch());
        return all;
    }

}

문제상황 1 : 무한루프

  • API 실행을 해보면 무한루프에 빠진 것처럼 트레이스가 끝나지 않음


Domain Member

@Entity
@Getter @Setter
public class Member {
    ...
    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();

}
  • Order 도메인은 Member를 갖고 있기 때문에 Member 객체의 json을 뽑아내고, Member 도메인 역시 orders를 갖고 있어서 다시 Order json을 뽑아내게 됨
  • 따라서 Order와 Member를 계속 호출하면서 무한루프에 빠지는 양방향 연관관계 문제가 발생하게 됨
@Entity
@Getter @Setter
public class Member {
    ...
    @JsonIgnore
    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();
 
}
@Entity
@Getter @Setter
public class Delivery {
...
    @JsonIgnore
    @OneToOne(mappedBy = "delivery", fetch = FetchType.LAZY)
    private Order order;
...
}
@Entity
@Table(name = "order_item")
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class OrderItem {
...
    @JsonIgnore
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id")
    private Order order;
...
}

해결방안 1 : @JsonIgnore

  • 양방향 연관관계가 걸린 곳은 한곳을 @JsonIgnore 처리를 해줘야 함
    • Member 외에 Order와 양방향이 걸리는 Delivery, OrderItem 모두 @JsonIgnore을 붙여줌

문제상황 2 : ByteBuddyInterceptor 관련 에러

public class Order {
...
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;
  • Order 도메인에서 Member를 LAZY로 Fetch함
  • 지연로딩이면 new로 Member 객체를 가져오는 것이 아님 > DB에서 데이터를 가져오지 않음
    • DB에서 가져올 때는 Order의 데이터만을 가져옴 > LAZY로 설정 되어 있기 때문에
  • Member에 null을 넣어둘 수 없기 때문에 하이버네이트에서 가짜 프록시 멤버 객체를 생성해서 넣어둠
    • proxy 기술을 사용할 때, byteBuddy라는 라이브러리를 사용함
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member = new ByteBuddyInterceptor();
  • 하이버네이트에 의해서 위 코드처럼 ByteBuddyInterceptor 객체를 new로 가져오게 되는 것
  • 프록시 객체를 가짜로 넣어두고, Member 객체의 값을 꺼내거나 변경하는 경우 DB에 Member 객체 SQL을 날려서 Member 객체의 값을 가져와 채워넣음
    • 이를 프록시로 초기화하는 과정이라고 함
  • 하지만 jackson 라이브러리는 기본적으로 이 프록시 객체를 json으로 어떻게 생성해야 하는지 모름 > 예외 발생

해결방안 3 : Hibernate5Module

  • Hibernate가 지연로딩인 경우에는 jackson 라이브러리가 아무것도 하지 않도록 해야 함
    • Hibernate5Module 등록이 필요함

스프링 부트 3.0 미만 : Hibernate5Module 등록

build.gradle

implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate5'

JpashopApplication에 아래 코드 추가

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

스프링 부트 3.0 이상 : Hibernate5JakartaModule 등록

build.gradle

implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate5-jakarta'

JpashopApplication

package jpabook.jpashop;

import com.fasterxml.jackson.datatype.hibernate5.jakarta.Hibernate5JakartaModule;
...

@SpringBootApplication
public class JpashopApplication {

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

    @Bean
    Hibernate5JakartaModule hibernate5Module() {
        return new Hibernate5JakartaModule();
    }

}
  • 기본적으로 초기화 된 프록시 객체만 노출하고, 초기화 되지 않은 프록시 객체는 노출하지 않음

중간 정리

  • 엔티티를 직접 노출하는 것은 좋지 않음(이미 앞 섹션에서 설명한 내용)
  • order -> member와 order -> delivery는 지연로딩
    • 따라서 실제 엔티티 대신에 프록시가 존재함
  • jackson 라이브러리는 기본적으로 이 프록시 객체를 json으로 어떻게 생성해야 하는지 알지 못함 > 예외 발생
  • Hibernate5Module을 스프링 빈으로 등록하면 해결됨

문제상황 4 : null 값 출력

  • 다시 api를 실행하면 정보가 출력이 되기는 하지만, 지연로딩 설정이 되어 있는 필드의 경우에는 null로 값이 출력됨
  • 지연로딩의 경우에는 아직 DB에서 값을 조회한 것이 아니기 때문
    • json에서 값을 뿌릴 때 기본 전략이 Hibernate5Module로 인해 지연 로딩은 무시하도록 되어 있음

해결방안 4-1 : Force Lazy Load

    @Bean
    Hibernate5JakartaModule hibernate5Module() {
        Hibernate5JakartaModule hibernate5JakartaModule = new Hibernate5JakartaModule();
        // 강제 지연로딩 설정
        hibernate5JakartaModule.configure(Hibernate5JakartaModule.Feature.FORCE_LAZY_LOADING, true);
        return new Hibernate5JakartaModule();
    }
  • 위 코드를 추가하면 json을 생산하는 시점에 강제로 lazyLoding을 함
  • 이 옵션을 키면 order -> member, member -> orders 양방향 연관관계를 계속 로딩하게 되기 때문에, @JsonIgnore 옵션을 한곳에 줘야 함
  • 엔티티를 직접 노출하는 방식을 사용하는 것은 좋지 않음
    • Order와의 연관 관계 중 Member와 Delivery만 필요한데, OrderItem까지 api 스펙과 관계없이 노출이 되어버림
  • 사용하지 않는 데이터까지 읽어오게 되어서 성능상 문제가 됨
  • Hibernate5Module을 사용하기 보다는 DTO로 변환해서 반환하는 것이 더 좋은 방법임 > 이후 섹션에서 진행될 예정

해결방안 4-2 : Lazy 강제 초기화

OrderSimpleApiController

package jpabook.jpashop.api;
...
@RestController
@RequiredArgsConstructor
public class OrderSimpleApiController {

    private final OrderRepository orderRepository;

    /**
     * V1. 엔티티 직접 노출
     * - Hibernate5Module 모듈 등록, LAZY=null 처리
     * - 양방향 관계 문제 발생 -> @JsonIgnore
     */
    @GetMapping("/api/v1/simple-orders")
    public List<Order> ordersV1() {

        List<Order> all = orderRepository.findAll(new OrderSearch());
        for (Order order : all) {
            order.getMember().getName(); //Lazy 강제 초기화
            order.getDelivery().getAddress(); //Lazy 강제 초기화
        }

        return all;

    }

}
  • force lazy load를 끄고 원하는 것만 골라서 출력하는 방식
  • order.getMember()까지는 가짜 프록시 객체를 가져오지만, getName()을 하게 되면 실제 name을 가지고 와야 하기 때문에 Lazy가 강제 초기화 되면서 Member Query를 날려서 jpa가 해당 데이터를 다 끌고 오게 됨
  • 하지만 일반적으로 API를 이렇게 복잡한 방식으로 만들지 않음

🚨 주의사항

  • 지연 로딩(LAZY)을 피하기 위해서 즉시 로딩(EARGR)으로 설정해서는 안됨
  • 즉시 로딩 때문에 연관 관계가 필요하지 않은 경우에도 데이터를 항상 조회해서 성능 문제가 발생할 수 있음
  • 즉시 로딩으로 설정하면 성능 튜닝이 매우 어려워짐
  • 항상 지연 로딩을 기본으로 해야 함
  • 성능 최적화가 필요한 경우에는 Fetch Join을 사용해야 함 > V3에서 사용할 방법


2. 간단한 주문 조회 V2 : 엔티티를 DTO로 변환

OrderSimpleApiController

package jpabook.jpashop.api;
...
@RestController
@RequiredArgsConstructor
public class OrderSimpleApiController {

    private final OrderRepository orderRepository;

    ...
    
    @GetMapping("/api/v2/simple-orders")
    public List<SimpleOrderDto> ordersV2() {

        List<Order> orders = orderRepository.findAll(new OrderSearch());

        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로 변환하는 일반적인 방법
  • api 스펙에 맞춰서 SimpleOrderDto 구성
  • map으로 order를 SimpleOrdeDto로 변환한 후 collect로 list 형태로 변환하여 반환
  • 엔티티가 변경되면 해당 dto에서도 컴파일 오류가 발생하여 변경을 캐치할 수 있음

단점 : 지연로딩으로 쿼리 N번 호출

  • 위 결과를 얻기 위해서는 Order, Member, Delivery 총 3개의 테이블의 조회해야 함
        public SimpleOrderDto(Order order) {
        
            orderId = order.getId();
            name = order.getMember().getName(); // LAZY 초기화
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getDelivery().getAddress(); // LAZY 초기화
            
        }
  • stream으로 루프를 돌리면, name 필드가 member에 name을 가져오는데 이때 Lazy가 초기화됨
    • Lazy가 초기화된다는 것은, 영속성 컨텍스트가 memberId를 가지고 영속성 컨텍스트를 찾아보고 없으면 db 쿼리를 날려서 데이터를 끌고오는 것임
    • address도 마찬가지임


  • Order, Member, Delivery 3개의 테이블 조회를 통해 첫번째 주문서가 완성이 됨
  • 두 번째 주문서는 또 Member, Delivery를 가져옴
    @GetMapping("/api/v2/simple-orders")
    public List<SimpleOrderDto> ordersV2() {
		// OOrder -> SQL 1번 실행 -> 결과 주문수 2개
        List<Order> orders = orderRepository.findAll(new OrderSearch());

		// 루프 2번 Member, Delivery 각각 2번 실행
        List<SimpleOrderDto> result = orders.stream()
                .map(o -> new SimpleOrderDto(o))
                .collect(toList());

        return result;

    }
    
     @Data
    static class SimpleOrderDto {
        ...
        public SimpleOrderDto(Order order) {
        
            orderId = order.getId();
            name = order.getMember().getName(); // LAZY 초기화 - Member 조회
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getDelivery().getAddress(); // LAZY 초기화 - Delivery 조회
            
        }

    }
  • Order가 2개이기 때문에, Member와 Delivery 지원로딩 조회가 각각 2번씩 실행됨
    • 총 5번의 쿼리 수행 = Order 1 + Member 2 + Delivery 2
  • order 수가 늘어날수록 쿼리의 수가 늘어남
  • 만약 지연로딩을 제거하면 수행 예측이 불가능하고, 성능도 오르지 않음
    • 따라서 모든 연관 기능은 LAZY로 설정해놓는 것이 좋고, 성능 튜닝은 fetch join을 이용해서 해야 함
  • 쿼리가 총 1 + N + N번 실행(v1과 쿼리수 결과는 같음)
    • order 조회 1번(order 조회 결과 수가 N이 됨)
    • order -> member 지연 로딩 조회 N번
    • order -> delivery 지연 로딩 조회 N번
    • 예) order의 결과가 4개면 최악의 경우 1 + 4 + 4번 실행됨(최악의 경우)
      • 지연로딩은 영속성 컨텍스트에서 조회하므로, 이미 조회된 경우 쿼리를 생략함
  • 지연로딩으로 인해 데이터베이스 쿼리가 너무 많이 호출되는 문제가 발생함

  • 지연로딩은 N번 수행되면, N번을 DB 쿼리가 아니라 영속성 컨텍스트를 찔러봄

    • 만약 주문한 회원이 모두 같은 memberId를 가지고 있으면, 그 경우에는 N번이 아니고 1번이 됨
    • userA가 주문을 여러번 수행한 경우, 처음에는 영속성 컨텍스트를 확인해 값이 없어서 DB 쿼리를 날려서 데이터를 가져오겠지만 그 이후에는 영속성 컨텍스트에 있는 값을 그대로 사용함
    • 하지만 대부분의 경우 다른 유저들이 주문한 경우가 많기 때문에, 최악의 경우로 생각해야 함


3. 간단한 주문 조회 V3 : 엔티티를 DTO로 변환 - fetch join 최적화

fetch join

  • jpql에서 성능 최적화를 위해 제공하는 조인의 종류

    • JPQL : 엔티티 객체를 조회하는 객체지향 쿼리
  • fetch join은 연관된 엔티티나 컬렉션을 한 번에 같이 조회할 수 있는 기능

  • JOIN FETCH 명령어로 사용이 가능함

  • 조회의 주체가 되는 엔티티 외에도 fetch join이 걸린 연관 엔티티도 함께 select하여 모두 영속화함

    • fetch join이 걸린 엔티티를 모두 영속화하기 때문에, FetchTypeLazy인 엔티티를 참조하더라도 이미 영속성 컨텍스트에 들어가 있는 상태임
    • 따라서 따로 쿼리를 실행하지 않기 때문에 N + 1 문제가 해결됨

OrderRepository

package jpabook.jpashop.repository;
...
@Repository
@RequiredArgsConstructor
public class OrderRepository {

    private final EntityManager em;
    
    ...
    
    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();
        
    }

}
  • 조회의 주체가 되는 Order 엔티티 외에도 fetch join이 걸린 Member, Delivery 엔티티도 함께 select하여 모두 영속화함

    • 이 경우에는 지연 로딩을 무시하고, 프록시 객체도 아닌 진짜 객체 값을 가져와서 채워버림
  • fetch join으로 order -> member, order -> delivery는 이미 조회된 상태이므로 지연로딩X


OrderSimpleApiController

    @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;

    }
  • V2와 동일한 SimpleOrderDto 사용

  • v2와 수행 결과는 동일함

  • order와 member가 조인, order와 delivery가 조인
  • 엔티티를 fetch join을 사용해서 쿼리 1번에 조회
    • fetch join을 활용하면 성능 튜닝이 확실하게 됨


4. 간단한 주문 조회 V4 : JPA에서 DTO로 바로 조회

  • 주문 조회 V3의 경우 엔티티를 조회한 후에 DTO로 변환을 해줬지만, 이번에는 DTO로 바로 조회하는 방식으로 구현
    • 이전 방식들보다 조금 더 성능 최적화가 가능해짐

OrderSimpleApiController

...
public class OrderSimpleApiController {
...
    @GetMapping("/api/v4/simple-orders")
    public List<SimpleOrderDto> ordersV4() {

        return orderRepository.findOrderDtos();

    }

}
  • OrderRepository에 findOrderDtos() 메서드 생성

OrderSimpleQueryDto

@Data
public class OrderSimpleQueryDto {

    private Long orderId;
    private String name;
    private LocalDateTime orderDate;
    private OrderStatus orderStatus;
    private Address address;

    public OrderSimpleQueryDto(Order order) {

            orderId = order.getId();
            name = order.getMember().getName();
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getDelivery().getAddress();

   }

}
  • 그러나 V3처럼 Controller 내부의 SimpleOrderDto를 사용하지 않고, 별도의 OrderSimpleQueryDto를 정의하여 Repository에서 해당 객체를 DTO로 사용할 수 있도록 해야 함
    • 의존관계는 한 방향으로 흘러가야 함
    • Controller 내부의 SimpleOrderDto를 바라볼 경우 RepositoryController를 바라보는 상황이 될 수 있음

수정 1

OrderSimpleQueryRepository

package jpabook.jpashop.repository.Order.simplequery;
...
@Repository
@RequiredArgsConstructor
public class OrderSimpleQueryRepository {

    private final EntityManager em;

    public List<OrderSimpleQueryDto> findOrderDtos() {

        return em.createQuery("select o from Order o" +
                        " join o.member m" +
                        " join o.delivery d", OrderSimpleQueryDto.class)
                .getResultList();

    }
}
  • V4를 위한 Repository도 별도로 생성해줌
    • OrderRepository는 순수하게 entity를 조회하는 용도로만 사용하고, api 스펙을 그대로 정의하는 v4의 경우에는 별도의 패키지로 분리하여 작성해주는 것이 좋음
package jpabook.jpashop.api;
...
@RestController
@RequiredArgsConstructor
public class OrderSimpleApiController {

    private final OrderSimpleQueryRepository orderSimpleQueryRepository;
    
    /**
     * V4. JPA에서 DTO로 바로 조회
     * @return List<OrderSimpleQueryDto>
     */
    @GetMapping("/api/v4/simple-orders")
    public List<OrderSimpleQueryDto> ordersV4() {

        return orderSimpleQueryRepository.findOrderDtos();

    }

}
  • JPA는 Entity나 Value Object만 기본적으로 반환하기 때문에, 해당 상태에서는 DTO로 매핑이 되는 것은 아님

수정 2

OrderSimpleQueryDto

package jpabook.jpashop.repository.Order.simplequery;
...
@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;
    }

}
  • Order 엔티티를 받을 수 없기 때문에 파라미터로 하나씩 값을 받도록 수정해야 함

OrderSimpleQueryRepository

package jpabook.jpashop.repository.Order.simplequery;
...
@Repository
@RequiredArgsConstructor
public class OrderSimpleQueryRepository {

    private final EntityManager em;

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

    }
}
  • OrderSimpleQueryRepository의 findOrderDtos()에서 jpql을 작성할 때, select 절에서 new operation을 사용해야 함

  • new 명령어를 사용해서 JPQL의 결과를 DTO로 즉시 변환함

  • 일반적인 SQL을 사용할 때처럼 원하는 값을 선택해서 조회

  • SELECT 절에서 원하는 데이터를 직접 선택하므로 DB -> 애플리케이션 네트워크 용량 최적화

    • 하지만 요즘은 네트워크 성능이 좋아져서 select 절에 들어갈 컬럼의 개수가 엄청 많거나 api 트래픽이 높지 않은 이상, 성능 차이가 미비함
  • 리포지토리 재사용성 떨어짐, API 스펙에 맞춘 코드가 리포지토리에 들어가는 단점이 있음


수행 테스트

V3와 V4의 수행 결과를 비교하기 위해, V3를 먼저 수행해봄

V3 수행 결과


V4 수행 결과


비교

  • V3처럼 join을 수행하는 부분은 동일하지만, V4의 경우 원하는 값만 select절에서 정의하여 가져온 것을 확인할 수 있음
  • V3는 모든 컬럼의 값을 가져오기 때문에 데이터를 DB에서 더 많이 가져오게 되고, 결과적으로 네트워크를 더 사용하게 됨
  • 하지만 항상 V4가 V3보다 좋다고 할 수는 없음


🚨 V3 vs V4 : Trade Off

Query

  • V3 : 외부의 모습은 건드리지 않은 상태에서(Order를 전달해야하는 것 자체를 건드리지 않음), 내부로 원하는 것만 패치조인을 통해 가져와서 성능튜닝이 가능함
  • V4 : 쿼리를 가져올 때, 실제 SQL 짜듯이 JPQL을 쭉 짜서 가져옴

재사용성

  • V3 : 재사용이 가능하여 공용으로 사용이 가능함
  • V4 : 특정 화면에는 최적화되어 있어서 재사용이 어려움
    • 리포지토리 재사용성 떨어짐, API 스펙에 맞춘 코드가 리포지토리에 들어가는 단점이 있음

데이터 변경 가능성

  • V3 : Entity를 조회하기 때문에 비즈니스 로직에서 데이터를 변경하는 작업이 가능함
  • V4 : DTO를 조회했기 때문에 엔티티가 아니여서 변경이 아예 불가능함

성능 최적화

  • V3보다 V4가 SELECT 절에서 원하는 데이터를 직접 선택하므로 DB -> 애플리케이션 네트워크 용량 최적화

코드의 복잡성

  • V4가 jpql 작성시 코드가 더 복잡한 측면이 있음


😊 정리

  • 엔티티를 DTO로 변환하거나 DTO로 바로 조회하는 두가지 방법은 각각 장단점이 존재함
  • 상황에 따라서 더 나은 방법을 선택하면 됨
  • 엔티티로 조회하면 리포지토리 재사용성도 좋고, 개발도 단순해짐

쿼리 방식 선택 권장 순서

  1. 우선 엔티티를 DTO로 변환하는 방법을 선택(V2방식)
  2. 필요하면 fetch join으로 성능을 최적화(V3방식) > 대부분의 성능 이슈가 해결됨
  3. 그래도 안되면 DTO로 직접 조회하는 방법 사용(V4방식)
  4. 최후의 방법은 JPA가 제공하는 네이티브 SQL이나 스프링 JDBC Template을 사용해서 SQL을 직접 사용

참고 사항

Native SQL

  • JPQL은 표준 SQL이 지원하는 대부분의 문법을 지원하지만 특정 데이터베이스에 종속적인 기능은 지원하지 않음
  • JPA는 Native SQL을 통해서 SQL을 직접 사용할 수 있는 기능을 제공함
  • SQL을 개발자가 직접 정의하는 것
  • 엔티티를 조회할 수 있고 JPA가 지원하는 영속성 컨텍스트의 기능도 그대로 사용할 수 있음
  • 사용방법 : em.createNativeQuery(SQL, 결과 클래스);
  • 이름 기반 파라미터를 지원하지 않고, 위치 기반 파라미터만 제공함

스프링 JDBC Template

  • JDBC : Java Database Connectivity, DB 접근
  • 트랜잭션 관리 및 리소스 관리 등을 자동으로 수행하여 JDBC를 편리하게 사용할 수 있게 해줌
  • 개발자는 SQL을 작성하고 전달할 파라미터를 정의한 후 응답값을 매핑하기만 하면 됨

강의 출처 : 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화

profile
Backend development

0개의 댓글