Spring #2

황현근·2023년 7월 2일
0

Spring

목록 보기
6/6
post-thumbnail

🧷 Client - Web Server - WAS - DB

  • Web server (정적 리소스 처리): HTML, 이미지 등을 처리하며 잘 죽지 않고 오류화면 HTML을 노출 시킬 수 있다.

  • WAS, Web Application Server (동적 리소스 처리): 업무 분담으로 과부하를 줄여주며 애플리케이션 로직을 실행시킨다.

📌 Servlet

: 자바를 사용해 웹 페이지를 동적으로 생성하는 서버측 프로그램 혹은 사양

서블릿은 서버에서 처리해야 하는 수많은 업무 중 비즈니스 로직을 제외한 모든 일을 대신 수행해준다.
이는 싱글톤으로 관리되어 공유 변수에 주의하여야 하며 소스코드는 싱글 스레드 프로그래밍하듯 개발하면 된다.

서블릿은 요청/응답 정보를 편리하게 사용할 수 있도록 HttpServletRequest, HttpServletResponse를 사용한다.

HTTP 요청을 하면 WAS는 request, response 객체를 새로 만들어 서블릿 객체를 호출한다. 그럼 그때 개발자는 request 객체에서 HTTP 요청 정보를 꺼내 쓰고, response 객체에 HTTP 응답 정보를 입력하면 된다.

WAS는 response 내용으로 HTTP 응답 정보를 생성해 브라우저로 전달해준다.


📖 Spring MVC

서블릿으로 개발할 때는 뷰를 위한 HTML 코드가 자바 코드와 섞여 복잡했다.
이를 해결하기 위해 JSP를 사용해 뷰를 생성하는 HTML 작업을 분리시켰고 중간중간 동적인 작업이 필요한 부분만 자바 코드를 적용시켰다.
하지만 이 또한 많은 자바 코드가 JSP에 노출되어 JSP가 너무 많은 역할을 떠맡게 된다.

그래서 비즈니스 로직과 뷰를 그리는 작업을 분리하기 위하여 MVC 패턴을 사용하게 되었다.

JSP, Java Server Pages

: HTML 코드에 자바 코드를 넣어 동적 웹 페이지를 생성하는 웹 어플리케이션 도구

MVC 패턴이란 JSP가 처리하던 것을 컨트롤러와 뷰 영역으로 나눈 것이다.

  • 컨트롤러
    :HTTP 요청을 받아 파라미터를 검증하고 비즈니스 로직을 수행한다. 그리고 뷰에 전달할 결과 데이터를 조회해 모델에 담는다.
  • 모델
    :뷰에 출력할 데이터를 담아둔다. 모델 덕분에 뷰는 화면을 렌더링하는 일에만 집중하면 된다.

  • :모델에 담긴 데이터를 사용해 화면을 그린다. (HTML 생성)

[Controller vs Service]

컨트롤러에 비즈니스 로직을 둘 수는 있지만, 이는 컨트롤러가 너무 많은 역할을 담당하게 된다.

그래서 비즈니스 로직을 Service 서비스 계층을 별도로 생성하여 처리한다.
그리고 Controller 컨트롤러는 비즈니스 로직이 있는 서비스를 호출하는 역할을 담당한다.

비즈니스 로직을 변경하면 비즈니스 로직을 호출하는 컨트롤러의 코드도 함께 변경될 수 있다.

MVC 패턴 동작

📌 Front Controller

초기 MVC 패턴은 컨트롤러의 역할과 뷰의 렌더링 역할을 명확히 구분하여 뷰의 코드가 깔끔하고 직관적이도록 해주었다. 하지만 컨트롤러는 중복이 많아 프론트 컨트롤러(Front Controller) 패턴을 도입하게 되었다.

프론트 컨트롤러는 프론트 컨트롤러라는 서블릿 하나로 클라이언트의 요청을 받은 다음 프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아 호출해주는 역할을 수행한다.

그래서 스프링에서는 DispatcherServlet이 FrontController 패턴으로 구현되어 있어 이를 사용할 수 있다.


Spring MVC 구조

스프링 MVC의 전체 구조를 보면 아래와 같다

  • DispatcherServlet(Front Controller)
    : 제일 앞단에서 HTTP Request를 처리하는 컨트롤러
  • Controller(Handler)
    : HTTP Request를 처리해 Model을 만들고 View 지정
  • ModelAndView
    : Controller에 의해 반환된 Model과 View가 Wrapping 된 객체
    (Model은 데이터만 저장하고 ModelAndView는 데이터와 이동하고자 하는 view page를 함께 저장한다.)
  • ViewResolver
    : ModelAndView를 처리해 View 그리기

