일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 추상메서드
- abstract
- 제네릭
- 컬렉션 타입
- 오라클
- 사용자예외클래스생성
- 환경설정
- cursor문
- 메소드오버로딩
- oracle
- 어윈 사용법
- 생성자오버로드
- 인터페이스
- 한국건설관리시스템
- 자바
- 참조형변수
- Java
- NestedFor
- 다형성
- exception
- 예외미루기
- 예외처리
- EnhancedFor
- 대덕인재개발원
- 정수형타입
- 집합_SET
- 자동차수리시스템
- GRANT VIEW
- 컬렉션프레임워크
- 객체 비교
- Today
- Total
거니의 velog
(5) 모델2 방식으로 효율적으로 개발하기 4_답변형 게시판 구현 2 본문
(1) 게시판 글 목록 보기 구현
* 다음 그림은 게시판의 글 목록 보기 기능을 구현하는 과정을 나타낸 것이다.
* 브라우저에서 /board/listArticle.do로 요청하면 Controller가 전달받아 Service와 DAO를 거쳐 글 정보를 조회한 후 listArticle.jsp로 전달하여 화면에 글 목록을 보여준다. 여기서 문제는 글 목록을 그냥 나열만 하는 것이 아니라 부모 글에 대한 답변 글을 계층 구조로 보여주어야 한다는 것이다. 이렇게 하려면 어떻게 해야 할까?
* 오라클에서 제공하는 계층형 SQL문 기능을 이용하면 이를 구현할 수 있다.
-- LEVEL : 오라클에서 제공하는 가상 컬럼으로 글의 깊이(depth)를 나타낸다. (부모 글은 1이다)
SELECT LEVEL
, articleNO
, parentNO
, LPAD(' ', 4*(LEVEL-1)) || title as title
, content
, writeDate
, id
FROM t_board
START WITH parentNO = 0 -- (1)
CONNECT BY PRIOR articleNO = parentNO -- (2)
ORDER SIBLINGS BY articleNO DESC; -- (3)
* SQL 문의 각 문법을 조금 살펴보자.
(1) 계층형 구조에서 최상위 계층의 로우(row)를 식별하는 조건을 명시한다.
parentNO가 0, 즉 부모 글부터 시작해 계층형 구조를 만든다는 의미이다.
(2) 계층 구조가 어떤 식으로 연결되는지를 기술하는 부분이다.
parentNO에 부모 글 번호가 있으므로 이를 표현하려면
CONNECT BY PRIOR articleNO = parentNO로 기술해야 한다.
(3) 계층 구조로 조회된 정보를 다시 articleNO를 이용해 내림차순으로 정렬하여 최종 출력한다.
* 위의 계층형 SQL문을 SQL Developer에서 실행하면 다음과 같이 자식 글이 부모 글 아래에 출력된다.
* 그럼 실제로 클래스와 JSP를 사용해 이를 구현해 보자.
1. sec03.brd01 패키지를 새로 만들고 관련된 클래스를 추가한다. 또한 board01 폴더를 만들고 listArticles.jsp를 추가한다.
2. BoardController 클래스를 다음과 같이 작성한다. 이 클래스는 /board/listArticles.do로 요청 시 화면에 글 목록을 출력하는 역할을 한다. getPathInfo() 메서드를 이용해 action 값을 가져오고 action 값이 null이거나 /listArticles.do일 경우 BoardService 클래스의 listArticles() 메서드를 호출해 전체 글을 조회한다. 그리고 조회한 글을 articlesList 속성으로 바인딩하고 글 목록창(listArticles.jsp)으로 포워딩한다.
package sec03.brd01;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/board/*")
public class BoardController extends HttpServlet {
private static final long serialVersionUID = 1L;
BoardService boardService;
ArticleVO articleVO;
public void init(ServletConfig config) throws ServletException {
boardService = new BoardService(); // 서블릿 초기화 시 BoardService 객체를 생성한다.
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doHandle(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doHandle(request, response);
}
private void doHandle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String nextPage = "";
request.setCharacterEncoding("utf-8");
response.setContentType("text/html; charset=utf-8");
String action = request.getPathInfo(); // 요청명을 가져온다.
System.out.println("action:" + action);
try {
List<ArticleVO> articlesList = new ArrayList<ArticleVO>();
if (action == null) {
articlesList = boardService.listArticles();
request.setAttribute("articlesList", articlesList);
nextPage = "/board01/listArticles.jsp";
}else if (action.equals("/listArticles.do")) { // action값이 /listArticles.do이면 전체 글을 조회한다.
articlesList = boardService.listArticles(); // 전체 글을 조회한다.
request.setAttribute("articlesList", articlesList); // 조회된 글 목록을 articlesList로 바인딩한 후 listArticles.jsp로 포워딩한다.
nextPage = "/board01/listArticles.jsp";
}else {
nextPage = "/board01/listArticles.jsp";
}
RequestDispatcher dispatch = request.getRequestDispatcher(nextPage);
dispatch.forward(request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3. BoardService 클래스를 다음과 같이 작성한다. BoardDAO 객체를 생성한 후 selectAllArticle() 메서드를 호출해 전체 글을 가져온다.
package sec03.brd01;
import java.util.List;
public class BoardService {
BoardDAO boardDAO;
public BoardService() {
boardDAO = new BoardDAO(); // 생성자 호출 시 BoardDAO 객체를 생성한다.
}
public List<ArticleVO> listArticles() {
List<ArticleVO> articlesList = boardDAO.selectAllArticles();
return articlesList;
}
}
BoardDAO 클래스의 메서드 이름은 보통 각 메서드들이 실행하는 SQL문에 의해 결정된다.
예를 들어 selectAllArticles() 메서드는 전체 글 정보를 조회하는 SQL문을 실행하므로
메서드 이름에 selectAll이 들어간다.
4. BoardDAO 클래스를 다음과 같이 작성한다. BoardService 클래스에서 BoardDAO의 selectAllArticles() 메서드를 호출하면 계층형 SQL문을 이용해 계층형 구조로 전체 글을 조회한 후 반환한다.
package sec03.brd01;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
public class BoardDAO {
private DataSource dataFactory;
Connection conn;
PreparedStatement pstmt;
public BoardDAO() {
try {
Context ctx = new InitialContext();
Context envContext = (Context) ctx.lookup("java:/comp/env");
dataFactory = (DataSource) envContext.lookup("jdbc/oracle");
} catch (Exception e) {
e.printStackTrace();
}
}
public List selectAllArticles() {
List articlesList = new ArrayList();
try {
conn = dataFactory.getConnection();
String query = " SELECT LEVEL, articleNO, parentNO, title, content, id, writeDate "
+ " from t_board "
+ " START WITH parentNO = 0 "
+ " CONNECT BY PRIOR articleNO = parentNO "
+ " ORDER SIBLINGS BY articleNO DESC "; // 오라클의 계층형 SQL문을 실행한다.
System.out.println(query);
pstmt = conn.prepareStatement(query);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
int level = rs.getInt("level"); // 각 글의 깊이(계층)를 level 속성에 저장한다.
int articleNO = rs.getInt("articleNO"); // 글 번호는 숫자형이므로 getInt()로 값을 가져온다.
int parentNO = rs.getInt("parentNO");
String title = rs.getString("title");
String content = rs.getString("content");
String id = rs.getString("id");
Date writeDate = rs.getDate("writeDate");
ArticleVO article = new ArticleVO();
article.setLevel(level);
article.setArticleNO(articleNO);
article.setParentNO(parentNO);
article.setTitle(title);
article.setContent(content);
article.setId(id);
article.setWriteDate(writeDate); // 글 정보를 ArticleVO 객체의 속성에 설정한다.
articlesList.add(article);
}
rs.close();
pstmt.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
return articlesList;
}
}
5. ArticleVO 클래스를 다음과 같이 작성한다. 조회한 글을 저장하는 ArticleVO 클래스에 글의 깊이를 저장하는 level 속성을 추가한다.
package sec03.brd01;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.sql.Date;
public class ArticleVO {
private int level;
private int articleNO;
private int parentNO;
private String title;
private String content;
private String imageFileName;
private String id;
private Date writeDate;
public ArticleVO() {}
public ArticleVO(int level, int articleNO, int parentNO, String title, String content, String imageFileName,
String id) {
this.level = level;
this.articleNO = articleNO;
this.parentNO = parentNO;
this.title = title;
this.content = content;
this.imageFileName = imageFileName;
this.id = id;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public int getArticleNO() {
return articleNO;
}
public void setArticleNO(int articleNO) {
this.articleNO = articleNO;
}
public int getParentNO() {
return parentNO;
}
public void setParentNO(int parentNO) {
this.parentNO = parentNO;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getImageFileName() {
try {
if (imageFileName != null && imageFileName.length() != 0) {
imageFileName = URLDecoder.decode(imageFileName, "UTF-8");
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return imageFileName;
}
public void setImageFileName(String imageFileName) {
try {
if (imageFileName != null && imageFileName.length() != 0) {
this.imageFileName = URLEncoder.encode(imageFileName, "UTF-8"); // 파일이름에 특수문자가 있을 경우 인코딩한다.
}else {
this.imageFileName = imageFileName;
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Date getWriteDate() {
return writeDate;
}
public void setWriteDate(Date writeDate) {
this.writeDate = writeDate;
}
}
6. 이제 JSP에 글 목록을 표시해 보자. listArticles.jsp를 다음과 같이 작성한다. 첫 번째 <forEach> 태그를 이용해 articlesList 속성으로 포워딩된 글 목록을 차례대로 전달받아 표시한다. <forEach> 태그 반복 시 각 글의 level 값이 1보다 크면 답글이므로 다시 내부 <forEach> 태그를 이용해 1부터 level 값까지 반복하면서 공백을 만들고(들여쓰기) 답글을 표시한다. 이때 level 값이 1보다 크지 않으면 부모 글이므로 공백 없이 표시한다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"
isELIgnored="false" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<%
request.setCharacterEncoding("UTF-8");
%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>글목록창</title>
<style>
.cls1 {
text-decoration: none;
}
.cls2 {
text-align: center;
font-size: 30px;
}
</style>
</head>
<body>
<table align="center" border="1" width="80%">
<tr height="10" align="center" bgcolor="lightgreen">
<td>글번호</td>
<td>작성자</td>
<td>제목</td>
<td>작성일</td>
</tr>
<c:choose>
<c:when test="${empty articlesList }">
<tr height="10">
<td colspan="4">
<p align="center">
<b><span style="font-size:9pt;">등록된 글이 없습니다.</span></b>
</p>
</td>
</tr>
</c:when>
<c:when test="${!empty articlesList}">
<%-- articlesList로 포워딩된 글 목록을 <forEach> 태그를 이용해 표시한다 --%>
<c:forEach var="article" items="${articlesList }" varStatus="articleNum">
<tr align="center">
<%-- <forEach> 태그의 varStatus의 count속성을 이용해 글 번호를 1부터 자동으로 표시한다. --%>
<td width="5%">${articleNum.count}</td>
<td width="10%">${article.id }</td>
<td align="left" width="35%">
<%-- 왼쪽으로 30px만큼 여백을 준 후 글 제목을 표시한다. --%>
<span style="padding-right:30px"></span>
<c:choose>
<c:when test='${article.level > 1 }'> <%-- level 값이 1보다 큰 경우는 자식 글이므로 level 값만큼 부모 글 밑에 공백으로 들여쓰기하여 자식 글임을 표시한다. --%>
<c:forEach begin="1" end="${article.level }" step="1">
<span style="padding-left:20px"></span>
</c:forEach> <%-- 부모 글 기준으로 왼쪽 여백을 level 값만큼 채워 답글을 부모 글에 대해 들여쓰기 한다. --%>
<span style="font-size:12px;">[답변]</span>
<a class="cls1" href="${contextPath}/board/viewArticle.do?articleNO=${article.articleNO}">${article.title}</a> <%-- 공백 다음에 자식 글을 표시한다. --%>
</c:when>
<c:otherwise>
<a class="cls1" href="${contextPath}/board/viewArticle.do?articleNO=${article.articleNO}">${article.title }</a>
</c:otherwise>
</c:choose>
</td>
<td width="10%">
<fmt:formatDate value="${article.writeDate}" />
</td>
</tr>
</c:forEach>
</c:when>
</c:choose>
</table>
<a class="cls1" href="#">
<p class="cls2">글쓰기</p>
</a>
</body>
</html>
7. 다음의 주소로 요청하여 글 목록이 우리가 원하는 대로 출력되는지 확인한다. 자식 글은 앞에 level 값만큼 들여쓰기가 되고 [답변] 이라는 텍스트 다음에 표시된다.
- http://localhost:8090/pro17/board/listArticles.do
* 게시판 글 목록을 계층형으로 구현하는 데 성공했다. 이제 세부적인 기능들을 하나씩 구현해 보자.
'Java > Java_JSP Model2' 카테고리의 다른 글
(7) 모델2 방식으로 효율적으로 개발하기 4_답변형 게시판 구현 4 (0) | 2023.09.26 |
---|---|
(6) 모델2 방식으로 효율적으로 개발하기 4_답변형 게시판 구현 3 (0) | 2023.09.23 |
(4) 모델2 방식으로 효율적으로 개발하기 4_답변형 게시판 구현 1 (0) | 2023.09.23 |
(3) 모델2 방식으로 효율적으로 개발하기 3 (0) | 2023.09.23 |
(2) 모델2 방식으로 효율적으로 개발하기 2 (0) | 2023.09.22 |