집돌이 공대남 IT

JPA 프로그래밍 마스터하기: 초급부터 전문가까지 실습을 통한 학습(10) 본문

IT/웹개발

JPA 프로그래밍 마스터하기: 초급부터 전문가까지 실습을 통한 학습(10)

집공이 2023. 8. 23. 12:00

JPA 성능 최적화 - 더 빠르고 효율적인 데이터 액세스 

안녕하세요, 여러분!집돌이 공대남입니다.

지난 시간에 이어 이번에는 JPA의 성능 최적화 기법에 대해서 상세하게 알아보도록 하겠습니다.

이 글을 통해 JPA를 이용한 데이터 액세스의 효율성을 높일 수 있는 여러 가지 방법을 확인하실 수 있습니다.

 

1. 지연 로딩(Lazy Loading) vs 즉시 로딩(Eager Loading)

먼저, JPA가 제공하는 편리한 기능 중 하나인 '지연 로딩'과 '즉시 로딩'에 대해 살펴보겠습니다. 이는 엔티티와 연관된 다른 엔티티들을 어느 시점에 로딩할지를 결정하는 방법입니다.

즉시 로딩(Eager Loading)은 연관된 엔티티를 메인 엔티티가 로딩될 때 함께 로딩하는 방법입니다. 즉, 쿼리를 실행하면 즉시 관련된 모든 엔티티들도 함께 로딩하므로, 쿼리 한 번으로 필요한 모든 데이터를 가져올 수 있습니다.

반면, 지연 로딩(Lazy Loading)은 연관된 엔티티를 필요할 때 로딩하는 방식입니다. 이 방식을 사용하면 메인 엔티티 로딩 시점에는 연관 엔티티를 로딩하지 않습니다. 대신, 연관 엔티티에 처음 접근할 때(즉, 데이터를 실제로 사용할 때) 해당 엔티티를 로딩합니다.

@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    // getters and setters
}

위의 코드에서는 User 엔티티를 지연 로딩 방식으로 설정하였습니다. 이 경우 Order 엔티티를 로딩하는 시점에는 User 엔티티는 로딩하지 않고, User 엔티티의 데이터가 실제로 필요한 시점에 데이터베이스에서 로딩합니다.

이처럼 지연 로딩을 이용하면 필요한 시점에만 데이터를 로딩함으로써 성능을 개선하는 효과가 있습니다.

 

2. 1차 캐시와 동일성 보장

두 번째로, JPA의 1차 캐시와 동일성 보장 기능에 대해 살펴보겠습니다. 1차 캐시는 EntityManager의 생명주기와 동일하며, EntityManager 내부에 엔티티를 보관하는 저장소입니다.

// 1차 캐시와 동일성 보장
// 데이터베이스에서 아이디가 1인 회원 엔티티를 조회
Member member1 = em.find(Member.class, 1L);
// 동일한 회원 엔티티를 다시 조회
Member member2 = em.find(Member.class, 1L);

System.out.println("result: " + (member1 == member2));

위의 코드에서 두 번의 조회에 대해 em.find() 메서드를 호출했지만, 실제로는 첫 번째 find() 호출 시점에 데이터베이스에서 데이터를 가져와 1차 캐시에 저장합니다. 그 후 두 번째 find() 메서드를 호출할 때는 1차 캐시에 저장된 데이터를 반환하기 때문에, 같은 트랜잭션

안에서는 동일한 엔티티를 반환하게 됩니다.

이렇게 1차 캐시를 활용하면 같은 엔티티에 대한 반복적인 조회 쿼리를 줄일 수 있어 성능 향상에 기여합니다.

 

3. 벌크 연산 최적화

벌크 연산은 한 번의 쿼리로 여러 행을 동시에 처리하는 연산을 말합니다. JPA는 CriteriaUpdateCriteriaDelete를 제공하여 벌크 연산을 지원합니다.

// 벌크 연산 예제
// 영속성 컨텍스트를 초기화하여 1차 캐시를 비운다.
em.flush();
em.clear();

// 벌크 연산을 수행한다.
String qlString = "update Product p set p.price = p.price * 1.1 where p.stockAmount < :stockAmount";
int resultCount = em.createQuery(qlString)
    .setParameter("stockAmount", 10)
    .executeUpdate();

System.out.println(resultCount + "개의 상품 가격이 인상되었습니다.");

위 코드는 재고량이 10 미만인 모든 상품의 가격을 10% 인상하는 벌크 연산을 수행하는 예시입니다. 벌크 연산을 사용하면 한 번의 쿼리로 대량의 데이터를 처리할 수 있어, 성능을 크게 향상시킬 수 있습니다.

 

4. 읽기 전용 쿼리

마지막으로, JPA에서는 읽기 전용 쿼리를 제공하여 성능을 향상시킬 수 있습니다. 읽기 전용 쿼리는 데이터를 수정하지 않고 오직 읽기만 수행하는 경우에 사용될 수 있습니다.

// 읽기 전용 쿼리 예제
@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
List<Order> findReadOnlyByMember(Member member);

위의 코드는 읽기 전용 쿼리를 사용하여 회원이 주문한 모든 주문 정보를 조회하는 예시입니다.

읽기 전용 쿼리를 사용하면 플러시 모드를 수동으로 변경하여 변경 감지를 하지 않도록 설정하고, 이를 통해 성능을 개선할 수 있습니다.

이상으로 JPA를 이용한 성능 최적화 방법에 대한 설명을 마치도록 하겠습니다.

다음 시간에는 JPA의 다른 고급 기능에 대해서 알아보도록 하겠습니다. 그 때 뵙겠습니다!