[동작 순서]

  1. 핸들러 조회
  2. 핸들러 어댑터 조회
  3. 핸들러 어댑터 실행
  4. 핸들러 실행
  5. ModelAndView 반환
  6. viewResolver 호출
  7. View 반환
  8. 뷰 렌더링

📌 Controller의 @RequestMapping

@RequestMapping는 실무에서 많이 쓰인다.

이는 요청 정보를 매핑하는 어노테이션으로 해당 URL이 호출되면 해당 메소드가 실행된다.

  • @GetMapping
  • @PostMapping
    위와 같이 명확하게 써야한다.

@Controller vs @RestController

@Controller는 반환 값이 String일 때 뷰 이름으로 인식하여 뷰를 찾고 뷰가 랜더링 된다.
반면, @RestController는 반환 값으로 뷰를 찾는 것이 아닌, HTTP 메시지 바디에 바로 입력되어 반환된 문자열을 그대로 노출시킬 수 있다.

Controller 사용 예시 [HomeController]

@Controller
public class HomeController {
	
	// 이 프로젝트의 최상위 경로 요청이 오면
	@RequestMapping("/")
	//생성자에 임의로 객체를 선언할 수 있다.
	public String home(HttpServletRequest request) {
		// home.jsp 페이지에서 필요한 모델(data)를 HttpServletRequest 객체에 담아두기
		List<String> noticeList = new ArrayList<String>();
		noticeList.add("날씨가 많이 더워지고 있어요.");
		noticeList.add("어떡하죠");
		noticeList.add("물을 많이 마셔요");
		request.setAttribute("noticeList", noticeList);
		// /WEB-INF/views/home.jsp 페이지로 forward 이동해서 응답하겠다는 의미
		// "home"이라는 문자열을 리턴하면 앞에 "/WEB-INF/views/"뒤에 ".jsp"가 자동으로 붙는다.
		return "home";
	}
	
	@RequestMapping(value = "/fortune")
	public String fortune(HttpServletRequest request) {
		//오늘의 운세라고 가정하자
		// String fortuneToday = "오늘은 되는게 없어요";
		String fortuneToday = "오늘은 불금불금~~ 저녁에 맥주 한 잔하게 될 거에요";
		// "fortuneToday" 라는 키값으로 String type 데이터를 담는다
		request.setAttribute("fortuneToday", fortuneToday);
		// "/WEB-INF/views/" + "test/fortune" + ".jsp"
		return "test/fortune";
	}
	
}

Controller 사용 예시 [UserController]

@Controller
public class UsersController {
    // 의존 객체 주입 받기(DI)
    @Autowired
    private UsersService service;

    /*
        GET 방식 /users/signup_form 요청을 처리할 메소드
        - 요청방식이 다르면 실행되지 않는다.
     */
    @RequestMapping(method = RequestMethod.GET, value = "/users/signup_form")
    public String signupForm() {

        return "users/signup_form";
    }

    // 회원 가입 요청처리
    @RequestMapping(method = RequestMethod.POST, value = "/users/signup")
    public ModelAndView signup(ModelAndView mView, UsersDto dto) {
        // 서비스를 이용해서 DB 에 저장하고
        service.addUser(dto);
        // view page 로 forward 이동해서 응답
        mView.setViewName("users/signup");
        return mView;
    }

    // 로그인 폼 요청 처리
    @RequestMapping(method = RequestMethod.GET, value = "/users/loginform")
    public String loginform() {
        return "users/loginform";
    }

    // 로그인 요청 처리
    @RequestMapping("/users/login")
    public ModelAndView login(ModelAndView mView, UsersDto dto, String url, HttpSession session) {
        /*
            서비스에서 비즈니스 로직을 처리할 때 필요로 하는 객체를 컨트롤러에서 직접 전달을 해주어야 한다.
            주로, HttpServeltRequest, HttpServletResponse, HttpSession, ModelAndView
            등등의 객체이다.
         */
        service.loginProcess(dto, session);

        // 로그인 후에 가야할 목적지 정보를 인코딩 하지 않는 것과 인코딩한 것을 모두 ModelAndView 객체에 담고
        String encodedUrl = URLEncoder.encode(url);
        mView.addObject("url", url);
        mView.addObject("encodedUrl", encodedUrl);

        // view page 로 forward 이동해서 응답한다.
        mView.setViewName("users/login");
        return mView;
    }

