데이터융합 JAVA응용 SW개발자 기업 채용연계 연수과정 47일차 강의 정리

misung·2022년 5월 26일
0

JSP

MVC2 패턴 기반 게시판 제작

실습

  • BoardDAO() 에서 글 상세 내용을 담을 boardContent() 메서드를 구현하고, 이걸 BoardController 에서 호출.
    페이지에서 글 상세보기 요청이 컨트롤러로 전달되면, 컨트롤러에서는 어떤 게시글인지 bId를 BoardDAO에 전달하고, DAO에서는 bId에 매칭되는 게시글을 찾아서 VO 객체로 돌려줌.
    최종적으로 컨트롤러에서 디스패쳐 객체에 받아온 게시물 객체를 넣고 forward() 로 보냄.

  • 컨트롤러가 자꾸 규모가 커지면서 dp같은 변수가 중복으로 생성되면서 이름을 따로 만들어야 한다던가 좀 귀찮은 문제가 생김. 따라서 컨트롤러에서 Service라는 부분을 따로 구현해서 컨트롤러의 규모를 줄이고 기능을 덜어내기로 함.

  • 컨트롤러에서 요청을 분석해서 case문으로 특정 페이지로 보내주는 것은 컨트롤러의 역할이 맞으므로 그러한 부분은 그대로 놔둘 것. 중요한것은 그러한 요청 분석과 리다이렉트를 제외한 로직(변수 얻어와서 DAO에 요청하거나 객체를 포장하고 setAttribute() 하는 등 / DP로 다음 페이지로 보내는 등의 것은 제외)은 Service로 분리할 것.

  • 물론 Service가 여러 개 존재하게 되면 중구난방으로 난잡해질 수 있으므로 Interface를 만들어 공통적으로 구현해야 하는 메서드를 정해 준다. (IBoardService)

  • 서비스들을 패키지로 빼고 kr.co.jsp.board.service, 컨트롤러에서 있던 write, regist, list, content 들을 ContentService, GetListService, IBoardService, RegistService 등으로 분리함.

  • 해당 서비스들에는 변수를 생성하고 저장하고, request 를 통해 setAttribute() 하는 등의 동작을 분리함.

  • board_content.jsp 소스를 제공받고 각각 제목, 내용 등이 표시되어야 하는 부분을 EL문법으로 적어넣어 완성.

  • 그 외에도 BoardController에서 ~"delete" 까지 만듬

진도가 상당히 밀려 있어서, 일단 완성된 소스를 기준으로 분석하는 쪽으로 선회하기로 결정

BoardController.java

package kr.co.jsp.board.controller;

import java.io.IOException;
import java.util.List;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import kr.co.jsp.board.model.BoardDAO;
import kr.co.jsp.board.model.BoardVO;
import kr.co.jsp.board.service.ContentService;
import kr.co.jsp.board.service.DeleteService;
import kr.co.jsp.board.service.GetListService;
import kr.co.jsp.board.service.IBoardService;
import kr.co.jsp.board.service.ModifyService;
import kr.co.jsp.board.service.RegistService;
import kr.co.jsp.board.service.SearchService;
import kr.co.jsp.board.service.UpdateService;


@WebServlet("*.board")
public class BoardController extends HttpServlet {
	private static final long serialVersionUID = 1L;
    
	private RequestDispatcher dp;
	private IBoardService sv;
   
