본문 바로가기
프로젝트/게시판

[SpringBoot] 게시판 4. 게시글 조회 기능 +페이징 처리 - 비동기 구현

by qkzkdo 2023. 8. 2.
728x90

구현 순서

1. index.html

2. controller

3. service 인터페이스

4. service 인터페이스를 구현한 클래스

5. mapper 인터페이스

6. mapper.xml

7. board-list.html

8. 게시글 조회 구현 화면

 

 

1. index.html

  1. 비동기 처리를 위한 ajax 코드를 작성
  2. 게시글 조회 섹션 작성
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"> 
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script type="text/javascript">
    $(function(){
        boardList();
    });

    function boardList(){
        $.ajax({ // 비동기 처리를 위한 AJAX 호출
            url: "/board-list", //Controller의 GET요청 메서드와 url 매핑
            type: "GET", //GET요청 => 조회
            success:function(result){ //서버로부터 데이터를 성공적으로 받았을 때의 처리
                $("#board-list").html(result); // id="board-list"에 받아온 결과값을 보여줌
            }
        });
    };
</script>
</head>
<body>
    <h3>비동기 게시판</h3>
    <div>
        <section>
            <div id="board-list">
                <!-- 비동기 게시글 조회 -->
            </div>
        </section>
    </div>
</body>
</html>
  • function boardList() : jQuery를 사용하여 비동기 방식으로 서버의 /board-list URL에 GET 요청을 보내고, 서버로부터 받아온 결과를 board-list.html 파일에 넣어서 결과를 보여주는 함수
  • boardList() 함수가 호출되면 서버로부터 게시판 목록을 가져와서 해당 데이터를 HTML 요소(#board-list)에 출력 함

 

 

2. controller

GET메서드 URL과 매핑할 코드 작성

@RequiredArgsConstructor  //생성자 주입을 위한 어노테이션
@Controller
public class BoardController {

    private final BoardService service; //@RequiredArgsConstructor: final을 사용해서 의존주입을 할 수 있다

    //게시글 조회 + 페이징
    @ResponseBody
    @GetMapping("/board-list") //비동기로 요청한 /board-list url 매핑
    public ModelAndView boardList(@RequestParam(defaultValue = "1") int page) { 
        return service.boardListProcess(page); //서비스 인터페이스에서 boardListProcess 호출
    }
}
  • @RequestParam(defaultValue = "1"): 페이징 처리를 위해 기본값이 1인 변수를 매개변수로 넣어줌
  • @RequestParam: HTTP 요청의 파라미터 값을 컨트롤러 메서드의 인자로 바인딩할 수 있다.
  • 비동기 처리시 조회값을 리턴하려면 ModelAndView를 반환타입으로 해야함(ModelAndView 형태로 반환)

 

 

3. service 인터페이스

컨트롤러와 서비스 구현 사이의 결합도를 낮춰주기 위해 인터페이스 생성

public interface BoardService {

	ModelAndView boardListProcess(int page);

}
  • 순서 : 컨트롤러 -> 서비스인터페이스 -> 서비스인터페이스구현
  • 인터페이스를 사용함으로써 컨트롤러와 서비스 간의 의존성을 낮추고 코드의 유연성과 확장성을 높일 수 있다.

 

 

4. service 인터페이스를 구현한 클래스

@RequiredArgsConstructor //생성자 주입을 위한 어노테이션
@Service
public class BoardServiceProcess implements BoardService {

    private final BoardMapper mapper;
	
    //게시글 조회 + 페이징
    @Override
    public ModelAndView boardListProcess(int page) { //BoardService 인터페이스의 boardListProcess 구현
        if(page<1) page = 1; //페이지 기본 값

        int limit = 5; //페이지당 게시글 수 
        int offset = (page-1)*limit; //게시글 어디부터 보여줄건지

        ModelAndView mv = new ModelAndView("board/board-list"); //값을 가져갈 html파일 위치를 지정한 생성자 호출
        List<BoardDTO> list = mapper.findAll(limit, offset); //findAll메서드에 limit, offset 값을 가져감
        mv.addObject("list", list); //뷰에 전달할 값을 "list"이름으로 mv에 담아서 뷰로 가져감

        int rowCount = mapper.countAll(); // 총 게시글 수
        int tot = rowCount/limit; //총 페이지 수
        if(rowCount % limit > 0) tot++; //나머지가 존재하면 1 증가

        int RANGE = 5; //한 페이지에 보여질 페이지 번호 개수
        int rangeNo = page/RANGE; //출력되는 페이지 번호 범위를 나타냄
        if(page % RANGE > 0)rangeNo++; // 나머지 페이지가 있으면 페이지 번호를 증가시킨다

        int to = RANGE * rangeNo; //한 페이지의 마지막 번호 
        int from = to - RANGE+1; //한 페이지의 시작 번호
        //1~5 6~10 11~15 16~20
        if(to > tot) to = tot; 

        mv.addObject("from", from); //한 페이지의 시작 번호를 "from" 이름으로 mv에 저장
        mv.addObject("to", to); //한 페이지의 마지막 번호를 "to" 이름으로 mv에 저장
        mv.addObject("tot", tot); //총 페이지 개수를 "tot" 이름으로 mv에 저장

        return mv;
    }

}

 

 

5. mapper 인터페이스

limit과 offset 매개변수가 XML 파일에서 동일한 이름으로 사용될 수 있도록 findAll()매서드 선언

@Mapper
public interface BoardMapper {
	
    //게시글 입력
    void boardSave(BoardDTO dto);

    //게시글 조회
    //board-mapper.xml과 매핑할 메서드 선언
    List<BoardDTO> findAll(@Param("limit") int limit, @Param("offset") int offset);

    //전체 게시글 수
    //xml에 파일에 작성하는대신 어노테이션을 사용해서 쿼리 작성
    @Select("select count(*) from board")
    int countAll();

}
  • @Param: 매개변수에 이름을 지정하여 XML 파일에서 해당 이름으로 매핑된 값을 사용할 수 있도록 해준다

 

 

6. mapper xml

<mapper namespace="com.moo.project.config.mapper.BoardMapper">

    <!-- 게시글 입력 -->
    <insert id="boardSave">
        insert into board(title, content) values(#{title}, #{content});
    </insert>

    <!-- 게시글 조회 -->
    <select id="findAll" resultType="com.pha.project.dto.BoardDTO">
        select * from board order by no desc limit #{limit} offset #{offset}
    </select>
	
</mapper>
  • id 값은 mapper인터페이스의 메서드 이름과 같아야 함
  • resultType: 실제로 조회된 데이터를 는 board 테이블을 객체화한 DTO클래스로 지정하여 매핑
  • limit: 한 페이지에 표시될 게시글 개수
  • offset: 조회를 시작할 위치(시작 게시글의 인덱스)를 의미

 

실제 디비에서의 쿼리

select * from board order by no desc limit 5 offset 0;

  • 조회한 게시글 인덱스 0번부터 5개까지만 데이터를 불러온다

 

 

7. board-list.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
    <a href="/">HOME</a>

    <table border="1">
        <tr>
            <td>글번호</td>
            <td>제목</td>
            <td>조회수</td>
            <td>수정일</td>
        </tr>
        <tr th:each="dto : ${list}">
            <td th:text="${dto.no}">글번호</td>
            <td th:text="${dto.title}">제목</td>
            <td th:text="${dto.readCount}">조회수</td>
            <td th:text="${#temporals.format(dto.updateDate, 'yyyy-MM-dd HH:mm:ss')}">수정일</td>
        </tr>
        <tr th:if="${#lists.isEmpty(list)}">
            <th colspan="4">게시글이 존재하지 않습니다.</th>
        </tr>
    </table>


    <div th:unless="${#lists.isEmpty(list)}" style="display: flex">
        <div th:if="${from gt 1}"><!-- ${from}이 1보다 큰 경우에만 이전 페이지 링크가 표시 -->
            <a th:href="|/board-list?page=${from-1}|">&lt;</a>
            <!-- 이전 페이지 링크를 클릭하면 현재 페이지 번호(from)보다 1 작은 페이지로 이동하도록 설정 -->
        </div>

        <div>
            <!-- ${from}부터 ${to}까지의 범위에 대해 반복하여 페이지 번호를 출력 -->
            <span th:each="no : ${#numbers.sequence(from, to)}">
                <a th:href="|/board-list?page=${no}|" th:text="${no}"></a>
                <!-- ?page=${no} 값이 @RequestParam(defaultValue = "1") int page 값과 매칭 -->
            </span>
        </div>

        <div th:if="${to lt tot}"><!-- ${to}가 ${tot}보다 작은 경우에만 다음 페이지 링크가 표시 -->
            <a th:href="|/board-list?page=${to+1}|">&gt;</a>
            <!-- 다음 페이지 링크를 클릭하면 현재 페이지 번호(to)보다 1 큰 페이지로 이동하도록 설정 -->
        </div>
    </div>
</body>
</html>
  • <html xmlns:th="http://www.thymeleaf.org"> : Thymeleaf 속성을 사용하기 위해서 Thymeleaf의 네임스페이스를 선언
  • th:each="dto : ${list}" : Thymeleaf의 th:each 속성을 사용하여 list에 있는 데이터를 순회하면서 테이블의 각 행을 만들어 줌
  • ${}는 Thymeleaf의 표현식
  • ${}를 사용하면 템플릿 코드에서 데이터를 참조하거나 표현할 수 있음
  • th:text="${dto.no}" : Thymeleaf의 속성 중 하나로, HTML 태그의 텍스트를 동적으로 설정하는 데 사용됨
  • #lists.isEmpty(list)는 list가 비어있는지를 체크하는 Thymeleaf 표현식

 

 

8. 게시글 조회 구현 화면

 

728x90