본문 바로가기
Spring

springboot(jpa, mybatis) - page 객체 및 커버링 인덱스를 사용해보자!

by khds 2023. 8. 24.

 

 

들어가기

 

평소 프로젝트를 진행할 때는 페이지 처리를 할 때, 페이지 처리 로직이나 객체를 직접 만들어서 사용했었다.

하지만 최근 Page 객체라는 것이 있고 spring에서 제공하고 있다는 것을 알았다.

Page 객체를 사용하면 다음페이지가 있는지, 전체 사이즈가 몇인지, 이전 페이지가 있는지 등 구현하기 귀찮은 여러 메서드들을 제공해 주니 유용하게 사용된다고 한다.

페이지 처리를 하는데 있어 Page 객체를 사용 안 해볼 수는 없기에 직접 사용해 보기로 하였다.

이 글은 페이지 객체를 사용한 경험담을 담고있다.

처음에는 Page 객체니, PageRequest 니 Pagable이니 PageImpl 등 뭔 Page 하나에 관련된 객체가 너무 많아서 어디서부터 접근해야 할지 막막했었다. 

page처리를 위해 몇 page인지와 몇 개씩 정렬할지만 필요한데 뭘 이리 복잡하게 만든 건지...

하지만 많은 사람들이 애용하는 데는 이유가 있을 것이다..!

그래서 하나하나 접근해 보기로 하였다.

 

 

Page 객체를 뜯어보자

 

Page가 어떻게 생겼는지 확인해 보니 Interface였다. Page 객체를 사용해도 이를 구현체로 사용하는 클래스는 따로 있었던 것이다.

바로 PageImpl이라는 구현체이다.

 

아래는 PageImpl의 생성자이다.

public PageImpl(List<T> content, Pageable pageable, long total) {

		super(content, pageable);

		this.total = pageable.toOptional().filter(it -> !content.isEmpty())//
				.filter(it -> it.getOffset() + it.getPageSize() > total)//
				.map(it -> it.getOffset() + content.size())//
				.orElse(total);
	}

 

 

super로 받는 생성자는 추상클래스인 Chunk의 생성자이다.

PageImpl은 Chunk와 Page를 상속받는다.

Page나 Chunk는 다른 객체를 또한 상속받고 있지만 이 글에서는 자세히 다루지는 않겠다.

중요한 것은 Page객체의 구현체로서 PageImpl을 생성할 때 담는 변수들이다. 저 변수들을 채워 넣기만 하면 Page 객체에서 제공하는 여러 편리한 메서드들을 그대로 사용할 수 있는 것이다. 

위에 코드를 보는 바와 같이 content 리스트, Pageable 객체, total이 있다. content 리스트는 원하는 페이지에 존재하는 레코드 리스트이고, total은 전체 레코드의 수를 의미한다는 것을 생성자 위에 쓰여있는 주석의 내용으로 확인할 수 있었다.

하지만 딱 봐도 한 번에 알 수 없는 것이 있었는데 바로 Pageable객체였다. 주석 내용에도 페이지 정보라고만 쓰여있었다.

이건 또 뭐지..?라는 생각이 들었다.

 

Page객체를 알다가 PageImpl 객체를 알고 또 Pageable 객체가 나왔다.

실제 사용되는 예시 코드를 보면 항상 Pageable 객체가 등장을 했었기에 이 또한 중요한 객체라고 생각하였다.

Pageable 객체를 확인해 보니 이 또한 인터페이스였다. 그렇다면 이를 구현하는 구현체가 있을 것이다.

찾아보니 PageRequest라는 클래스가 AbstractPageRequest라는 추상클래스를 상속받고 AbstractPageRequest가 Pageable 인터페이스를 상속받는 것이다. 즉, PageRequest가 구현체인 셈이다.

 

PageRequest의 생성자는 아래와 같다.

protected PageRequest(int page, int size, Sort sort) {

		super(page, size);

		Assert.notNull(sort, "Sort must not be null");

		this.sort = sort;
	}

 

 

여기서 확인할 사항은 매개변수로 page와 size와 sort를 담는 것이다. 

page는 몇 페이지인지, size는 페이지당 몇 개의 레코드들을 담을지를 나타낸다. 