    @RequestMapping("/users/logout")
    public String logout(HttpSession session) {
        // 세션에서 id 라는 키값으로 저장된 값 삭제
        session.removeAttribute("id");
        return "users/logout";
    }

    // 개인정보 보기 요청 처리
    @RequestMapping("/users/info")
    public ModelAndView info(HttpSession session, ModelAndView mView) {
        service.getInfo(session, mView);

        mView.setViewName("users/info");
        return mView;
    }

    // 비밀번호 수정폼 요청 처리
    @RequestMapping("/users/pwd_updateform")
    public String pwdUpdateForm() {
        return "users/pwd_updateform";
    }

    // 비밀번호 수정 요청 처리
    @RequestMapping("/users/pwd_update")
    public ModelAndView pwdUpdate(UsersDto dto, ModelAndView mView, HttpSession session) {
        // 서비스에 필요한 객체의 참조값을 전달해서 비밀번호 수정 로직을 처리한다.
        service.updateUserPwd(session, dto, mView);
        // view page 로 forward 이동해서 작업 결과를 응답한다.
        mView.setViewName("users/pwd_update");
        return mView;
    }

    // 개인정보 수정폼 요청 처리
    @RequestMapping("/users/updateform")
    public ModelAndView updateform(HttpSession session, ModelAndView mView) {
        service.getInfo(session, mView);
        mView.setViewName("users/updateform");
        return mView;
    }

    // ajax 프로필 사진 업로드 요청 처리
    @RequestMapping(value = "/users/profile_upload", method = RequestMethod.POST)
    @ResponseBody
    public Map<String, Object> profileUpload(HttpServletRequest request, MultipartFile image) {
        // 서비스를 이용해서 이미지를 upload 폴더에 저장하고 리턴되는 Map 을 리턴해서 json 문자열 응답하기
        return service.saveProfileImage(request, image);
    }

    @RequestMapping(value = "/users/update", method = RequestMethod.POST)
    public ModelAndView update(UsersDto dto, HttpSession session, ModelAndView mView) {
        // 서비스를 이용해서 개인정보를 수정하고
        service.updateUser(dto, session);
        // 개인정보 보기로 리다일렉트 이동시킨다.
        mView.setViewName("redirect:/users/info");
        return mView;
    }

    // 회원 탈퇴 요청 처리
    @RequestMapping("/users/delete")
    public ModelAndView delete(HttpSession session, ModelAndView mView){
        service.deleteUser(session, mView);
        mView.setViewName("users/delete");
        return mView;
    }
}

Service 사용 예시 [CafeService.interface]

public interface CafeService {
    public void getList(HttpServletRequest request);

    public void getDetail(HttpServletRequest request);

    public void saveContent(CafeDto dto);

    public void updateContent(CafeDto dto);

    public void deleteContent(int num, HttpServletRequest request);

    public void getData(HttpServletRequest request); // 글 수정하기 위해 정보 불러오는 기능

    public void saveComment(HttpServletRequest request); // 댓글 저장

    public void deleteComment(HttpServletRequest request); // 댓글 삭제

    public void updateComment(CafeCommentDto dto); // 댓글 수정

    public void moreCommentList(HttpServletRequest request); // 댓글 더보기 기능
}

[CafeServiceImpl]

@Service
public class CafeServiceImpl implements CafeService {
    @Autowired
    private CafeDao cafeDao;

    @Autowired
    private CafeCommentDao cafeCommentDao;

