오늘은 MVC패턴으로 실제 웹사이트 만드는 작업중.
CRUD 기능을 포함하고 있고 , Servlet 과 jsp 를 나누어서 작업한다. 이클립스 dynamic web project 생성해서 사용.
- webapp 폴더에 resources 라는 공통적으로 사용할 header 나 footer같은 jsp 파일들 작성.
- css나 js 같은 공통적으로 적용할 css 나 개별 페이지에 적용할 파일들도 이곳에 저장.
- webapp 폴더안에서 개발자 외에 타인이 접근할 수 없게 jsp 파일은 WEB-INF 폴더 안에 생성한다.
- lib 폴더안은 각종 라이브러리를 넣는다.
- 안에서 세세하게 user,book 사용자별 나누고 , 같은 폴더 안 페이지별로 나눈다.
header나 footer 같은 jsp 파일 불러올때는 include태그를 사용한다.
각 섹션마다 layout 클래스를 적용해서 padding 을 줘서 가운데로 약간 몰리게 설정
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<nav class="layout navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid p-0">
<a class="navbar-brand" href="#">HOME</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item dropdown m-4 " >
<a class="nav-link dropdown-toggle " s href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
도서관리
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<%
String r = (String)session.getAttribute("role");
if(r!=null && "ROLE_MEMBER".equals(r)){
%>
<li><a class="dropdown-item" href="${pageContext.request.contextPath}/book/add">도서등록</a></li>
<%
}
%>
<li><a class="dropdown-item" href="${pageContext.request.contextPath}/book/list">도서조회</a></li>
</ul>
</li>
<li class="nav-item dropdown m-4">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
도서대여
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="#">대여신청</a></li>
<li><a class="dropdown-item" href="#">대여조회</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
부트스트랩을 적용한 nav jsp 파일.
이 부분임.
가장 처음에 들어오면 나오는 index.jsp 페이지이다.
(web.xml 에서 welcome-file 부분에 index.jsp 를 넣어서 실행시 자동으로 시작되게 설정해놓음)
<!-- Filter -->
<filter>
<filter-name>UTF-8_FILTER</filter-name>
<filter-class>Filter.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>UTF-8_FILTER</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
UTF-8 인코딩을 해주는 서블릿 filter 를 만들어 모든 파일에 매핑시킨다.
(url-pattern을 /* , 즉 모든파일로 지정해서 모든 파일에 한글이 깨지지 않도록 해준다.)
book/add 서블릿으로 form 데이터를 보낸다.
<servlet>
<servlet-name>FC</servlet-name>
<servlet-class>Controller.FrontController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
web.xml 에 frontController가 무조건 실행되게 설정 .
모든 요청은 frontcontroller를 거쳐가게 된다
보낸 form 은 frontController에 의해 받은 데이터에 따라 각각의 다른 Controller를 실행한다.
기본적으로 “/” 일 시에는 HomeController가 실행되는데 이때 HomeController는 index.jsp 로 포워딩을 한다.
예외처리를 하는 ExceptionHadler 함수 정의.
public void ExceptionHandler(Exception e,HttpServletRequest req,HttpServletResponse resp) throws ServletException{
try {
req.setAttribute("exception", e);
req.getRequestDispatcher("/WEB-INF/view/book/error.jsp").forward(req, resp);
}catch(Exception ex) {
throw new ServletException(ex);
}
//예외가 일어난다면 이 함수가 실행되는데 . setAttribute 로 예외와 error페이지로 포워딩처리를 하는 기능을 가진다.
}
@Override
public void execute(HttpServletRequest req, HttpServletResponse resp) {
try {
//Method==GET -> 페이지 표시(Forwarding)
String method = req.getMethod();
if("GET".equals(method)) {
System.out.println("[BC] GET /book/add..");
req.getRequestDispatcher("/WEB-INF/view/book/add.jsp").forward(req, resp);
return ;
}
BookAddController에서 execute 함수를 재정의 메소드 방식을 받아온 후에 메소드 방식을 문자열로 받은후 GET 이라면 add.jsp 로 포워딩처리
// 파라미터 받기
Integer bookCode = Integer.parseInt( req.getParameter("bookCode") );
String bookName = req.getParameter("bookName");
String publisher = req.getParameter("publisher");
String isbn = req.getParameter("isbn");
BookDto bookDto = new BookDto(bookCode,bookName,publisher,isbn);
System.out.println("bookDto : " + bookDto);
파라미터들을 받은 후에 파라미터 정보를담은 BookDto 객체를 생성.
bookService 의 bookRegistration 함수 호출.
Map<String,Object> bookRegistration(BookDto dto){
Map<String,Object> returnValue = new HashMap(); //리턴을 해줄 hashmap 선언
int result = 0; //데이터가 CRUD 됐는지 결과를 담는 result
result = bookDaoImpl.insert(dto) //controller에서 받아온 dto 를 받아와서 dao에서 insert 함수를 호출.
if(result>0) {
returnValue.put("success",true); //완료됐다면 true 를 담는다
returnValue.put("message","도서등록 완료"); //String 형태로 메시지를 담아줌
}
}
return returnValue;
Map<String,Object> returnVal= bookService.bookRegistration(bookDto);
boolean isAdded = (boolean)returnVal.get("success");
String message = (String)returnVal.get("message");
// 뷰로이동(내용전달 - ?)
if(isAdded) {
resp.sendRedirect(req.getContextPath()+ "/?message="+URLEncoder.encode(message,"UTF-8"));
return ;
}else {
req.getRequestDispatcher("/WEB-INF/view/book/add.jsp").forward(req, resp);
return ;
}
Service 로직에서 dao에 접근하여 Map<String,Object> 형태인 returnValue를 반환받은후에 그 결과값을 Controller까지 가져온다.
조건문으로 isAdded에 true가 들어있다면 주소창에 “ 도서등록 완료 “ 쿼리스트링이 담긴 url 로 Redirect 한다.
false라면 다시 add.jsp 로 돌아가서 정보를 재입력 하도록 한다.
페이징 처리
수업시간 내내 어려웠던 페이징처리 부분이다.
코드를 한번 살펴보자.
생성자와 ExceptionHadler 예외처리 함수까지는 동일.
우선 , 페이징 처리를 하기 위해서는 page의 dto 가 따로 필요하다.
public class Criteria {
//현재 페이지
private int pageno; // 현재 페이지의 넘버
private int amount; // 한 페이지의 나타나는 요소 개수
private String type;
private String keyword;
수업에는 criteria 라고 하는 페이지의 규격을 정하는 class를 정의했다.
public class PageDto {
private static final long serialVersionUID = 5L;
//페이지정보(전체페이지,현재페이지)
private int totalpage; //총게시물건수 / amount
private Criteria criteria; //현재페이지,한페이지당 읽을 게시물의 건수가 저장되어있음
//블럭정보
private int pagePerBlock; //블럭에 표시할 페이지개수(15건 지정)
private int totalBlock; //totalpage / pagePerBlock
private int nowBlock; //현재페이지번호 /pagePerBlock
//표시할 페이지(블럭에표시할 시작페이지-마지막페이지)
private int startPage;
private int endPage;
//NextPrev 버튼 표시여부
private boolean prev,next;
그리고 page 처리를 하기 위해 페이지의 상태를 나타내는 dto를 정의
public PageDto(int totalcount,Criteria criteria) {
this.criteria = criteria;
//전체페이지 계산
totalpage =(int)Math.ceil((1.0*totalcount)/criteria.getAmount()); //올림 처리해서 나머지있을 페이지까지 만듬
//블럭계산
pagePerBlock=15;
totalBlock = (int)Math.ceil( (1.0*totalpage) / pagePerBlock ); //총 있을 리스트 블럭의 개수
nowBlock = (int)Math.ceil ((1.0*criteria.getPageno()) / pagePerBlock); //현재 몇번째 블럭인지 16이상부터는 1.? 이니까 2로 되서 2번째 블럭
//Next,Prev 버튼 활성화 유무
prev=nowBlock>1; //현재 블럭이 1보다 크면 이전 버튼이 첫번째 블럭이면 이전버튼이 없으니까 같거나 작아야하는거아닌가
next=nowBlock<totalBlock;
//블럭에 표시할 페이지 번호 계산
//
this.endPage = (nowBlock * pagePerBlock<totalpage) ? nowBlock * pagePerBlock : totalpage ;
this.startPage=nowBlock * pagePerBlock -pagePerBlock + 1;
}
페이지가 생성되었을 때 pageDto. 각 요소들을 보자면
totalPage(전체페이지 개수) : 아래에 있는 이런 블럭의 총 개수를 설정한다.
요소의 총 개수 / 한 페이지의 요소개수 를 해서 총 페이지 개수 설정
pagePerBlock = 한 줄에 있을 페이지의 개수. 나는 15개로 길이를 잡았다.
totalBlock = 15개 길이짜리 리스트 블럭의 총 개수.
전체페이지개수 / pagePerBlock(15) 를 해서 구한다.
ex) 페이지가 25개라면 15를 나누면 1 하고 나머지 10이 남는데 이 때 남아있는 요소들도 페이지가 있어야 하기 때문에 올림을 해서 뒤에 애매하게 남은 페이지의 개수까지 길이로 잡는다.
nowBlock = 현재 몇번째 블럭인지 나타내는 요소. ex) 현재 페이지가 55이라면 15로 나누어서 나머지가 10이 남는데 올림처리를 해서 4가 된다. 4번째 페이지의 중간쯤 있기 때문에..
endPage = 나타낼 블럭 마지막 페이지. ex) nowBlock = 2, pagePerBlock = 10, totalpage = 18 , nowBlock * pagePerBlock는 2 * 10 = 20
20이 totalpage (18)보다 크므로, endPage는 totalpage의 값인 18이 됩니다.
startPage = 블럭 첫번째 페이지. ex) 현재블럭 4 * 15 - 15 + 1 = 60 - 15 + 1 = 46.
@Override
public void execute(HttpServletRequest req, HttpServletResponse resp) {
try {
//Method==GET -> 페이지 표시(Forwarding)
String method = req.getMethod();
System.out.println("[BC] GET /book/list..");
//파라미터 처음 - 전체조회 , 키워드 조회 , 페이지번호
String pageno = req.getParameter("pageno"); //int 가 맞는데 null 예외 때문에 String 으로 일단 받음 왜 ? String 은 없으면 빈 문자열인데 int 는 없으면 null 이기 떄문
String amount = req.getParameter("amount");
String type = req.getParameter("type");
String keyword = req.getParameter("keyword");
Criteria criteria = null;
if(pageno==null) {
criteria = new Criteria(); //pageNo = 1 amount = 10 type = null = keyword = null
}else {
criteria = new Criteria(pageno,10,type,keyword);
}
Criteria의 정보를 받아온다.
pageno 가 null이라면 pageNo가 1이고 amount가 10 인 기본생성자 생성.
아니라면 pageNo을 받고 amount가 10인 생성자 호출
service 함수 중 Map 타입의 getBooks 반환받음.
Map rvalue 와 List list 선언.
Type이 null 이라면 몇번째 데이터 부터 가져와야 하는지를 담은변수 offset 을 선언 .
현재 페이지 번호 - 1 * 페이지 당 항목수 = 15 , 30 , 45 이런식으로 증가.
BookDao 에서 limit 부분검색하는 select 정의한다.
pstmt = conn.prepareStatement("SELECT * FROM bookdb.tbl_book order by bookcode desc limit ?,?");
pstmt.setInt(1, offset);
pstmt.setInt(2, amount);
offset부터 amount 개수 만큼 조회하도록 하는 select 를 호출한다.
int total = 요소의 총 개수
total 과 criteria 정보를 담은 객체 생성
Map rvalue 에 select 결과 list 와 pageDto 객체정보를 담아서 리턴한다.
다시 컨트롤러로 돌아와
request에 list와 pageDto 를 요청 객체에 담아 list.jsp로 포워딩한다.
tbody에서 list 요소를 불러와 list 길이만큼 순회하며 tbody를 생성한다.
아래 tfoot 페이지 블럭에서는 type 과 keyword를 불러온 뒤에 Prev가 true이고 검색조건type이 null이라면 전체검색으로 url에 페이지번호만 나타낸 채 URL 을 생성한다.
여기서 getStartPage() -1 을 a태그로 감싸서 이전 버튼을 누를시 이전 리스트넘버로 이동되게함.
그 후 startpage 와 endpage 를 불러와서 반복문으로 startpage 부터 endpage 까지 생성되도록 하고
type이 있다면 URL 에 type과 keyword 를 가진채로 페이지를 생성한다.
next button 도 마찬가지로 a태그 안에서 +1 을 해서 클릭할 시 다음 페이지로 넘어가게 한다.
페이징 처리 우리가 이용하는 사이트로 봤을 때는 정말 간단하게 생겨서 몰랐는데 안에는 이렇게 복잡한 로직들이 가득히 들어가 있을 줄은 정말 몰랐다.
언젠가 할 프로젝트에서도 이 단계에서 아마 많이 시간이 소요되고 막히게 될 것 같은 느낌이 든다.
'Developer Note > 국비과정 수업내용 정리&저장' 카테고리의 다른 글
24년 11월 5일 (0) | 2024.11.11 |
---|---|
24년 11월 4일 (4) | 2024.11.06 |
24년 10월 31일 (1) | 2024.11.04 |
24년 10월 30일 (1) | 2024.11.04 |
24년 10월 29일 (0) | 2024.11.03 |