처음 본 객체는 Sort인데 이 또한 간단했다. 어떤 값을 어느 방향으로 정렬할지를 객체로 표현한 것이었다.

Sort 객체를 확인해 보니 확연히 눈에 띄는 변수가 하나 있었는데 List <Order> 타입의 'orders'였다.

이 orders가 정렬을 이루는 핵심 변수라는 것을 알았고 이와 관련된 메서드들이 많이 있었다.

 

Order 객체는 Sort 객체 내부에 static class로 있었고 확인해 보니 아래와 같은 변수들로 이루어져 있었다.

private final Direction direction;
private final String property;
private final boolean ignoreCase;
private final NullHandling nullHandling;

 

 

Direction 객체는 'ASC'와 'DESC'로 이루어진 enum 값으로 정방향인지 역방향인지를 나타낸다.

property는 어떠한 값을 기준으로 정렬을 할지 나타내는 String 값이다. 

Direction과 property는 바로 알 수 있었지만 ignoreCase와 NullHandling는 뭔지를 잘 몰랐지만 객체 내부 주석이나 스프링 공식문서를 확인하여 알아낼 수 있었다.

ignoreCase는 대문자, 소문자를 구분하지 않도록 하는 것이고 NullHandling은 NATIVE, NULLS_FIRST, NULLS_LAST로 이루어진 enum 값으로 예외가 발생할 때 처리되는 값이라고 한다. 

 

아래는 Sort 객체를 생성하는 예시코드이다. 

Sort sort = Sort.by(Order.asc("id").ignoreCase(), Order.desc("content"));
Sort sort = Sort.by(Order.asc("id"), Order.desc("content"));

 

 

찾아보니 두 가지 방법 정도 확인했고 취향에 따라 쓰면 될 것 같다. 

"id"와 "content"가 property이고 asc나 desc가 direction이다. 

 

즉, 정리하면 

Page 인터페이스를 구현한 PageImpl을 완성해야 하고 이를 위해 content 리스트, Pageable 객체, totalCount가 필요하다.

Pageable 인터페이스를 구현한 PageRequest를 완성하기 위해선 page, page당 size, Sort 객체가 필요하다. 

Sort는 order 정보이고 direction과 property가 필요하다. 

결국, content 리스트, page, page당 size, Sort(direction, property), totalCount 만 필요한 것이다. 

정리하고 보니 생각보다단순했고 어려운 것도 아니었다...

Page 객체를 처음 접할 때는 어디서부터 접근해야 할지 난감했었지만 하나하나 확인해 보니 결국 필요한 것들은 쉽게 정할 수 있는 것이었다.

그렇다면 이제 Page객체를 사용하여 페이지 처리를 해보자!

 

 

Page 객체를 사용한 페이징 처리

 

이제는 직접 Page 객체를 활용해 페이지 처리를 할 차례이다. 

페이지 처리 방식에도 오프셋 기반 방식과 커서 기반 방식이 있다. 이 글에서는 오프셋 기반 방식에 대해 작성하겠다.

페이지 조회를 위해 select를 할 때 정해야 할 것은 offset와 limit, order by이다. 

offset는 전체 데이터중 몇 번째 데이터부터인지, limit는 몇 개까지 조회할지, order by는 정렬을 어떻게 할지 이다. order by는 id desc, content asc 등 자주 접했을 것이다. 

 

offset은 PageRequest가 상속받은 AbstractPageRequest에서 구하는 방식이 나와있고 아래와 같다.

public long getOffset() {return (long) page * (long) size;}

 

즉 page * size 인 것이다. 여기서 주의할 것은 페이지 값을 그대로 입력하면 안 된다. 항상 -1 한 값을 페이지로 넣어야 한다. 그 이유는 위에서 offset를 구하는 방식에 있다.

