본문 바로가기
Everyday Study

2024.09.23(월) { 페이징, @EntityGraph }

by xogns93 2024. 9. 23.

페이징(Paging)은 대량의 데이터를 한 번에 모두 가져오지 않고, 일정한 단위(페이지)로 나누어 나누어 처리하는 기법입니다. 이를 통해 성능을 개선하고, 메모리 사용을 최적화하며, 사용자에게 보다 나은 경험을 제공합니다. 예를 들어, 데이터베이스에 1000개의 데이터가 있다면, 이를 한 번에 모두 가져오지 않고, 한 페이지에 10개씩 나누어 보여주는 것이 페이징입니다.

페이징의 주요 개념

  1. 페이지 번호 (Page Number):
    • 데이터를 나누었을 때, 각 묶음을 식별하는 번호입니다. 보통 1페이지, 2페이지, 3페이지 등으로 표현되며, 사용자가 원하는 페이지를 선택할 수 있습니다.
  2. 페이지 크기 (Page Size):
    • 한 페이지에 표시할 데이터의 개수입니다. 예를 들어, 페이지 크기가 10이라면, 한 페이지에 10개의 데이터가 표시됩니다.
  3. 오프셋 (Offset):
    • 데이터의 시작점을 의미합니다. 즉, 몇 번째 데이터부터 조회할지 결정하는 값입니다. 오프셋은 (pageNumber - 1) * pageSize로 계산됩니다.
  4. 리미트 (Limit):
    • 한 번에 조회할 데이터의 개수를 의미합니다. 보통 페이지 크기와 같은 값으로 설정됩니다.

페이징의 필요성

  1. 성능 최적화:
    • 대량의 데이터를 한 번에 모두 가져오면, 처리 속도가 느려지고 시스템에 부하가 발생할 수 있습니다. 페이징을 통해 필요한 데이터만 부분적으로 가져옴으로써 성능을 향상시킬 수 있습니다.
  2. 메모리 절약:
    • 모든 데이터를 한 번에 메모리에 적재하지 않고, 필요한 만큼씩 가져오기 때문에 메모리 사용을 줄일 수 있습니다.
  3. 사용자 경험 개선:
    • 웹 애플리케이션이나 앱에서는 한 번에 많은 데이터를 보여주는 것이 아니라, 스크롤을 내리거나 페이지를 이동하면서 데이터를 추가로 불러오는 방식이 사용자에게 더 친숙합니다.

페이징의 동작 방식

페이징은 주로 데이터베이스에서 이루어지며, SQL이나 JPQL, QueryDSL과 같은 쿼리 언어를 사용하여 구현됩니다.

페이징의 예

예를 들어, 100명의 사용자가 있는 데이터베이스에서 10명씩 페이징 처리를 한다고 가정하면:

  1. 1페이지: 1번 사용자부터 10번 사용자까지 조회
  2. 2페이지: 11번 사용자부터 20번 사용자까지 조회
  3. 3페이지: 21번 사용자부터 30번 사용자까지 조회

JPQL로 구현한 페이징 예시

public List<Member> getMembers(int pageNumber, int pageSize) {
    EntityManager em = emf.createEntityManager();
    TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m ORDER BY m.name ASC", Member.class);

    // 페이징 처리
    query.setFirstResult((pageNumber - 1) * pageSize); // 시작 위치 설정
    query.setMaxResults(pageSize); // 한 페이지에 보여줄 최대 결과 수 설정

    return query.getResultList();
}

이 코드에서:

  • setFirstResult()는 조회할 데이터의 시작 위치(오프셋)를 설정합니다.
  • setMaxResults()는 한 페이지에 표시할 데이터의 개수를 설정합니다.

QueryDSL을 사용한 페이징 예시

