페이지 처리 설정 구성
package com.example.demo.config;
import com.baomidou.mybatisplus.extension.plugins.MyBatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan(basePackages = "com.example.demo.mapper")
public class MyBatisConfig {
// 페이지 처리 플러그인 등록
@Bean
public MyBatisPlusInterceptor configurePagination(){
MyBatisPlusInterceptor interceptor = new MyBatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
페이지 쿼리 예시
컨트롤러 페이지 요청
@PostMapping("/searchPage")
public IPage<ExperimentData> getPageData(){
Page pageInfo = new Page();
pageInfo.setCurrent(1L);
pageInfo.setSize(2L);
IPage<ExperimentData> result = service.getData(pageInfo);
return result;
}
서비스 계층 로직
@Override
public IPage<ExperimentData> getData(Page pageInfo) {
return mapper.queryData(pageInfo);
}
마이바티스 매퍼 인터페이스
IPage<ExperimentData> queryData(IPage pageInfo);
SQL 매핑 파일
<select id="queryData" resultType="com.example.demo.model.ExperimentData">
select * from experiment_table
</select>
원본 쿼리 실행 흐름
- 자동 생성된 COUNT 쿼리 실행: select count(*) from (select * from experiment_table)
- 결과가 0보다 클 경우 실제 데이터 조회 쿼리 실행
첫 번째 단계: select count(*) from (select * from experiment_table) 두 번째 단계: select * from experiment_table limit xxx
성능 문제 분석
select count(*) from (select * from experiment_table) 쿼리는 전체 컬럼을 스캔하게 되어 성능 저하를 유발합니다. 필요한 컬럼만 선택하거나 인덱스를 활용한 COUNT 쿼리가 최적입니다.
플러그인 소스 분석
public class PaginationInnerInterceptor implements InnerInterceptor {
public boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
// 페이지 객체 추출
IPage<?> page = (IPage)ParameterUtils.findPage(parameter).orElse(null);
if (page != null && page.getSize() > 0 && page.isCountEnabled()) {
// 커스텀 COUNT 쿼리 확인
MappedStatement countMs = this.buildCustomCountStatement(ms, page.getCustomCountMethod());
BoundSql countSql;
// 커스텀 쿼리가 존재할 경우 실행
if (countMs != null) {
countSql = countMs.getBoundSql(parameter);
}
// 기본 쿼리 생성
else {
countMs = this.generateAutoCountStatement(ms);
String countSqlStr = this.optimizeCountQuery(boundSql.getSql());
MPBoundSql mpBoundSql = PluginUtils.createBoundSql(boundSql);
countSql = new BoundSql(countMs.getConfiguration(), countSqlStr, mpBoundSql.parameterMappings(), parameter);
PluginUtils.updateParameters(countSql, mpBoundSql.additionalParameters());
}
CacheKey cacheKey = executor.createCacheKey(countMs, parameter, rowBounds, countSql);
List<Object> result = executor.query(countMs, parameter, rowBounds, resultHandler, cacheKey, countSql);
long total = 0L;
if (!result.isEmpty()) {
total = Long.parseLong(result.get(0).toString());
}
page.setTotal(total);
return this.continueProcessing(page);
} else {
return true;
}
}
......
}
이 로직을 통해 커스텀 COUNT 쿼리 메서드를 지정하면 기본 쿼리 대신 사용할 수 있습니다.
최적화된 쿼리 구현
컨트롤러 로직은 동일합니다
서비스 계층 변경
@Override
public IPage<ExperimentData> getData(Page pageInfo) {
// 커스텀 COUNT 메서드 지정
pageInfo.setCustomCountMethod("countEntries");
return mapper.queryData(pageInfo);
}
매퍼 인터페이스 확장
IPage<ExperimentData> queryData(IPage pageInfo);
Long countEntries(IPage pageInfo);
SQL 매핑 파일 수정
<select id="queryData" resultType="com.example.demo.model.ExperimentData">
select * from experiment_table
</select>
<select id="countEntries" resultType="java.lang.Long">
SELECT COUNT(*) FROM experiment_table
</select>
최적화 후 쿼리 실행 과정
첫 번째 단계: SELECT COUNT(*) FROM experiment_table 두 번째 단계: select * from experiment_table limit xxx
커스텀 COUNT 쿼리를 통해 불필요한 데이터 스캔을 제거하여 성능 향상