예를 들어 1페이지의 size는 10개를 조회하며 id에 asc로 조회를 하고 싶다고 하자. id는 0부터 9까지 10개를 조회하기를 원한다. 위의 offset 공식으로 page * size를 하면 1 * 10으로 offset는 10으로 10부터 10개를 조회하게 된다. 이는 2페이지의 정보이다. 만약 페이지를 0으로 넣으면 0 * 10으로 offset는 0으로 0부터 10개를 조회하게 되고 원하는 정보를 얻을 수 있게 된다. 이는 페이지가 설정 자체가 1부터 시작이 아니라 0부터 시작해야 0 ~ 1 사이의 값이 첫 번째로 올 수 있도록 되어있기 때문이다. 그렇기에 프론트로부터 몇 페이지인지 받으면 서비스에서는 페이지에서 -1 한 값을 사용하도록 하자.

limit는 size로 크게 계산할 것은 없다. 

 

 

1. mybatis를 통한 페이징 처리 

 

우선 mybatis를 통해 구현해 보았다. 데이터베이스는 mysql을 사용하였다. 

페이징을 위한 테이블은 article이고 id, title, content, createDate, memberId로 이루어져 있다. 

데이터는 10만 건을 미리 삽입하였다.

offest과 limit는 위에서 언급한 데로 쉽게 구할 수 있는데 orderBy는 어떻게 할 것인지를 고민했었다. 왜냐하면 mybatis에서는 쿼리를 작성해야 하는데 orderBy에는 "id desc" 든 지 "content asc"든지 문자열로 표현이 되어야 하기 때문이다. service 단에서 문자열을 직접 집어넣어도 되지만 지금은 Sort 객체를 사용하고 있다. 

그렇다면 Sort 객체를 문자열로 바꿀 필요가 있는 것이다. Sort 객체는 위에서 언급한 바와 같이 Order객체의 리스트로 구성되어 있고 Order를 이루는 변수 중에 Direction과 Property가 있다. 두 변수를 결합하여 "id DESC content ASC"의 형태로 만들어야 한다.

 

그래서 Sort 객체를 입력받으면 String를 리턴하는 PageHelper라는 클래스를 구현하였다. 코드는 아래와 같다.

public class PageHelper {

    public static String orderBy(Sort sort) {
        if (sort.isEmpty()) {
            return "id DESC";
        }
        List<String> sorted = sort.stream()
            .map(order -> order.getProperty() + " " + order.getDirection())
            .collect(Collectors.toList());
        String orderBy = String.join(", ", sorted);
       
        return orderBy;
    }
}

 

 

만약 sort가 없다면 default로 'id desc'를 리턴하도록 하였다. sort가 있다면 stream.map으로 list를 order라는 값으로 뽑아내고 getProperty()와 getDirection()을 결합하여 orderBy 형식에 맞게 String으로 구성하여 Strlng 리스트를 만들고, 이를 String.join을 통해 하나의 String으로 만들고 리턴하도록 하였다. 이 String 값이 orderBy로 들어가는 것이다.

 

아래는 서비스 메서드를 구현한 코드이다.

    public Page<Article> findArticlePageByMaBatis(int page, Long memberId) {
        int pageSize = 10;
        Sort sort = Sort.by("id").descending();
        PageRequest pageable = PageRequest.of(page, pageSize, sort);
        List<Article> articles = articleMapper.findArticlesInPage(pageable.getPageSize(),
            pageable.getOffset(), memberId, PageHelper.orderBy(sort));
        Long totalCount = articleMapper.countTotalArticles();
        return new PageImpl<>(articles, pageable, totalCount);
    }

 

 

memberId와 page 번호를 받고 내부에서 Sort와 PageRequest를 만들고 articles와 totalCount를 반환하는 메서드를 실행하여 Page 객체를 반환하도록 하였다. 

 

articleMapper.findArticlesInPage()는 어떻게 작성되었는지를 아래의 코드를 봐보자.

// mapper interface
List<Article> findArticlesInPage(int size, Long offset, Long memberId, String orderBy);

//xml 파일
<select id="findArticlesInPage" resultType="Article">
    SELECT id, title, content, create_date as createDate, member_id as memberId
    FROM article WHERE member_id = #{memberId}
    ORDER BY ${orderBy}
    limit #{size} offset #{offset}
  </select>

 

 

size와 offset, memberId, orderBy를 입력받고 쿼리의 내용을 실행하도록 하였다. 

