티스토리 뷰

본문 설명에 들어가기 앞서 아래와 같이 현재 개발 환경을 참고하시길 바람

 

개발 환경
 - 프레임워크 : Spring boot + JPA
 - 템플릿 엔진 : Thymeleaf + Bootstrap
 - RDBMS : MySQL
 -  IDE : Intellij(Back-end), Visual Studio Code(Front-end)

 

 

위에 개발환경을 설명드린 이유는 Boostrap의 Datatable을 이용하면 페이징을 포함한 다양한 기능을 사용할 수 있음.

 

 

 

제 경우 Bootstrap template을 사용하여 개발해오고 있는데

<tr> 태그를 클릭하면 아래 세부 내용을 보여줄 수 있는 accordion을 사용하기 위해 'colspan'을 사용했음.  

 

 

이후 경우 브라우저 console 창에 아래와 같이 에러가 발생하기 시작했음..

 

관련 내용을 찾아보니 datatable은  table header에는 colspan과 rowspan을 지원하지만, table body에는 지원하지 않음.

 

(원인 찾는데 너무 힘들었음 ㅠㅠ..)

 

 

▶ 지원 내용 참고 url

 

DataTables example - Complex headers (rowspan and colspan)

Complex headers (rowspan and colspan) When using tables to display data, you will often wish to display column information in groups. DataTables fully supports colspan and rowspan in the table's header, assigning the required order listeners to the TH elem

datatables.net

 

 

jQuery DataTables: COLSPAN in table body TBODY | Gyrocode.com

Provides a solution to use COLSPAN attribute for a cell in table body with jQuery DataTables.

www.gyrocode.com

 

 


 

 

 

에러 원인을 파악하고 페이징에 관해 공부할 겸 직접 개발해 보기로 결정!

 

 

 

페이징 기능을 수행하는 방법은 아래와 같이 여러 방법이 있음

1) JQuery를 이용한 방법 
→ 오픈소스로 공유된 코드를 사용
JQuery 방법은 현재 백엔드 스터디용 프로젝트인지라 Pass


2) Spring boot JPA Pageable을 이용하는 방법
→ JpaRepository를 상속받은 후 findAll() 함수에 Pageable를 파라미터로 넣어 사용
Spring boot JPA의 Pageable은 JpaRepository를 상속받아 개발, 현재 EntityManager를 이용해서 모든 쿼리문을 작성하면서 개발 중임으로 Pass

3) JPA(Hibernate)를 이용하는 방법
→ EntityManger 기능 중 setFirstResult와 setMaxResults를 이용하여 DB에서 조회된 데이터 범위를 지정해서 가져올 수 있음

 

구글링을 해봤는데 hibernate를 이용하여 개발하는 사람이 많지 않았음.(대부분 Spring Data JPA 사용함)

 

노력 끝에 한 블로그를 글을 찾았는데 관련 링크는 아래 참고 바람

 

[spring] 페이징 처리하기 (feat. Thymeleaf)

페이징 처리는 웹 개발에서 필수적인 기술이며, 요즘은 무한스크롤로 처리하는 경우도 많다. 무한스크롤 + 페이징을 접목하는 경우도 다수 본 것 같다. (...나중에 써봐야징..) 페이징을 처리하는

pugyu.tistory.com

 

 

 

전체 흐름은 Domain - Pagination을 만들고, 별도 함수를 이용해 setFirstResult(Index)에 들어갈 Index를 뽑아내고, Repository에서 DB조회한 후 view 단에 조회된 게시글을 전달.

 

view단에서 각 페이지 번호를 누르면 Getmapping을 통해 해당 값을 가져와 위의 과정을 반복함.

 

 

이해를 돕기위해 직접 작성한 데이터 흐름도를 첨부하니, 꼭 참고바람.

페이징 데이터 흐름도.pdf
0.66MB

 

현재 개발프로젝트에서는 해당사용자에 해당하는 기안문만 출력하도록 해야 하므로 JPA 쿼리문을 일부 수정했음.

 

 


 

 

 

더하여, 참고용 코드를 첨부함.

Domain - Pagination

더보기
import lombok.Data;

@Data
public class Pagination {

    /**
     * 1. 페이지 당 보여지는 게시글의 최대 개수
     **/
    private int pageSize = 10;

    /**
     * 2. 페이징된 버튼의 블럭당 최대 개수
     **/
    private int blockSize = 10;

    /**
     * 3. 현재 페이지
     **/
    private int page = 1;

    /**
     * 4. 현재 블럭
     **/
    private int block = 1;

