| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | 31 |
Tags
- Springsecurity
- D2Coding
- Node.js 설치
- 증감 연산자
- if else
- @PreAuthorize("isAuthenticated()")
- SQL import
- StringBuilder
- JAVA 변수
- SpringSecurity 로그아웃
- if else if
- SpringBoot
- 별찍기
- StringBuffer
- 클래스 형변환
- 스프링시큐리티 로그아웃
- 스프링부트 로그인
- 중첩 if
- SQL dump
- 이클립스 설치
- 인텔리제이 Web 애플리케이션
- MySQL workbench dump
- System클래스
- Scanner 시간구하기
- JSP 실습
- 회원정보 수정
- 중첩for
- jdk 설정
- SpringSecurity 로그인
- 접근제어자
Archives
- Today
- Total
gi_dor
다시 돌아온 페이징처리 SpringBoot + MyBatis // Pageable XX 본문
RentCompleteSearchDTO 이름을 RentSearchConditionDTO로 변경해야하는데 수정할곳이 많아서 그냥두기로함
1. 검색 조건을 담을 DTO
HTML 에서 Controller 로 검색 조건들을 전달하기 위해 필요하다
@Getter
@Setter
public class RentCompleteSearchDTO {
private String sggCd; // 시군구 코드
private String umdNm; // 법정동 이름
private String name; // 건물명
private Integer minDeposit; // 최소 보증금
private Integer maxDeposit; // 최대 보증금
private Integer minRent; // 최소 월세
private Integer maxRent; // 최대 월세
}
2. 페이징 계산 처리해줄 DTO
총 데이터 갯수와 , 현재 페이지 번호를 알려주면 SQL 쿼리와 UI 에 필요한 모든 값 ( offset , beginPage, endPage 등등)
자동 계산해준다
package com.realestate.rent_insight.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class RentPaginationDTO {
// 페이징 계산 담당일찐
// 입력 받는 값
private int totalRows; // DB내의 전체 데이터 -- 쿼리타면 여기에 갯수 들어감
private int currentPage; // 사용자가 요청한 페이지 번호 -- URL의 ?page=1
// 기본 설정 값
private int rows = 30; // 페이지당 계약건수 갯수
private int pages = 5; // 페이지네이션 바에 보여줄 페이지 갯수 (1 2 3 4 5)
// 계산되어야하는 값
private int totalPages;
private int totalBlocks;
private int currentBlock; // 현재 페이지가 속한 블록 번호
private int beginPage; // 현재 블록 시작 페이지
private int endPage; // 현재 블록 끝 페이지
private int offset; // SQL 쿼리에 사용할 offset 값
// 현재 첫번째 페이지 ,마지막 페이지 구분
private boolean isFirst;
private boolean isLast;
public RentPaginationDTO() {
}
public RentPaginationDTO(int currentPage, int totalRows) {
this.currentPage = currentPage;
this.totalRows = totalRows;
// 객체가 생성되면서 모든 페이징 값 계산 해버리기
init();
}
private void init() {
// 글이 존재하는지
if (totalRows > 0) {
// 전체 페이지 갯수 계산하기
totalPages = (int) Math.ceil((double) totalRows / rows); // 3000개 데이터 / 30행 = totalPages= 100개
// (현재페이지 번호 -1) * 한 페이지당 보여줄 데이터
offset = (currentPage - 1) * rows;
// 페이징 바 계산기
totalBlocks = (int)Math.ceil((double) totalPages / pages);
currentBlock = (int) Math.ceil((double) currentPage / pages);
beginPage = (currentBlock - 1) * pages + 1;
endPage = currentBlock * pages;
// 마지막 블럭의 끝 페이지 번호 - 총 12페이지일 때 3번째 블록(11, 12)의 끝 페이지는 15가 아니라 12가 되어야 함
if (currentBlock == totalBlocks) {
endPage = totalPages;
}
// 혹시 모를 상황 대비: endPage가 totalPages를 넘지 않도록
if (endPage > totalPages) {
endPage = totalPages;
}
isFirst = (currentPage == 1);
isLast = (currentPage == totalPages);
}
}
}
3. 최종결과를 담아줄 DTO
화면으로 보낼 결과물은 데이터목록 + 페이징 정보 두가지다
이것을 처리할 DTO
package com.realestate.rent_insight.dto;
import lombok.Getter;
import java.util.List;
@Getter
public class RentPaginationResultDTO<T> {
private List<T> list;
private RentPaginationDTO rentPaginationDTO; // init() 계산끝난 페이징 정보객체
public RentPaginationResultDTO(List<T> list, RentPaginationDTO rentPaginationDTO) {
this.list = list;
this.rentPaginationDTO = rentPaginationDTO;
}
}
4. 데이터베이스에 실제 SQL 명력을 내릴 Mapper
@Param : 여러 개의 파라미터를 XML에 전달할 때 각각의 파라미터에 이름표 를 붙여준다
package com.realestate.rent_insight.domain.mapper;
import com.realestate.rent_insight.domain.entity.RentComplete;
import com.realestate.rent_insight.dto.RentCompleteSearchDTO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* resources/mappers/RentCompleteMapper.xml 파일에 정의된 SQL 쿼리 연결
*/
@Mapper
public interface RentCompleteMapper {
List<RentComplete> findByComplexConditions(
@Param("searchDto") RentCompleteSearchDTO searchDto,
@Param("limit") int limit,
@Param("offset") int offset);
int countByComplexConditions(@Param("searchDto") RentCompleteSearchDTO searchDto);
}
5. 실제 실행될 SQL 쿼리 작성
- <sql> 태그 를 사용하여 중복되는 WHERE 절 분리 (사실 같은거 길어서 꼴보기 싫었음)
- <if> 태그 검색 조건 (searchDto) 에 값이 있을때만 AND 조건 동적으로 추가한다
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.realestate.rent_insight.domain.mapper.RentCompleteMapper">
<select id="findByComplexConditions"
resultType="com.realestate.rent_insight.domain.entity.RentComplete">
SELECT
id, -- 계약 고유번호
contract_date AS contractDate, -- 계약일
sgg_cd AS sggCd, -- 시군구 코드
sgg_nm AS sggNm, -- 시군구 이름
umd_nm AS umdNm, -- 법정동 이름
jibun, -- 지번 주소
name, -- 오피스텔 단지명
deposit, -- 보증금
monthly_rent AS monthlyRent, -- 월세
area, -- 전용 면적
build_year AS buildYear, -- 건축 년도
floor, -- 층수
contract_term AS contractTerm, -- 계약 기간
contract_type AS contractType -- 계약 종류 (신규/갱신)
FROM
rent_complete
<include refid="whereCondition"></include>
ORDER BY contract_date DESC
LIMIT #{limit} OFFSET #{offset}
</select>
<sql id="whereCondition">
<where>
<if test="searchDto.sggCd != null and searchDto.sggCd != ''">
AND sgg_cd = SUBSTRING(#{searchDto.sggCd}, 1, 5)
</if>
<if test="searchDto.umdNm != null and searchDto.umdNm != ''">
AND umd_nm = #{searchDto.umdNm}
</if>
<if test="searchDto.name != null and searchDto.name != ''">
AND name LIKE CONCAT('%', #{searchDto.name}, '%')
</if>
<if test="searchDto.minDeposit != null and searchDto.maxDeposit != null">
AND deposit BETWEEN #{searchDto.minDeposit} AND #{searchDto.maxDeposit}
</if>
<if test="searchDto.minRent != null and searchDto.maxRent != null">
AND monthly_rent BETWEEN #{searchDto.minRent} AND #{searchDto.maxRent}
</if>
</where>
</sql>
<select id="countByComplexConditions" resultType="int">
SELECT COUNT(*)
FROM rent_complete
<include refid="whereCondition" />
</select>
</mapper>
6. 비즈니스 로직 처리 - 페이징 처리 모든 과정을 순서대로 사용한다고 보면된다
package com.realestate.rent_insight.service;
import com.realestate.rent_insight.domain.entity.RentComplete;
import com.realestate.rent_insight.domain.mapper.RentCompleteMapper;
import com.realestate.rent_insight.dto.RentCompleteSearchDTO;
import com.realestate.rent_insight.dto.RentPaginationDTO;
import com.realestate.rent_insight.dto.RentPaginationResultDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 전월세 계약 정보를 검색기능
* MyBatis 매퍼를 사용하여 복합적인 조건의 검색
*/
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true) // 기본적으로 읽기 전용 트랜잭션으로 실행
public class RentCompleteSearchService {
private final RentCompleteMapper rentCompleteMapper;
public RentPaginationResultDTO<RentComplete> searchRent(RentCompleteSearchDTO rentCompleteSearchDTO, int page) {
int totalRows = rentCompleteMapper.countByComplexConditions(rentCompleteSearchDTO);
RentPaginationDTO rentPaginationDTO = new RentPaginationDTO(page,totalRows);
List<RentComplete> list = rentCompleteMapper.findByComplexConditions(
rentCompleteSearchDTO,
rentPaginationDTO.getRows(),
rentPaginationDTO.getOffset()
);
return new RentPaginationResultDTO<>(list, rentPaginationDTO);
}
}
7. 웹에서 사용자가 요청을 주면 , 서비스에게 일시키고 그 결과를 화면에 전달만 하는 컨트롤러
최종결과를 Model 에 담아 HTML 파일에 보냄
package com.realestate.rent_insight.controller;
import com.realestate.rent_insight.domain.entity.DataUpdateLog;
import com.realestate.rent_insight.domain.entity.Region;
import com.realestate.rent_insight.domain.entity.RentComplete;
import com.realestate.rent_insight.domain.repository.DataUpdateLogRepository;
import com.realestate.rent_insight.dto.RentCompleteSearchDTO;
import com.realestate.rent_insight.dto.RentPaginationResultDTO;
import com.realestate.rent_insight.service.RegionService;//import com.realestate.rent_insight.service.RentSearchService;
import com.realestate.rent_insight.service.RentCompleteSearchService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
import java.util.Optional;
@Controller
@RequiredArgsConstructor
@RequestMapping("/rent")
public class RentSearchController {
private final RentCompleteSearchService rentCompleteSearchService;
private final RegionService regionService;
private final DataUpdateLogRepository dataUpdateLogRepository;
@GetMapping("/search")
public String searchForm(@ModelAttribute("searchDto") RentCompleteSearchDTO rentCompleteSearchDTO,
@RequestParam(value ="page" , defaultValue = "1") int page,
Model model) {
// 1. RegionService를 사용하여 시군구 목록(코드, 이름 포함)을 조회
List<Region> sigunguList = regionService.getSigunguList();
model.addAttribute("sigunguList", sigunguList);
// 2. 서비스 계층을 호출하여 검색 조건에 맞는 데이터를 조회
RentPaginationResultDTO rentSearchResult = rentCompleteSearchService.searchRent(rentCompleteSearchDTO,page);
model.addAttribute("rentSearchResult", rentSearchResult);
// 3. SUCCESS 로그 조회
Optional<DataUpdateLog> lastLog = dataUpdateLogRepository.findFirstByStatusOrderByIdDesc("SUCCESS");
if(lastLog.isPresent()) {
DataUpdateLog log = lastLog.get();
model.addAttribute("lastLog", log.getCompletionTime());
}
// 4. 뷰
return "rent/search";
}
}
<!-- 페이지네이션 UI -->
<div class="card-footer bg-white" th:if="${rentSearchResult.rentPaginationDTO.totalRows > 0}">
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center mb-0">
<!-- '처음' 버튼 -->
<li class="page-item" th:classappend="${rentSearchResult.rentPaginationDTO.first} ? 'disabled'">
<a th:if="${!rentSearchResult.rentPaginationDTO.first}" class="page-link" th:href="@{/rent/search(page=1, sggCd=${searchDto.sggCd}, umdNm=${searchDto.umdNm}, name=${searchDto.name}, minDeposit=${searchDto.minDeposit}, maxDeposit=${searchDto.maxDeposit}, minRent=${searchDto.minRent}, maxRent=${searchDto.maxRent})}">«</a>
<span th:if="${rentSearchResult.rentPaginationDTO.first}" class="page-link">«</span>
</li>
<!-- '이전' 버튼 -->
<li class="page-item" th:classappend="${rentSearchResult.rentPaginationDTO.currentPage == 1} ? 'disabled'">
<a th:if="${rentSearchResult.rentPaginationDTO.currentPage > 1}" class="page-link" th:href="@{/rent/search(page=${rentSearchResult.rentPaginationDTO.currentPage - 1}, sggCd=${searchDto.sggCd}, umdNm=${searchDto.umdNm}, name=${searchDto.name}, minDeposit=${searchDto.minDeposit}, maxDeposit=${searchDto.maxDeposit}, minRent=${searchDto.minRent}, maxRent=${searchDto.maxRent})}"><</a>
<span th:if="${rentSearchResult.rentPaginationDTO.currentPage == 1}" class="page-link"><</span>
</li>
<!-- 페이지 번호 버튼 -->
<li th:each="pageNumber : ${#numbers.sequence(rentSearchResult.rentPaginationDTO.beginPage, rentSearchResult.rentPaginationDTO.endPage)}" class="page-item" th:classappend="${pageNumber == rentSearchResult.rentPaginationDTO.currentPage} ? 'active'">
<a class="page-link" th:href="@{/rent/search(page=${pageNumber}, sggCd=${searchDto.sggCd}, umdNm=${searchDto.umdNm}, name=${searchDto.name}, minDeposit=${searchDto.minDeposit}, maxDeposit=${searchDto.maxDeposit}, minRent=${searchDto.minRent}, maxRent=${searchDto.maxRent})}" th:text="${pageNumber}">1</a>
</li>
<!-- '다음' 버튼 -->
<li class="page-item" th:classappend="${rentSearchResult.rentPaginationDTO.currentPage == rentSearchResult.rentPaginationDTO.totalPages} ? 'disabled'">
<a th:if="${rentSearchResult.rentPaginationDTO.currentPage < rentSearchResult.rentPaginationDTO.totalPages}" class="page-link" th:href="@{/rent/search(page=${rentSearchResult.rentPaginationDTO.currentPage + 1}, sggCd=${searchDto.sggCd}, umdNm=${searchDto.umdNm}, name=${searchDto.name}, minDeposit=${searchDto.minDeposit}, maxDeposit=${searchDto.maxDeposit}, minRent=${searchDto.minRent}, maxRent=${searchDto.maxRent})}">></a>
<span th:if="${rentSearchResult.rentPaginationDTO.currentPage == rentSearchResult.rentPaginationDTO.totalPages}" class="page-link">></span>
</li>
<!-- '마지막' 버튼 -->
<li class="page-item" th:classappend="${rentSearchResult.rentPaginationDTO.last} ? 'disabled'">
<a th:if="${!rentSearchResult.rentPaginationDTO.last}" class="page-link" th:href="@{/rent/search(page=${rentSearchResult.rentPaginationDTO.totalPages}, sggCd=${searchDto.sggCd}, umdNm=${searchDto.umdNm}, name=${searchDto.name}, minDeposit=${searchDto.minDeposit}, maxDeposit=${searchDto.maxDeposit}, minRent=${searchDto.minRent}, maxRent=${searchDto.maxRent})}">»</a>
<span th:if="${rentSearchResult.rentPaginationDTO.last}" class="page-link">»</span>
</li>
</ul>
</nav>
</div>
728x90
'Back_End > SpringBoot' 카테고리의 다른 글
| RentInsight _ Index 로 조회 성능 올리기 , MySql Profiling으로 병목 구간 측정 (5) | 2026.02.25 |
|---|---|
| [Jackson] API 필드명 @JsonProperty .araboja (1) | 2026.02.21 |
| Spring에서 비동기 @Async (0) | 2024.08.29 |
| [Refactor] 카페인 캐싱으로 성능개선 , Ngrinder (1) | 2024.06.11 |
| 스프링부트 + MyBatis +MYSQL 페이징 처리 (0) | 2024.05.09 |