offset부터 limit인 size까지의 레코드들을 orderBy에 맞게 뽑아내는 것이다.  위의 서비스를 통해 api를 실행한 결과는 아래와 같다.

{
    "content": [
        {
            "id": 99990,
            "title": "LgAmlO",
            "content": "fXichmLwXTheTYeCjme",
            "createDate": "2022-11-26T01:39:47.760+00:00",
            "memberId": 1
        },
        
         ...
         
        {
            "id": 99981,
            "title": "oMLXICokha",
            "content": "wIgSitH",
            "createDate": "2022-11-23T05:02:50.282+00:00",
            "memberId": 1
        } //10건
    ],
    "pageable": {
        "sort": {
            "empty": false,
            "sorted": true,
            "unsorted": false
        },
        "offset": 10,
        "pageSize": 10,
        "pageNumber": 1,
        "paged": true,
        "unpaged": false
    },
    "last": false,
    "totalElements": 100000,
    "totalPages": 10000,
    "size": 10,
    "number": 1,
    "first": false,
    "sort": {
        "empty": false,
        "sorted": true,
        "unsorted": false
    },
    "numberOfElements": 10,
    "empty": false
}

 

 

content 안에 원하는 데이터들이 있고 그 아래 기타 정보들이 나와있다. 이 정보들을 활용하여 여러 가지를 할 수 있을 것이다. 

 

 

2. JPA를 통한 페이징 처리 

 

Spring Date Jpa에서는 놀랍게도 Page 객체에 대해 편리한 기능을 가지고 있다고 한다.

바로 메서드를 선언하고 Pageable 객체를 넣으면 Page객체를 리턴해주는 것이다!

Page 객체는 content 리스트와 Pageable, totalCount로 이루어져 있고 content 리스트나 totalCount는 데이터베이스에 접근하여 얻을 수가 있기에 Pageable 객체만 가지고 Page 객체를 얻을 수 있는 것이다.

Mybatis로 구현했을 때보다 너무 쉽게 구현되었기에 살짝 헛 고생한 느낌이 들었다...

하지만 내부에서 실행되는 로직은 비슷하고 복잡한 쿼리를 작성할 때는 @Query를 사용하거나 QueryDsl, mybatis 등을 사용해야 하니 크게 영향을 받지는 않을 것이라고 생각하였다. 

물론 간단한 쿼리를 작성할 때는 쉽게 구현을 하기에 굉장히 유용하다고 생각한다.

 

주요 쿼리들은 아래와 같다. 

@Transactional(readOnly = true)
    public Page<Article> findArticlePage(int page, Long memberId) {
        int pageSize = 10;
        PageRequest pageable = PageRequest.of(page, pageSize, Sort.by("id").descending());
        Page<Article> articles = articleRepository.findAllByMemberId(memberId, pageable);

        return articles;
    }

 

 

public interface ArticleRepository extends JpaRepository<Article, Long> {

    Page<Article> findAllByMemberId(Long memberId, Pageable pageable);
}

 

 

mybatis에서의 service 단 보다 더 짧게 구현을 할 수 있었다. 단순히 articles를 구하고 count를 구하고 Page객체를 생성하는 코드가 짧게 변한 것이다. 아래를 보면 contents를 조회하는 쿼리와 count를 구하는 쿼리가 각각 실행된 것을 알 수 있다.

 

jpa find page 객체

 

 

아래는 결과 값으로 mybatis 때와 동일하다. 

{
    "content": [
        {
            "id": 99990,
            "title": "LgAmlO",
            "content": "fXichmLwXTheTYeCjme",
            "createDate": "2022-11-26T01:39:47.760+00:00",
            "memberId": 1
        },
        
         ...
         
        {
            "id": 99981,
            "title": "oMLXICokha",
            "content": "wIgSitH",
            "createDate": "2022-11-23T05:02:50.282+00:00",
            "memberId": 1
        } //10건
    ],
    "pageable": {
        "sort": {
            "empty": false,
            "sorted": true,
            "unsorted": false
        },
        "offset": 10,
        "pageSize": 10,
        "pageNumber": 1,
        "paged": true,
        "unpaged": false
    },
    "last": false,
    "totalElements": 100000,
    "totalPages": 10000,
    "size": 10,
    "number": 1,
    "first": false,
    "sort": {
        "empty": false,
        "sorted": true,
        "unsorted": false
    },
    "numberOfElements": 10,
    "empty": false
}

 

 