    /**
     * 5. 총 게시글 수
     **/
    private int totalListCnt;

    /**
     * 6. 총 페이지 수
     **/
    private int totalPageCnt;

    /**
     * 7. 총 블럭 수
     **/
    private int totalBlockCnt;

    /**
     * 8. 블럭 시작 페이지
     **/
    private int startPage = 1;

    /**
     * 9. 블럭 마지막 페이지
     **/
    private int endPage = 1;

    /**
     * 10. DB 접근 시작 index
     **/
    private int startIndex = 0;

    /**
     * 11. 이전 블럭의 마지막 페이지
     **/
    private int prevBlock;

    /**
     * 12. 다음 블럭의 시작 페이지
     **/
    private int nextBlock;

    public Pagination(int totalListCnt, int page) {

        // 총 게시물 수와 현재 페이지를 Controller로 부터 받아온다.

        // 총 게시물 수	- totalListCnt
        // 현재 페이지	- page


        /** 3. 현재 페이지 **/
        setPage(page);


        /** 5. 총 게시글 수 **/
        setTotalListCnt(totalListCnt);


        /** 6. 총 페이지 수 **/
        // 한 페이지의 최대 개수를 총 게시물 수 * 1.0로 나누어주고 올림 해준다.
        // 총 페이지 수 를 구할 수 있다.
            // Math.ceil(실수) : 올림
            // Math.round(실수) : 반올림
            // Math.floor(실수) : 내림
        setTotalPageCnt((int) Math.ceil(totalListCnt * 1.0 / pageSize));


        /** 7. 총 블럭 수 **/
        // 한 블럭의 최대 개수를 총  페이지의 수 * 1.0로 나누어주고 올림 해준다.
        // 총 블럭 수를 구할 수 있다.
        setTotalBlockCnt((int) Math.ceil(totalPageCnt * 1.0 / blockSize));


        /** 4. 현재 블럭 **/
        // 현재 페이지 * 1.0을 블록의 최대 개수로 나누어주고 올림한다.
        // 현재 블록을 구할 수 있다.
        setBlock((int) Math.ceil((page * 1.0) / blockSize));


        /** 8. 블럭 시작 페이지 **/
        setStartPage((block - 1) * blockSize + 1);


        /** 9. 블럭 마지막 페이지 **/
        setEndPage(startPage + blockSize - 1);


        /* === 블럭 마지막 페이지에 대한 validation ===*/
        if (endPage > totalPageCnt) {
            this.endPage = totalPageCnt;
        }


        /** 11. 이전 블럭(클릭 시, 이전 블럭 마지막 페이지) **/
        setPrevBlock((block * blockSize) - blockSize);


        /* === 이전 블럭에 대한 validation === */
        if (prevBlock < 1) {
            this.prevBlock = 1;
        }


        /** 12. 다음 블럭(클릭 시, 다음 블럭 첫번째 페이지) **/
        setNextBlock((block * blockSize) + 1);


        /* === 다음 블럭에 대한 validation ===*/
        if (nextBlock > totalPageCnt) {
            nextBlock = totalPageCnt;
        }


        /** 10. DB 접근 시작 index **/
        setStartIndex((page - 1) * pageSize);



    }
}

 

 

Controller - HomeController

더보기
@Slf4j
@Controller
@RequiredArgsConstructor
public class HomeController {

    @GetMapping("/home")
    public String homeLogin(HttpServletRequest request, Model model, @RequestParam(defaultValue = "1") int page) throws ParseException {


        HttpSession session = request.getSession(false); //false : 새로 생성 X

        if (session == null) {
            return "/";
        }

        Member loginMember = (Member) session.getAttribute(SessionConst.LOGIN_MEMBER);

        // 세션에 회원데이터가 없으면 로그인창으로
        if (loginMember == null) {
            return "/";
        }

        log.info("loginMember.getEmpNum >> " + loginMember.getEmpNum());

        Long empNum = loginMember.getEmpNum();

        // List<Board> boards = boardService.findAll(empNum);

        // 페이징

        // 1. 총 게시물 수
        int totalListCnt = boardService.findAllCnt(empNum);

        // 2. 생성인자로  총 게시물 수, 현재 페이지를 전달
        Pagination pagination = new Pagination(totalListCnt, page);


        // DB select start index
        int startIndex = pagination.getStartIndex();

        // 페이지 당 보여지는 게시글의 최대 개수
        int pageSize = pagination.getPageSize();

        List<Board> boards = boardService.findAllPaging(empNum, startIndex, pageSize);

        model.addAttribute("pagination", pagination);
        model.addAttribute("boards", boards);
        model.addAttribute("member", loginMember);
        model.addAttribute("newBoard", new Board());

        return "home";
    }
}

 

 