    public BoardController() {
        super();
    }

	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doRequest(request, response);
	}

	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");
		doRequest(request, response);
	}
	
	protected void doRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		String uri = request.getRequestURI();
		String conPath = request.getContextPath();
		uri = uri.substring(conPath.length()+1, uri.lastIndexOf("."));
		
		System.out.println(uri);
		
		switch(uri) {
		
		case "write":
			System.out.println("글쓰기 페이지로 이동 요청!");
			response.sendRedirect("board/board_write.jsp");
			break;
			
		case "regist":
			System.out.println("글 등록 요청이 들어옴!");
			sv = new RegistService();
			sv.execute(request, response);
			/*
			 왜 board_list.jsp로 바로 리다이렉트를 하면 안될까???
			 board_list.jsp에는 데이터베이스로부터 전체 글 목록을 가져오는
			 로직이 없으니까요. (jsp는 단순히 보여지는 역할만 할 뿐이다.)
			 컨트롤러로 글 목록 요청이 다시 들어올 수 있게끔
			 sendRedirect()를 사용해서 자동 목록 재요청이 들어오게 하는 겁니다.
			 */
			
			response.sendRedirect("/MyWeb/list.board");
			break;
		
		case "list":
			System.out.println("글 목록 요청이 들어옴!");
			sv = new GetListService();
			sv.execute(request, response);
			//sendRedirect를 하면 안되는 이유
			//request객체에 list를 담아서 전달하려 하는데, sendRedirect를 사용하면
			//응답이 나가면서 request 객체가 소멸해 버립니다.
//			response.sendRedirect("board/board_list.jsp"); (x)
			
			//request객체를 다음 화면까지 운반하기 위한 forward 기능을 지원하는 객체
			//-> RequestDispatcher
			dp = request.getRequestDispatcher("board/board_list.jsp");
			dp.forward(request, response);
			break;
		
		case "content":
			System.out.println("글 상세보기 요청이 들어옴!");
			sv = new ContentService();
			sv.execute(request, response);
			dp = request.getRequestDispatcher("board/board_content.jsp");
			dp.forward(request, response);
			break;
			
		case "modify":
			System.out.println("글 수정 페이지로 이동 요청!");
			sv = new ModifyService();
			sv.execute(request, response);
			dp = request.getRequestDispatcher("board/board_modify.jsp");
			dp.forward(request, response);
			break;
			
		case "update":
			System.out.println("글 수정 요청이 들어옴!");
			sv = new UpdateService();
			sv.execute(request, response);
			
			response.sendRedirect("/MyWeb/content.board?bId=" + request.getParameter("bId"));
			break;
			
		case "delete":
			System.out.println("글 삭제 요청이 들어옴!");
			sv = new DeleteService();
			sv.execute(request, response);
			response.sendRedirect("/MyWeb/list.board");
			break;
			
		case "search":
			System.out.println("글 검색 요청이 들어옴!");
			/*
			 검색 요청을 받아서 키워드, 카테고리 값을 이용해서 검색 데이터를
			 board_list.jsp에 표현하세요.
			 */
			sv = new SearchService();
			sv.execute(request, response);
			if(request.getAttribute("bList") != null) {
				dp = request.getRequestDispatcher("board/board_list.jsp");
				dp.forward(request, response);
			}
			break;
		}
	}
}

첫 번째 주목 포인트

private RequestDispatcher dp;

dp를 원래 아래 case들에서 새로이 선언하여 넣어주고 있었는데, 이럴 경우 이름 중복이 발생해서 dp1, dp2 이런 식으로 나오게 되어 메모리 공간을 추가로 잡아먹는 일이 생겼으므로, 위에서 공통으로 사용할 변수를 선언하고 갈아끼우는 식으로 변경.

두 번째 주목 포인트

sv = new RegistService();

모든 요청들에 대해서 Service() 로 분리하여 해당 서비스들을 별도의 클래스로 빼고, 관련된 로직을 해당 서비스에서 실행하도록 변경한다.

세 번째 주목 포인트

dp = request.getRequestDispatcher("board/board_content.jsp");
			dp.forward(request, response);

RequestDispatcher 는 다음 페이지로 이동할 때 Attribute 가 소멸하지 않도록 해 주는 forward 메서드를 가지고 있다. 이걸 써서 해당 객체가 소멸하지 않도록 해 준다.

~~~Service 클래스들

공통 구현할 인터페이스인 IBoardService
package kr.co.jsp.board.service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/*
 * 모든 서비스 객체가 하나의 인터페이스 타입으로 객체를 생성할 수 있게,
 * 같은 이름의 메서드로 동작할 수 있게끔 인터페이스를 제작.
 */
public interface IBoardService {
	
	// 추상 메서드 선언
	void execute(HttpServletRequest request, HttpServletResponse response);
	
}

모든 서비스들은 공통적으로 execute가 존재할 것이므로 인터페이스를 생성해둔다.

클래스 구경
package kr.co.jsp.board.service;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import kr.co.jsp.board.model.BoardDAO;
import kr.co.jsp.board.model.BoardVO;

public class GetListService implements IBoardService {

	@Override
	public void execute(HttpServletRequest request, HttpServletResponse response) {
		List<BoardVO> boardList = BoardDAO.getInstance().listBoard();
		request.setAttribute("bList", boardList);
	}

}