3. JPA를 통한 페이징 처리 - Slice 객체를 사용 

 

지금까지 Page 객체를 사용했다면 지금은 Slice 객체를 사용해보려고 한다. 아래의 사진을 봐보자.

 

 

 

보는바와 같이 마지막 페이지로 가는 버튼이 존재한다. 

하지만 몇몇 웹사이트들은 페이지의 끝단을 볼 수 없도록 설정이 되어있다. 왜냐하면 마지막 페이지를 알기 위해서는 totalCount가 무조건 필요하기 때문이다. 

지금까지 구현한 부분에서는 Page 객체를 완성하기 위해 totalCount 가 필요했는데, 이는 데이터 수가 많을수록 구하는 과정에서 성능 이슈가 있다.

그렇기에 totalCount을 아예 제외하고 서비스 사용자들이 페이지의 마지막 부분을 확인할 수 없도록 하여 성능 문제를 해결하는 것이다.

totalCount를 제외한 객체가 바로 Slice 객체이다.

Page 객체도 겨우 이해했는데 Slice 객체도 이해하자니... 처음에는 난감할 뿐이었다.

하지만 다행히 Page와 비슷하기에 크게 어려움은 없었다.

 

Slice객체 또한 인터페이스고 이를 구현한 SliceImpl이라는 구현체가 존재하였다. SliceImpl의 생성자는 아래와 같다. 

public SliceImpl(List<T> content, Pageable pageable, boolean hasNext) {

		super(content, pageable);

		this.hasNext = hasNext;
		this.pageable = pageable;
	}

 

 

super()는 Page와 동일하고 Page와 다른 부분은 totalCount 대신 hasNext 가 추가되었다는 것이다.

이는 다음페이지가 존재하냐 존재하지 않냐를 물어보는 것이다. 쿼리를 작성할 때 limit를 size가 아닌 size + 1로 설정하고 원래 size보다 더 많은 레코드가 존재하는지 않하는지를 통해 다음페이지가 존재하는지를 확인할 수 있다.

Page보다 쉬우면 더 쉬었지 더 어려운 것은 아니었다..

메서드는 반환 값만 Slice로 변경하였고 따로 수정은 하지 않았다.

 

Spring Data JPA는 Slice로도 바로 반환할 수 있기 때문이다.

Slice<Article> articles = articleRepository.findAllByMemberId(memberId, pageable);

 

 

아래는 실행했을 때 나가는 쿼리이다. 확실히 count를 조회하지를 않으니 성능적으로 향상된 부분이 있다.

 

 

 

아래는 반환값으로 Page 객체와 다르게 totalCount 값이 없는 것을 알 수 있다.

{
    "content": [
        {
            "id": 99990,
            "title": "LgAmlO",
            "content": "fXichmLwXTheTYeCjme",
            "createDate": "2022-11-26T01:39:47.760+00:00",
            "memberId": 1
        },
        
        ...
        
        {
            "id": 99981,
            "title": "oMLXICokha",
            "content": "wIgSitH",
            "createDate": "2022-11-23T05:02:50.282+00:00",
            "memberId": 1
        } //10건
    ],
    "pageable": {
        "sort": {
            "empty": false,
            "sorted": true,
            "unsorted": false
        },
        "offset": 10,
        "pageNumber": 1,
        "pageSize": 10,
        "paged": true,
        "unpaged": false
    },
    "size": 10,
    "number": 1,
    "sort": {
        "empty": false,
        "sorted": true,
        "unsorted": false
    },
    "numberOfElements": 10,
    "first": false,
    "last": false,
    "empty": false
}

 

 

4. 커버링 인덱스를 통한 성능 개선

 

지금까지 Page, Slice 객체를 통해 page 처리를 해보았다. 하지만 오프셋 기반 페이징 방식에는 큰 문제가 하나 있다고 한다.

