[Spring Boot] 18. 페이징

shr·2022년 3월 2일
0

Spring

목록 보기
17/23
post-thumbnail

페이징


  1. src/maim/java - com.example.demo.dto - Page 생성

    package com.example.demo.dto;
    
    import java.util.*;
    
    import lombok.*;
    
    // 페이지 정보를 담고 있는 DTO : 이전 페이지, 시작 페이지, 끝 페이지, 다음 페이지, 글들
    @Data
    @AllArgsConstructor
    public class Page {
        private Integer prev;
        private Integer start;
        private Integer end;
        private Integer next;
        private Integer pageno;
        private List<ProductDto.ReadAll> products;
    }

  1. src/main/java - com.example.demo.dto - ProductDto에 코드 추가
    public class ProductDto {
        // 제품 목록 출력용 DTO
        @Data
        public static class ReadAll {
            private Integer pno;
            private String vendor;
            private String name;
            private Double star;
            private String imagename;
        }

  1. src/main/java - com.example.demo.service - ProductService 수정

    package com.example.demo.service;
    
    import java.io.*;
    import java.util.List;
    import java.util.UUID;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
    import org.springframework.web.multipart.MultipartFile;
    
    import com.example.demo.dao.ProductDao;
    import com.example.demo.dto.*;
    import com.example.demo.dto.ProductDto.Add;
    import com.example.demo.entity.Product;
    
    import lombok.*;
    
    @RequiredArgsConstructor
    @Service
    public class ProductService {
        @Value("${ck.image.folder}")
        private String CKImageFolder;
        @Value("${ck.image.path}")
        private String ckImagepath;
        @Value("${product.image.folder}")
        private String imageFolder;
        @Value("${product.image.path}")
        private String imagePath;
        @Value("${default.image.name}")
        private String defaultImage;
        private final ProductDao productDao;
        public CKResponse ckImageUpload(MultipartFile image){
            if (image != null && image.isEmpty() == false) {
                String imageName = UUID.randomUUID() + "-" + image.getOriginalFilename();
    
                File file = new File(CKImageFolder, imageName);
    
                try {
                    image.transferTo(file);
                } catch (IllegalStateException | IOException e) {
                    e.printStackTrace();
                }
                return new CKResponse(1, imageName, ckImagepath + imageName);
            }
            return null;
        }
    
        public void add(Add dto) {
            Product product = dto.toEntity();
            MultipartFile image = dto.getImagename();
            product.setImagename(defaultImage);
            if (image != null && image.isEmpty() != false && image.getContentType().toLowerCase().startsWith("image/")) {
                String imagename = UUID.randomUUID() + "-" + image.getOriginalFilename();
                File file = new File(imageFolder, imagename);
                try {
                    image.transferTo(file);
                    product.setImagename(imagename);
                } catch (IllegalStateException | IOException e) {
                    e.printStackTrace();
                }
            }
            productDao.save(product);
        }
    
        // ★ 페이징
        public Page readAll(int pageno) {
            // 한 페이지당 상품의 개수는 10개, 블록당 페이지의 개수는 5개
            int PRODUCT_PER_PAGE = 10;
            int PAGE_PER_BLOCK = 5;
            int count = productDao.count(null);
            /*
                글이 123개라고 치면
                pageno		startRownum		endRownum
                1			1				10
                2			11				20
                3			21				30
                13			121				123
             */
            int startRownum = ((pageno-1)*PRODUCT_PER_PAGE) + 1;
            int endRownum = (startRownum + PRODUCT_PER_PAGE) - 1;
            if (count < endRownum)
                endRownum = count;
            List<ProductDto.ReadAll> products = productDao.findAll(startRownum, endRownum);
    
            // 페이지의 개수 -> 글이 119개면 12 페이지, 120개면 12 페이지, 121개면 13 페이지
            int countOfPage = (count/PRODUCT_PER_PAGE) + 1;
            // 글이 120개면 countOfPage는 13이 나온다. PRODUCT_PER_PAGE의 배수인 경우 countOfPage를 1 감소시켜야 한다.
            if (count % PRODUCT_PER_PAGE == 0)
                countOfPage--;
    
            /*
                pageno		blockNo		prev		start		end			next
                1~5			0			0			1			5			6		
                6~10		1			5			6			10			11
                11~13		2			10			11			13			0
             */
            int blockNo = pageno/PAGE_PER_BLOCK;
            // pageno가 5면(PAGE_PER_BLOCK의 배수) 1이 나오게 되므로 따로 처리
            if (pageno % PAGE_PER_BLOCK == 0)
                blockNo--;
    
            int start = blockNo * PAGE_PER_BLOCK + 1;
            int prev = start - 1;
            int end = start + PAGE_PER_BLOCK - 1;
            int next = end + 1;
    
            // blockNo가 2인 경우 end는 15, next는 16이므로 따로 처리
            if (end >= countOfPage) {
                end = countOfPage;
                next = 0;
            }
    
            return new Page(prev, start, end, next, pageno, products);
        }
    }

  1. src/main/java - com.example.demo.dao - ProductDao

    package com.example.demo.dao;
    
    import java.util.List;
    
    import org.apache.ibatis.annotations.*;
    
    import com.example.demo.dto.ProductDto;
    import com.example.demo.entity.*;
    
    @Mapper
    public interface ProductDao {
    
        public void save(Product product);
    
        public int count(String categoryCode);
    
        public List<ProductDto.ReadAll> findAll(int startRownum, int endRownum);
    
    }

  1. src/main/resources - mapper - productMapper.xml

    <?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.example.demo.dao.ProductDao">
    
        <insert id="save" useGeneratedKeys="true" keyProperty="pno">
            <selectKey keyProperty="pno" order="BEFORE" resultType="int">
                select product_seq.nextval from dual
            </selectKey>
            insert into product(pno, vendor, name, info, imagename, price, salesVolume, countOfStar, sumOfStar, countOfReview, stock, categoryCode) 
            values(#{pno}, #{vendor}, #{name}, #{info}, #{imagename}, #{price}, #{salesVolume}, #{countOfStar}, #{sumOfStar}, #{countOfReview}, #{stock}, #{categoryCode})
        </insert>
    
        <select id="count" resultType="int">
            select /*+ index_ffs(product product_pk_pno) */ count(pno) from product
            <where>
                <if test="categoryCode!=null">categoryCode=#{categoryCode}</if>
            </where>
        </select>
    
        <select id="findAll" resultType="com.example.demo.dto.ProductDto$ReadAll">
            <![CDATA[
            select * from
                (select rownum as rnum, newdesc.* from
                    (select /*+ index_desc(product product_pk_pno)*/ pno, vendor, name, nvl(sumOfStar/nullif(countOfStar, 0), 0.0) as star, imagename from product) newdesc
                where rownum<=#{endRownum})
            where rnum>=#{startRownum}
            ]]>
        </select>
    </mapper>

    📝 /*+ INDEX_FFS(테이블명 인덱스명) */
    논리적인 트리 구조를 무시하고, 인덱스를 병렬 방식으로 읽는다. 따라서 Full Scan보다 속도가 빠르다. 쿼리에 사용한 컬럼이 모두 인덱스에 포함되어 있을 때만 사용 가능하다.


    📝 nvl(sumOfStar/nullif(countOfStar, 0), 0.0)
    countOfStar가 0이면 null이 되고, null이 들어간 연산의 결과는 null이므로 nvl 함수를 이용해 0.0으로 바꾼다.

profile
못하다 보면 잘하게 되는 거야 ・ᴗ・̥̥̥

0개의 댓글