public List<Member> getMembersByQueryDSL(int pageNumber, int pageSize) {
    EntityManager em = emf.createEntityManager();
    JPAQueryFactory queryFactory = new JPAQueryFactory(em);

    QMember member = QMember.member;

    return queryFactory.selectFrom(member)
            .orderBy(member.name.asc())
            .offset((pageNumber - 1) * pageSize)  // 오프셋 설정
            .limit(pageSize)                      // 페이지 크기 설정
            .fetch();
}

결론

  • 페이징은 대량의 데이터를 페이지 단위로 나누어 처리하는 방식으로, 성능 최적화와 메모리 관리, 사용자 경험 개선을 목적으로 사용됩니다.
  • 페이징을 구현하는 방법으로는 SQL, JPQL, QueryDSL 등 다양한 쿼리 언어를 사용할 수 있으며, 이를 통해 데이터를 효율적으로 조회하고 관리할 수 있습니다.

@EntityGraph는 JPA(Java Persistence API)에서 N+1 문제를 해결하거나, 지연 로딩(Lazy Loading)을 제어하기 위해 엔티티 그래프(Entity Graph)를 사용하는 방법입니다. 이를 통해 JPQL이나 기본 쿼리로 엔티티를 조회할 때 연관된 엔티티들을 함께 조회하도록 설정할 수 있습니다.

N+1 문제

  • N+1 문제란, 한 번의 쿼리로 데이터 N개를 가져온 후, 그와 관련된 데이터를 다시 N번 각각 조회하는 방식으로 추가 쿼리가 발생하는 문제입니다. 이 문제는 성능에 큰 영향을 미치기 때문에, 이를 해결하기 위해 @EntityGraph를 사용할 수 있습니다.

@EntityGraph 개념

  • 엔티티 그래프(Entity Graph)프록시로 인해 발생하는 지연 로딩(Lazy Loading)을 피하고, 원하는 엔티티와 연관된 엔티티를 즉시 로딩(Eager Loading)으로 가져오도록 설정하는 방법입니다.
  • 이를 통해 연관된 엔티티들을 명시적으로 함께 조회할 수 있습니다.

@EntityGraph 사용 예시

기본 사용법

@Entity
public class Member {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    private Team team;

    // getter, setter
}

Member 엔티티는 TeamManyToOne 관계로 맺어져 있으며, 기본적으로 FetchType.LAZY를 사용하여 지연 로딩을 합니다.

@EntityGraph 적용 예시

public interface MemberRepository extends JpaRepository<Member, Long> {

    @EntityGraph(attributePaths = {"team"})
    List<Member> findAll();
}
  • 위 코드에서 @EntityGraph(attributePaths = {"team"})을 사용하여 Member와 연관된 Team 엔티티를 함께 조회합니다. 이를 통해 지연 로딩이 아닌 즉시 로딩으로 데이터를 가져옵니다.
  • 즉, Member를 조회할 때 N+1 문제를 방지하고 한 번의 쿼리로 Team 엔티티도 함께 조회하게 됩니다.

@EntityGraph 속성

  • attributePaths: 함께 조회할 연관된 엔티티의 경로를 명시합니다. 위 예시에서는 team 엔티티를 함께 조회하도록 설정했습니다.

JPQL과 함께 사용

@EntityGraph는 JPQL 쿼리에도 적용할 수 있습니다. 예를 들어, JPQL 쿼리에서 @EntityGraph를 사용하여 특정 연관 엔티티를 즉시 로딩하도록 할 수 있습니다.

@Query("SELECT m FROM Member m")
@EntityGraph(attributePaths = {"team"})
List<Member> findMembersWithTeams();

이렇게 하면, MemberTeam이 한 번의 쿼리로 함께 조회됩니다.

결론

  • @EntityGraph는 JPA에서 지연 로딩으로 인해 발생하는 N+1 문제를 해결하고, 필요한 연관된 엔티티를 즉시 로딩할 수 있도록 돕는 유용한 어노테이션입니다.
  • 이를 사용하면 성능 최적화에 도움이 되며, JPQL과 함께 사용할 수도 있습니다.