totalCount를 조회하는 문제인가? 생각을 하였지만 Slice 객체로 받을 때 totalCount를 받지 않는 것을 보면 이는 문제가 아니다. 아무리 쿼리를 보아도 무슨 문제인지를 전혀 찾지 못하였다. 알고 보니 쿼리 내부의 실행 순서에 답이 있다고 한다. 실행 순서는 아래와 같다.

  1. FROM
  2. WHERE
  3. GROUP BY
  4. HAVING
  5. SELECT
  6. ORDER BY
  7. LIMIT

 

위의 순서를 주의 깊게 봐보자.

바로 SELECT를 한 후에 ORDER BY, LIMIT를 하고 있다. 

예를들어 100만 건의 데이터가 있 다고 하고, 30만 건부터 10개를 조회한다고 해보자.

OFFSET는 30만이고 LIMIT는 10이 될 것이다. 여기서 OFFSET의 한 가지 문제가 있다. 바로 30만 건까지 전부 조회를 한 후 10건이 조회가 되는 구조여서 300010건의 데이터의 접근을 해야 하는 것이다.

페이지 수가 적을 경우에는 큰 문제가 없지만 높은 페이지를 조회할 때는 치명적인 문제인 것이다. 

 

하지만 이를 커버링 인덱스를 통해 해결할 수 있다. 

 

여기서 문제가 되는 것은 30만 건의 데이터를 접근하는 랜덤 액세스(Random Access)가 일어난다는 것이다. 랜덤 액세스는 데이터를 저장하는 블록을 한 번에 하나의 블록만을 액세스 하는 방식이다. 이러한 랜덤 엑세스가 많으면 많을수록 부하가 커질 수밖에 없다. 

여기서 커버링 인덱스를 쓰면 300010건의 랜덤 엑세스를 무려 10건의 랜덤 엑세스로 줄일 수가 있다..!

말도 안 되는 성능 향상이다. 도대체 어떻게 줄일 수 있는 걸까?

30만 건의 조회를 확 줄이는 것이니까 말이다. 이는 인덱스의 원리를 이해해야 한다. 

 

아래의 그림을 봐보자.

 

왼쪽의 표는 Content를 기반으로 생성한 인덱스, 오른쪽의 표는 테이블 생성 시 기본적으로 생성되는 Primary Index이다. 클러스터 인덱스라고도 하며 데이터의 위치를 결정하는 키 값이라고도 할 수 있다.

content 인덱스를 통해 Id를 알고 Id를 통해 '데이터 주소로 가서 데이터를 조회하는 것'이 바로 랜덤 엑세스이다. 

어떤 인덱스를 설정해도 데이터를 찾기 위해서는 이러한 랜덤 엑세스가 일어난다.

 

하지만 여기서 이런 생각을 가질 수 있다. 

 

id만 필요하다면?

필요한 값들을 인덱스 값으로 미리 설정해 놓는다면? 

 

만약 이럴 경우 데이터 주소까지 가지 않아도 되기 때문에 랜덤 엑세스가 일어나지 않는다!

이와 같이 쿼리를 충족하는 모든 값들을 가지고 있는 인덱스가 커버링 인덱스이다. 이제 이러한 커버링 인덱스를 활용할 것이다.

offset과 limit를 쓰면 300010 건의 데이터에 접근하고 10개를 조회한다. 우리가 필요한 것은 10건의 데이터에만 접근하는 것이다. 

300010만 건을 조회하는 offset과 limit를 할 때에는 구체적인 데이터가 필요하지 않다.

오직 10건만이 데이터의 접근이 필요하다!

즉, offset과 limit는 id만을 조회토록 하고 이러한 id값을 본래의 테이블과 join 하여 10건의 데이터만을 접근하면 되는 것이다!

아래의 코드들을 봐보자.

 

이전

SELECT id, title, content, create_date as createDate, member_id as memberId
    FROM article WHERE member_id = #{memberId}
    ORDER BY ${orderBy}
    limit #{size} offset #{offset}

 

이후

SELECT id, title, content, create_date as createDate, member_id as memberId
    FROM (select id from article WHERE member_id = {memberId}
    ORDER BY ${orderBy} limit {size} offset {offset}) b
    join article a on a.id = b.id;

 

 