    // 페이징 처리, 검색어 기능을 고려한 비즈니스 로직 처리를 하는 메소드
    @Override
    public void getList(HttpServletRequest request) {
        // 한 페이지에 몇개씩 표시할 것인지
        final int PAGE_ROW_COUNT = 5;
        // 하단 페이지를 몇개씩 표시할 것인지
        final int PAGE_DISPLAY_COUNT = 5;

        // 보여줄 페이지의 번호를 일단 1이라고 초기값 지정
        int pageNum = 1;
        // 페이지 번호가 파라미터로 전달되는지 읽어와 본다.
        String strPageNum = request.getParameter("pageNum");
        // 만일 페이지 번호가 파라미터로 넘어 온다면
        if (strPageNum != null) {
            // 숫자로 바꿔서 보여줄 페이지 번호로 지정한다.
            pageNum = Integer.parseInt(strPageNum);
        }

        // 보여줄 페이지의 시작 ROWNUM
        int startRowNum = 1 + (pageNum - 1) * PAGE_ROW_COUNT;
        // 보여줄 페이지의 끝 ROWNUM
        int endRowNum = pageNum * PAGE_ROW_COUNT;

      /*
         [ 검색 키워드에 관련된 처리 ]
         -검색 키워드가 파라미터로 넘어올수도 있고 안넘어 올수도 있다.
      */
        String keyword = request.getParameter("keyword");
        String condition = request.getParameter("condition");
        // 만일 키워드가 넘어오지 않는다면
        if (keyword == null) {
            // 키워드와 검색 조건에 빈 문자열을 넣어준다.
            // 클라이언트 웹브라우저에 출력할때 "null" 을 출력되지 않게 하기 위해서
            keyword = "";
            condition = "";
        }

        // 특수기호를 인코딩한 키워드를 미리 준비한다.
        String encodedK = URLEncoder.encode(keyword);

        // CafeDto 객체에 startRowNum 과 endRowNum 을 담는다.
        CafeDto dto = new CafeDto();
        dto.setStartRowNum(startRowNum);
        dto.setEndRowNum(endRowNum);

        // 만일 검색 키워드가 넘어온다면
        if (!keyword.equals("")) {
            // 검색 조건이 무엇이냐에 따라 분기 하기
            if (condition.equals("title_content")) { // 제목 + 내용 검색인 경우
                // 검색 키워드를 CafeDto 에 담아서 전달한다.
                dto.setTitle(keyword);
                dto.setContent(keyword);
            } else if (condition.equals("title")) { // 제목 검색인 경우
                dto.setTitle(keyword);
            } else if (condition.equals("writer")) { // 작성자 검색인 경우
                dto.setWriter(keyword);
            } // 다른 검색 조건을 추가 하고 싶다면 아래에 else if() 를 계속 추가 하면 된다.
        }
        // 글 목록 얻어오기
        List<CafeDto> list = cafeDao.getList(dto);
        // 전체글의 갯수
        int totalRow = cafeDao.getCount(dto);

        // 하단 시작 페이지 번호
        int startPageNum = 1 + ((pageNum - 1) / PAGE_DISPLAY_COUNT) * PAGE_DISPLAY_COUNT;
        // 하단 끝 페이지 번호
        int endPageNum = startPageNum + PAGE_DISPLAY_COUNT - 1;


        // 전체 페이지의 갯수
        int totalPageCount = (int) Math.ceil(totalRow / (double) PAGE_ROW_COUNT);
        // 끝 페이지 번호가 전체 페이지 갯수보다 크다면 잘못된 값이다.
        if (endPageNum > totalPageCount) {
            endPageNum = totalPageCount; // 보정해 준다.
        }
        // view page 에서 필요한 값을 request 에 담아준다.
        request.setAttribute("pageNum", pageNum);
        request.setAttribute("startPageNum", startPageNum);
        request.setAttribute("endPageNum", endPageNum);
        request.setAttribute("condition", condition);
        request.setAttribute("keyword", keyword);
        request.setAttribute("encodedK", encodedK);
        request.setAttribute("totalPageCount", totalPageCount);
        request.setAttribute("list", list);
        request.setAttribute("totalRow", totalRow);
    }

