Spring Boot – JPA Paging

By | 2023년 2월 7일
Table of Contents

Spring Boot – JPA Paging

Spring Boot 의 JPA Paging 을 위한 방법을 설명합니다.

작동가능한 코드를 목적으로 하지 않고,
기본 원리를 정리하는 것을 목적으로 합니다.

아래 코드는 DB 접속 API 서버와 사용자 요청을 받는 서버가 분리된 것을 가정합니다.

Repository

Page<Entity> 를 리턴받습니다.

public interface CompanyRepositoryCustom {
    Page<Company> search(SearchCompanyRequestDto dto);
}

Service

MapStruct 등을 이용해 DTO 로 변환합니다.

@Service
@RequiredArgsConstructor
public class CompanyService {
    public ResponseEntity<?> search(SearchCompanyRequestDto dto) {
        Page<CompanyDto> dtos = repository.search(dto).map(converter::toDto);
        return ResponseEntity.ok(new ApiResponseWithData(ResponseCode.OK, dtos));
    }
}

Controller

Page<DTO> 를 리턴해 줍니다.

@RestController
@RequiredArgsConstructor
@RequestMapping("/v1/account")
public class CompanyController {
    @PostMapping("/search")
    public ResponseEntity<?> search(@RequestBody SearchCompanyRequestDto dto) {
        return service.search(dto);
    }
}

의존성 추가

DB 접속 API 서버와 사용자 요청을 받는 서버가 분리된 상태라,
의존성을 추가로 넣어 줘야 합니다.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}
@SpringBootApplication(exclude = {
        DataSourceAutoConfiguration.class,
        DataSourceTransactionManagerAutoConfiguration.class,
        HibernateJpaAutoConfiguration.class
})
public class WarehousewebApplication {
    public static void main(String[] args) {
        SpringApplication.run(WarehousewebApplication.class, args);
    }
}

커스텀 매퍼 생성

import org.springframework.data.domain.Page;

public class PageModule extends SimpleModule {
    public PageModule() {
        addDeserializer(Page.class, new PageDeserializer());
    }
}
public class PageDeserializer extends JsonDeserializer<Page<?>> implements ContextualDeserializer {
    private static final String CONTENT = "content";
    private static final String NUMBER = "number";
    private static final String SIZE = "size";
    private static final String TOTAL_ELEMENTS = "totalElements";
    private JavaType valueType;

    @Override
    public Page<?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        final CollectionType valuesListType = ctxt.getTypeFactory().constructCollectionType(List.class, valueType);

        List<?> list = new ArrayList<>();
        int pageNumber = 0;
        int pageSize = 0;
        long total = 0;
        if (p.isExpectedStartObjectToken()) {
            p.nextToken();
            if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
                String propName = p.getCurrentName();
                do {
                    p.nextToken();
                    switch (propName) {
                        case CONTENT:
                            list = ctxt.readValue(p, valuesListType);
                            break;
                        case NUMBER:
                            pageNumber = ctxt.readValue(p, Integer.class);
                            break;
                        case SIZE:
                            pageSize = ctxt.readValue(p, Integer.class);
                            break;
                        case TOTAL_ELEMENTS:
                            total = ctxt.readValue(p, Long.class);
                            break;
                        default:
                            p.skipChildren();
                            break;
                    }
                } while (((propName = p.nextFieldName())) != null);
            } else {
                ctxt.handleUnexpectedToken(handledType(), p);
            }
        } else {
            ctxt.handleUnexpectedToken(handledType(), p);
        }

        return new PageImpl<>(list, PageRequest.of(pageNumber, pageSize), total);
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
        final JavaType wrapperType = ctxt.getContextualType();
        final PageDeserializer deserializer = new PageDeserializer();
        deserializer.valueType = wrapperType.containedType(0);
        return deserializer;
    }
}

Feign Service

@FeignClient(name = "COMPANY-SERVICE",configuration = {FeignClientConfig.class, FeignRetryConfig.class}, url = "${api-server.url}:${api-server.port}")
public interface ApiCompanyService {
    @PostMapping("/v1/account/search")
    ApiResponseWithData search(
            @RequestBody SearchCompanyRequestDto req
    );
}

Service

@Service
@RequiredArgsConstructor
public class CompanyService {

    private final ApiCompanyService apiCompanyService;

    public Page<SearchCompanyResponseDto> search(SearchCompanyRequestDto req) {
        ApiResponseWithData apiResponseWithData = apiCompanyService.search(req);

        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new PageModule());
        mapper.registerModule(new JavaTimeModule());

        return mapper.convertValue(apiResponseWithData.getData(), new TypeReference<>() {});
    }
}

Controller

Page<DTO> 를 변환합니다.

@Controller
public class CompanyIndexController {
    @GetMapping("/admin/company/")
    public String search(HttpServletRequest request, ModelMap model, @ModelAttribute SearchCompanyRequestDto req) {
        Page<SearchCompanyResponseDto> searchCompanyResponseDtoPage = service.search(req);

        model.addAttribute("list", searchCompanyResponseDtoPage.toList());
        model.addAttribute("req", req);
        model.addAttribute("currentPage", req.getPageNo());
        model.addAttribute("maxPage", searchCompanyResponseDtoPage.getTotalPages());

        return "/admin/company";
    }
}

답글 남기기