위와 같이 offset과 limit로 id를 먼저 조회한 다음 이를 본래의 테이블과 join하여 원하는 값들을 찾을 수가 있다. 

그렇다면 과연 성능은 어떻게 향상되었을까?

 

 

적용 전

 

 

적용 후

 

 

확실한 차이다!

실행계획에서 접근하는 rows 수나 api를 통한 시간의 감축만 봐도 커버링 인덱스를 활용하여 성능 향상을 이룬 것을 알 수 있다.

 

 

결론

 

이 글에서는 mybatis, jpa를 통해 Page 객체를 활용한 페이지 처리와 커버링 인덱스를 활용하여 성능 향상에 대해 알아보았다. Page 객체를 써본 소감은 확실히 편리한 메서드가 많이 있기에 여러 메서드를 구현하는 시간, 공간에 절약은 있을 것이라고 생각한다. 특히 Spring Data Jpa를 통해 구현할 때는 매우 편리할 것 같다. 하지만 아직은 필요한 부분만을 커스텀하여 사용하는 방법이 나한테는 익숙하며 직접 구현하는 것이 변수나 메서드를 파악할 수가 있기에 더 마음이 놓일 것 같다.

인덱스에 대해서는 많이 공부를 했다고 느꼈지만 커버링 인덱스를 활용하여 데이터 접근을 확연히 줄인 것을 보면서, 이렇게도 성능 향상을 할 수 있다는 것을 알게되고, 아직 갈 길이 멀다고 생각하였다.

쿼리 실행 순서, 인덱스 등 성능 향상을 꾀할 수 있는 길은 많고, 상황에 따라 튜닝을 하기 위해서는 기본적인 기능들과 여러 연습을 통해 숙달해야겠다고 생각한다. 

 

이만 글을 마치겠다. 잘못된 내용이나 궁금한 내용은 언제든지 댓글로 남겨주길 바랍니다.

 

 

 

참고자료

https://docs.spring.io/spring-data/commons/docs/1.9.1.RELEASE/api/index.html? org/springframework/data/domain/Sort.Order.html 

 

Spring Data Core 1.9.1.RELEASE API

 

docs.spring.io

 

https://stackoverflow.com/questions/15777638/case-insensitive-sort-using-spring-data

 

case insensitive sort using spring data

how can I do case insensitive sorting using Spring-data Pageable? I have this method in my Repository public interface ItemRepository extends QueryDslPredicateExecutor<Item>{ @Query("S...

stackoverflow.com

 

https://wearegolden.tistory.com/entry/SPRINGJPA-PagingPagination-Pageable-%EA%B0%9D%EC%B2%B4-%EB%B0%9B%EC%95%84-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0

 

[SPRING][JPA] Paging/Pagination - Pageable 객체 받아 사용하기

더보기 https://tecoble.techcourse.co.kr/post/2021-08-15-pageable/ Pageable을 이용한 Pagination을 처리하는 다양한 방법 Spring Data JPA에서 Pageable 를 활용한 Pagination 의 개념과 방법을 알아본다. tecoble.techcourse.co.kr ht

wearegolden.tistory.com

 

https://ojt90902.tistory.com/716

 

JPA : Pageable 객체를 이용한 페이징

이 포스팅은 인프런 영한님의 강의를 복습하며 작성한 글입니다. 페이징 조건 검색조건 : 나이가 10살 정렬 조건 : 이름으로 내림차순 페이징 조건 : 첫번째 페이지, 페이지당 보여줄 데이터는 3

ojt90902.tistory.com

 

https://qpdh.tistory.com/211

 

[Spring Data JPA] - 정렬과 페이징 처리

정렬 처리단순 오름차 내림차 정렬// Acs : 오름차순, Desc : 내림차순 List findByNameOrderByNumberAsc(String name); // name인 Product를 찾되, Number기준으로 오름차순 정렬한다. List findByNameOrderByNumberDesc(String name)

qpdh.tistory.com

 

https://velog.io/@boo105/%EC%BB%A4%EB%B2%84%EB%A7%81-%EC%9D%B8%EB%8D%B1%EC%8A%A4

 

커버링 인덱스

데이터베이스, 커버링 인덱스, SQL, 페이징

velog.io