    @Override
    public void getDetail(HttpServletRequest request) {
        // 자세히 보여줄 글번호를 읽어온다.
        int num = Integer.parseInt(request.getParameter("num"));
        // 조회수 올리기
        cafeDao.addViewCount(num);

      /*
         [ 검색 키워드에 관련된 처리 ]
         -검색 키워드가 파라미터로 넘어올수도 있고 안넘어 올수도 있다.
      */
        String keyword = request.getParameter("keyword");
        String condition = request.getParameter("condition");
        // 만일 키워드가 넘어오지 않는다면
        if (keyword == null) {
            // 키워드와 검색 조건에 빈 문자열을 넣어준다.
            // 클라이언트 웹브라우저에 출력할때 "null" 을 출력되지 않게 하기 위해서
            keyword = "";
            condition = "";
        }
        // CafeDto 객체를 생성해서
        CafeDto dto = new CafeDto();
        // 자세히 보여줄 글번호를 넣어준다.
        dto.setNum(num);
        // 만일 검색 키워드가 넘어온다면
        if (!keyword.equals("")) {
            // 검색 조건이 무엇이냐에 따라 분기 하기
            if (condition.equals("title_content")) {// 제목 + 내용 검색인 경우
                // 검색 키워드를 CafeDto 에 담아서 전달한다.
                dto.setTitle(keyword);
                dto.setContent(keyword);
            } else if (condition.equals("title")) { // 제목 검색인 경우
                dto.setTitle(keyword);
            } else if (condition.equals("writer")) { // 작성자 검색인 경우
                dto.setWriter(keyword);
            } // 다른 검색 조건을 추가 하고 싶다면 아래에 else if() 를 계속 추가 하면 된다.
        }

        // 글하나의 정보를 얻어온다.
        CafeDto resultDto = cafeDao.getData(dto);

        // 특수기호를 인코딩한 키워드를 미리 준비한다.
        String encodedK = URLEncoder.encode(keyword);

       /*
         [ 댓글 페이징 처리에 관련된 로직 ]
       */
        // 한 페이지에 몇개씩 표시할 것인지
        final int PAGE_ROW_COUNT = 10;
        // detail.jsp 페이지에서는 항상 1페이지의 댓글 내용만 출력한다.
        int pageNum = 1;
        // 보여줄 페이지의 시작 ROWNUM
        int startRowNum = 1 + (pageNum - 1) * PAGE_ROW_COUNT;
        // 보여줄 페이지의 끝 ROWNUM
        int endRowNum = pageNum * PAGE_ROW_COUNT;
        // 원글의 글번호를 이용해서 해당글에 달린 댓글 목록을 얻어온다.
        CafeCommentDto commentDto = new CafeCommentDto();
        commentDto.setRef_group(num);
        // 1페이지에 해당하는 startRowNum 과 endRowNum 을 dto 에 담아서
        commentDto.setStartRowNum(startRowNum);
        commentDto.setEndRowNum(endRowNum);
        // 1페이지에 해당하는 댓글 목록만 select 되도록 한다.
        List<CafeCommentDto> commentList = cafeCommentDao.getList(commentDto);

        // 원글의 글번호를 이용해서 댓글 전체의 갯수를 얻어낸다.
        int totalRow = cafeCommentDao.getCount(num);
        // 댓글 전체 페이지의 갯수
        int totalPageCount = (int) Math.ceil(totalRow / (double) PAGE_ROW_COUNT);

        // request scope 에 글 하나의 정보 담기
        request.setAttribute("dto", resultDto);
        request.setAttribute("condition", condition);
        request.setAttribute("keyword", keyword);
        request.setAttribute("encodedK", encodedK);
        request.setAttribute("totalRow", totalRow);
        request.setAttribute("commentList", commentList);
        request.setAttribute("totalPageCount", totalPageCount);
    }

    @Override
    public void saveContent(CafeDto dto) {
        // title, content, writer 정보가 들어있는 CafeDto 를 dao 에 전달해서 DB 에 저장되도록 한다.
        cafeDao.insert(dto);
    }

    @Override
    public void updateContent(CafeDto dto) {
        cafeDao.update(dto);
    }

    @Override
    public void deleteContent(int num, HttpServletRequest request) {
        // 세션에서 로그인된 아이디를 읽어와서
        String id = (String) request.getSession().getAttribute("id");
        // 글 작성자와 로그인된 아이디가 다르다면
        CafeDto dto = cafeDao.getData(num);
        // 예외를 발생시켜서 삭제가 안되도록 한다.
        if (!id.equals(dto.getWriter())) {
            throw new NotDeleteException("하지마라 ~");
        }
        cafeDao.delete(num);
    }

    // 글 수정 폼에 필요한 값을 HttpServletRequest 에 담아주는 메소드
    @Override
    public void getData(HttpServletRequest request) {
        // 수정할 글 정보
        int num = Integer.parseInt(request.getParameter("num"));
        // 수정할 글의 정보 얻어와서
        CafeDto dto = cafeDao.getData(num);
        // request 에 담아준다.
        request.setAttribute("dto", dto);
    }