위의 경우 게시물 목록을 가져오는 서비스에 해당하는데,
DAO 로부터 게시물 목록 요청을 하고 BoardVO 리스트에 담아 그것을 setAttribute() 메서드로 "bList" 라는 이름으로 포장하여 보내고 있다.

이러한 일련의 과정을 Service로 분리하고, 나머지 과정들만 기존의 Controller에서 진행하도록 한다.

BoardDAO.java

package kr.co.jsp.board.model;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class BoardDAO implements IBoardDAO {

	private DataSource ds;
	
	private BoardDAO() {
		try {
			InitialContext ct = new InitialContext();
			ds = (DataSource) ct.lookup("java:comp/env/jdbc/myOracle");
		} catch (NamingException e) {
			e.printStackTrace();
		}
	}
	
	private static BoardDAO dao = new BoardDAO();
	
	public static BoardDAO getInstance() {
		if(dao == null) {
			dao = new BoardDAO();
		}
		return dao;
	}
	
	
	//////////////////////////////////////////////////////
	
	@Override
	public void regist(String writer, String title, String content) {
		String sql = "INSERT INTO my_board "
				+ "(board_id, writer, title, content) "
				+ "VALUES(board_seq.NEXTVAL,?,?,?)";
		try(Connection conn = ds.getConnection();
				PreparedStatement pstmt = conn.prepareStatement(sql)) {
			pstmt.setString(1, writer);
			pstmt.setString(2, title);
			pstmt.setString(3, content);
			pstmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		}	
	}

	@Override
	public List<BoardVO> listBoard() {
		List<BoardVO> articles = new ArrayList<>();
		String sql = "SELECT * FROM my_board ORDER BY board_id DESC";
		try(Connection conn = ds.getConnection();
				PreparedStatement pstmt = conn.prepareStatement(sql);
				ResultSet rs = pstmt.executeQuery()) {
			while(rs.next()) {
				BoardVO vo = new BoardVO(
							rs.getInt("board_id"),
							rs.getString("writer"),
							rs.getString("title"),
							rs.getString("content"),
							rs.getTimestamp("reg_date"),
							rs.getInt("hit")
						);
				articles.add(vo);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return articles;
	}

	@Override
	public BoardVO contentBoard(int bId) {
		BoardVO vo = null;
		String sql = "SELECT * FROM my_board WHERE board_id=?";
		try(Connection conn = ds.getConnection();
				PreparedStatement pstmt = conn.prepareStatement(sql)) {
			pstmt.setInt(1, bId);
			ResultSet rs = pstmt.executeQuery();
			
			if(rs.next()) {
				vo = new BoardVO(
							rs.getInt("board_id"),
							rs.getString("writer"),
							rs.getString("title"),
							rs.getString("content"),
							rs.getTimestamp("reg_date"),
							rs.getInt("hit")
						);
			}	
		} catch (Exception e) {
			e.printStackTrace();
		}
		return vo;
	}

	@Override
	public void updateBoard(String title, String content, int bId) {
		String sql = "UPDATE my_board "
				+ "SET title=?, content=? "
				+ "WHERE board_id=?";
		try(Connection conn = ds.getConnection();
				PreparedStatement pstmt = conn.prepareStatement(sql)) {
			pstmt.setString(1, title);
			pstmt.setString(2, content);
			pstmt.setInt(3, bId);
			pstmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public void deleteBoard(int bId) {
		String sql = "DELETE FROM my_board WHERE board_id=?";
		try(Connection conn = ds.getConnection();
				PreparedStatement pstmt = conn.prepareStatement(sql)) {
			pstmt.setInt(1, bId);
			pstmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public List<BoardVO> searchBoard(String keyword, String category) {
		List<BoardVO> list = new ArrayList<>();
		String sql = "SELECT * FROM my_board "
				+ "WHERE " + category + " LIKE ? "
						+ "ORDER BY board_id DESC";
		try(Connection conn = ds.getConnection();
				PreparedStatement pstmt = conn.prepareStatement(sql)) {
			pstmt.setString(1, "%" + keyword + "%");
			ResultSet rs = pstmt.executeQuery();
			while(rs.next()) {
				BoardVO vo = new BoardVO(
							rs.getInt("board_id"),
							rs.getString("writer"),
							rs.getString("title"),
							rs.getString("content"),
							rs.getTimestamp("reg_date"),
							rs.getInt("hit")
						);
				list.add(vo);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return list;
	}
	
	@Override
	public void upHit(int bId) {
		String sql = "UPDATE my_board SET hit=hit+1 "
				+ "WHERE board_id=?";
		try(Connection conn = ds.getConnection();
				PreparedStatement pstmt = conn.prepareStatement(sql)) {
			pstmt.setInt(1, bId);
			pstmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

주목 포인트

@Override
	public List<BoardVO> searchBoard(String keyword, String category) {
		List<BoardVO> list = new ArrayList<>();
		String sql = "SELECT * FROM my_board "
				+ "WHERE " + category + " LIKE ? "
						+ "ORDER BY board_id DESC";
		try(Connection conn = ds.getConnection();
				PreparedStatement pstmt = conn.prepareStatement(sql)) {
			pstmt.setString(1, "%" + keyword + "%");
			ResultSet rs = pstmt.executeQuery();
			while(rs.next()) {
				BoardVO vo = new BoardVO(
							rs.getInt("board_id"),
							rs.getString("writer"),
							rs.getString("title"),
							rs.getString("content"),
							rs.getTimestamp("reg_date"),
							rs.getInt("hit")
						);
				list.add(vo);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return list;
	}

searchBoard() 메서드가 조금 중요하다고 여겨지는데, category 부분은 검색창 사이드의 드롭다운 메뉴를 의미한다. 거기엔 작성자, 제목, 내용 등을 선택할 수 있으니 category 를 매개 변수로 받아서 WHERE 절에 두고, LIKE 절은 직접 검색할 문장을 넣어야 하니 ? 부분에 setString() 메서드를 호출하도록 한다.

WHERE ? LIKE ? 로 하고 pstmt.setString(1, category) pstmt.setString(2, "%" + keyword + "%"); 이런 식으로 하면 되지 않느냐는 생각이 들 수도 있지만, 이렇게 실행하면 keyword쪽은 문제가 발생하지 않지만, category 는 문제가 발생한다.

왜냐하면 setString() 등의 메서드로 값을 넣을 때는 보통 " " 따옴표를 이용하여 값을 전달하는데, 이렇게 전달하면 값이 제대로 전달이 될 리가 없다.

따라서 매개 변수로 받아온 category 를 직접 넣는 것이다.

searchService.java

package kr.co.jsp.board.service;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import kr.co.jsp.board.model.BoardDAO;
import kr.co.jsp.board.model.BoardVO;

public class SearchService implements IBoardService {

	@Override
	public void execute(HttpServletRequest request, HttpServletResponse response) {

		String keyword = request.getParameter("search");
		String category = request.getParameter("category");
		
		List<BoardVO> list = BoardDAO.getInstance().searchBoard(keyword, category);
		
		/*
		if(list.size() == 0) {
			request.setAttribute("searchFail", true);
		} else {
			request.setAttribute("bList", list);
		}
		*/
		
		if(list.size() == 0) {
			//자바 클래스에서 HTML이나 JS 문법을 사용하는 방법: PrintWriter 객체를 사용.
			response.setContentType("text/html; charset=UTF-8");
			
			try {
				PrintWriter out = response.getWriter();
				
				//원하시는 html / js 코드를 문자열 형태로 작성합니다.
				
				String htmlCode = "<script> \r\n" + 
								  "alert('검색 결과에 따른 게시물이 없습니다.'); \r\n" +
								  "location.href='/MyWeb/list.board'; \r\n" +
								  "</script>";
				out.print(htmlCode); //버퍼에 작성한 문자열을 저장합니다.
				
				out.flush(); //버퍼에 저장되어 있는 내용을 클라이언트로 전송하고 버퍼를 비웁니다.
				
				return;
				
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		request.setAttribute("bList", list);
		
	}

}

서비스들 중에서도 조금 중요하다고 생각해서 가져왔는데, 분석해보자면,

  1. DAOsearchBoard() 메서드를 통해서 키워드와 카테고리가 일치하는 게시물들을 찾아 리스트로 반환해줌

  2. 리스트의 사이즈가 0인 경우 일치하는 게시물이 없을 테이므로, JS를 이용하여 경고를 출력하고 게시물 목록으로 돌려보냄.
    그리고 JS를 사용할 땐 response 객체에 setContentType() 메서드를 사용해서 타입을 정해 놓고, PrintWriter 객체를 생성하여 getWriter() 메서드로 받아오고, htmlCode를 String 타입으로 생성한 다음, print() 메서드 호출 후 flush() 로 클라이언트로 전송 후 버퍼를 비운다.

  3. 만약 게시물이 1개 이상 존재하게 되면 게시물 리스트를 "bList" 로 포장하여 보내준다.

board_list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style>
	tbody{
		font-size: 20px;
	}

	
</style>

</head>
<body>

	<%--
		로그인하지 않은  사용자가 게시판에 들어왔을 경우 돌려보내는 코드를 작성.
	 --%>
	 <c:if test="${user == null}">
	 	<script>
	 		alert("회원만 이용 가능한 게시판입니다. 로그인 해 주세요.");
	 		//board_list.jsp로 직접 요청이 들어가는 경우는 없기 때문에
	 		//컨트롤러를 기준으로 상대 경로로 작성하시든지, 절대 경로로 작성해야 합니다.
	 		location.href="user/user_login.jsp";
	 	</script>
	 </c:if>
	 
	<%--  <c:if test="${searchFail}">
	 	<script>
	 		alert("조회 결과가 없습니다.");
	 		location.href="/MyWeb/list.board";
	 	</script>
	 </c:if> --%>

	<jsp:include page="../include/header.jsp"/>

	<div class="container">
		<h2>My Web게시판</h2>
		
		<table class="table table-secondary table-hover table-bordered">
			<thead style="font-size: 25px">
				<tr>
					<th>글 번호</th>
					<th>작성자</th>
					<th>제목</th>
					<th>날짜</th>
					<th>조회수</th>
				</tr>
			</thead>

			
			<tbody>
				<c:forEach var="b" items="${bList}">
					<tr>
						<td>${b.boardId}</td>
						<td>${b.writer}</td>
						<td>
							<a href="/MyWeb/content.board?bId=${b.boardId}">${b.title}</a>
							&nbsp;&nbsp;
							<c:if test="${b.newMark}">
								<img alt="newMark" src="/MyWeb/img/icon_new.gif">
							</c:if>
						</td>
						<td>
							<fmt:formatDate value="${b.regDate}" pattern="yyyy년 MM월 dd일 a hh시 mm분"/>
						</td>
						<td>${b.hit}</td>
					</tr>
				</c:forEach>
			</tbody>
			
			<%-- 페이징을 처리할 구간 --%>
			<tbody>
				<tr>
					<td colspan="5" align="center">
						<ul class="pagination pagination-lg">
						
						<%-- 이전 버튼 --%>
                     	
	                        <li class="page-item"><a class="page-link"
	                           href=""
	                           style="background-color: #643691; margin-top: 0; height: 40px; color: white; border: 0px solid #f78f24; opacity: 0.8">이전</a>
	                        </li>
                     	

                    	<%-- 페이지 버튼 --%>
   						
   						
	                        <li class="page-item">
	                        <a href="" class="page-link"
	                           style="margin-top: 0; height: 40px; color: pink; border: 1px solid #643691;">1</a>
	                        </li>
               			

                     	<%-- 다음 버튼 --%>
    		
	                        <li class="page-item"><a class="page-link"
	                           href=""
	                           style="background-color: #643691; margin-top: 0; height: 40px; color: white; border: 0px solid #f78f24; opacity: 0.8">다음</a>
	                        </li>
					
						</ul>
					</td>
				</tr>
			</tbody>
			
			<tbody>
				<tr>
					<td colspan="5" align="right">
						<form action="/MyWeb/search.board" class="form-inline" >
						  <div class="form-group">
						  	<select name="category" class="form-control">
						  		<option value="title">제목</option>
						  		<option value="writer">작성자</option>
						  		<option value="content">내용</option>
						  	</select>
						    <input type="text" name="search" placeholder="검색어 입력" class="form-control" >
						  	<input type="submit" value="검색" class="btn btn-default">
							<input type="button" value="글 작성" class="btn btn-default" onclick="location.href='/MyWeb/write.board'">
						  </div>
						</form> 
					</td>
				</tr>
			</tbody>
		
		</table>
	</div>

	<jsp:include page="../include/footer.jsp"/>

</body>
</html>

주목 포인트

<fmt:formatDate value="${b.regDate}" pattern="yyyy년 MM월 dd일 a hh시 mm분"/>

날짜 포맷을 지정하려면 <fmt:formatDate> 태그를 사용한다.

0개의 댓글