Repository - JpaBoardRepository

더보기
@Slf4j
@Repository
@RequiredArgsConstructor
@Transactional
public class JpaBoardRepository implements BoardRepository {

    private final EntityManager em;

  
    @Override
    public int findAllCnt(Long empNum) {

        Object checkSpot = em.createQuery("select m.spot from Member m where m.empNum = :number")
                .setParameter("number", empNum).getSingleResult();

        Object checkDeptNum = em.createQuery("select m.deptNum from Member m where m.empNum = :number")
                .setParameter("number", empNum).getSingleResult();


        // Number로 cast 후 intvalue를 이용해서 object -> int로 변환
        if(checkSpot.equals("사원")){
            return ((Number) em.createQuery("Select count(b.id) from Board b left join b.member where b.empNum = :number")
                    .setParameter("number", empNum)
                    .getSingleResult()).intValue();
        }else{
            return ((Number) em.createQuery("select count(b.id) from Board b left join b.member where b.empNum = :number or " +
                            "b.approveLevel='부서장' and b.deptNum = :deptNum and b.status in ('결재중','결재완료') ")
                    .setParameter("number", empNum)
                    .setParameter("deptNum", checkDeptNum)
                    .getSingleResult()).intValue();
        }



    }


    @Override
    @Transactional(readOnly = true)
    public List<Board> findAllPaging(Long empNum, int startIndex, int pageSize) {

        String sql;

        Object checkSpot = em.createQuery("select m.spot from Member m where m.empNum = :number")
                .setParameter("number", empNum).getSingleResult();

        Object checkDeptNum = em.createQuery("select m.deptNum from Member m where m.empNum = :number")
                .setParameter("number", empNum).getSingleResult();

        if (checkSpot.equals("사원")) {

            sql = "SELECT b FROM Board b JOIN FETCH b.member WHERE b.empNum = :number ORDER BY b.writeDate DESC ";
            List<Board> resultList = em.createQuery(sql, Board.class)
                    .setParameter("number", empNum)
                    .setFirstResult(startIndex)
                    .setMaxResults(pageSize)
                    .getResultList();

            return resultList;

        } else {
            sql = "select b from Board b join fetch b.member where b.empNum = :number or b.approveLevel='부서장' and b.deptNum = :deptNum and b.status in ('결재중','결재완료') ORDER BY b.writeDate DESC ";
            List<Board> resultList = em.createQuery(sql, Board.class)
                    .setParameter("number", empNum)
                    .setParameter("deptNum", checkDeptNum)
                    .setFirstResult(startIndex)
                    .setMaxResults(pageSize)
                    .getResultList();

            for (Board board : resultList) {
                System.out.println("board.getId() = " + board.getId());
            }
            
            return resultList;
        }


    }
}

 

 

 

home.html

더보기
  <!-- pagination -->

    <nav aria-label="Page navigation example">
        <ul class="pagination justify-content-center">

            <li class="page-item">
                <a class="page-link"  th:href="@{/home?page=1}" aria-label="Previous">처음</a>
            </li>

            <li class="page-item">
                <a class="page-link" th:href="@{/home?page={page} (page = ${pagination.prevBlock})}" aria-label="Previous">
                이전
                </a>
            </li>

            <!-- start of 페이징 버튼 -->

            <!-- thymeleaf 문법 ->>  #numbers.sequence(0,3) : 0~3까지 숫자 생성 -->
            <th:block  th:with="start = ${pagination.startPage}, end = ${pagination.endPage}">
                <li class="page-item" 

                        th:each="pageButton : ${#numbers.sequence(start, end)}">
                        <a class="page-link" th:href="@{/home?page={page} (page = ${pageButton})}" th:text=${pageButton}></a>
                </li>
            </th:block>
            <!-- End of 페이징 버튼 -->

            <li class="page-item">
                <a class="page-link" th:href="@{/home?page={page} (page = ${pagination.nextBlock})}" aria-label="Next">
                다음
                </a>
              </li>

              <li class="page-item">
                <a class="page-link" th:href="@{/home?page={page} (page = ${pagination.totalPageCnt})}" aria-label="Previous">
                끝
                </a>
              </li>

        </ul>
    </nav>

 

 

 


 

 

개발 완료한 모습

 

이상으로 Spring JPA를 이용한 페이징 관련 글을 마무리함.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
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
글 보관함