    @Override
    public void saveComment(HttpServletRequest request) {
        // 폼 전송되는 파라미터 추출
        int ref_group = Integer.parseInt(request.getParameter("ref_group")); // 원글의 글번호
        String target_id = request.getParameter("target_id"); // 댓글 대상자의 아이디
        String content = request.getParameter("content"); // 댓글의 내용
        /*
            원글의 댓글은 comment_group 번호가 전송이 안되고
            댓글의 댓글은 comment_group 번호가 전송이 된다.
            따라서 null 여부를 조사하면 원글의 댓글인지 댓글의 댓글인지 판단할 수 있다.
         */
        String comment_group = request.getParameter("comment_group");

        // 댓글 작성자는 session 영역에서 얻어내기
        String writer = (String) request.getSession().getAttribute("id");
        // 댓글의 시퀀스 번호 미리 얻어내기
        int seq = cafeCommentDao.getSequence();

        // 저장할 댓글의 정보를 dto 에 담기
        CafeCommentDto dto = new CafeCommentDto();
        dto.setNum(seq);
        dto.setWriter(writer);
        dto.setTarget_id(target_id);
        dto.setContent(content);
        dto.setRef_group(ref_group);
        // 원글의 댓글인 경우
        if (comment_group == null) {
            // 댓글의 글 번호를 comment_group 번호로 사용한다.
            dto.setComment_group(seq);
        } else {
            // 전송된 comment_group 번호를 숫자로 바꿔서 dto 에 넣어준다.
            dto.setComment_group(Integer.parseInt(comment_group));
        }
        // 댓글 정보를 DB에 저장하기
        cafeCommentDao.insert(dto);
    }

    @Override
    public void deleteComment(HttpServletRequest request) {
        int num = Integer.parseInt(request.getParameter("num"));
        // 삭제할 댓글 정보를 읽어와서
        CafeCommentDto dto = cafeCommentDao.getData(num);
        String id = (String) request.getSession().getAttribute("id");
        // 글 작성자와 로그인된 아이디와 일치하지 않으면
        if (!dto.getWriter().equals(id)) {
            throw new NotDeleteException("남의 댓글 지우면 혼단다!");
        }
        // dao 를 이용해서 DB 에서 삭제하기
        cafeCommentDao.delete(num);
    }

    @Override
    public void updateComment(CafeCommentDto dto) {
        cafeCommentDao.update(dto);
    }

    @Override
    public void moreCommentList(HttpServletRequest request) {
        // 로그인된 아이디
        String id = (String) request.getSession().getAttribute("id");
        // ajax 요청 파라미터로 넘어오는 댓글의 페이지 번호를 읽어낸다.
        int pageNum = Integer.parseInt(request.getParameter("pageNum"));
        // ajax 요청 파라미터로 넘어오는 원글의 글 번호를 읽어낸다.
        int num = Integer.parseInt(request.getParameter("num"));
        /*
         [ 댓글 페이징 처리에 관련된 로직 ]
        */
        // 한 페이지에 몇개씩 표시할 것인지
        final int PAGE_ROW_COUNT = 10;

        // 보여줄 페이지의 시작 ROWNUM
        int startRowNum = 1 + (pageNum - 1) * PAGE_ROW_COUNT;
        // 보여줄 페이지의 끝 ROWNUM
        int endRowNum = pageNum * PAGE_ROW_COUNT;

        // 원글의 글번호를 이용해서 해당글에 달린 댓글 목록을 얻어온다.
        CafeCommentDto commentDto = new CafeCommentDto();
        commentDto.setRef_group(num);
        // 1페이지에 해당하는 startRowNum 과 endRowNum 을 dto 에 담아서
        commentDto.setStartRowNum(startRowNum);
        commentDto.setEndRowNum(endRowNum);

        // pageNum 에 해당하는 댓글 목록만 select 되도록 한다.
        List<CafeCommentDto> commentList = cafeCommentDao.getList(commentDto);
        // 원글의 글번호를 이용해서 댓글 전체의 갯수를 얻어낸다.
        int totalRow = cafeCommentDao.getCount(num);
        // 댓글 전체 페이지의 갯수
        int totalPageCount = (int) Math.ceil(totalRow / (double) PAGE_ROW_COUNT);

        // view page 에 필요한 값 request 에 담아주기
        request.setAttribute("commentList", commentList);
        request.setAttribute("num", num); // 원글의 글번호
        request.setAttribute("pageNum", pageNum); // 댓글의 페이지 번호
    }
}

0개의 댓글