테이블 이름과 동일하게 변수를 선언한다.
public class BoardVO {
private int bno;
private int p_bno, level; //level은 답글의 개수다.
private String title, content, imgName, id;
private Date regdate;
...Getter Setter toString 생성자 생략...
level은 답글의 개수를 나타내는 칼럼이다.
실제로 존재하지 않는 컬럼이지만 쿼리로 직접 만들어서 쓴다.
게시물 목록을 불러오는 쿼리를 테스트한다.
public class BoardDAOTest {
private final static Logger log = Logger.getGlobal();
private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String URL = "jdbc:mysql://127.0.0.1:3306/servletex?serverTimezone=Asia/Seoul";
private static final String USER = "servlet";
private static final String PW = "1234";
@Test
public void getListTest() throws ClassNotFoundException {
List<BoardVO> list = new ArrayList<>();
Class.forName(DRIVER);
String sql = "SELECT CASE WHEN LEVEL-1 > 0 then CONCAT(CONCAT(REPEAT(' ', level - 1),''), t.title)\r\n" +
" ELSE t.title\r\n" +
" END AS title\r\n" +
" , t.bno\r\n" +
" , t.p_bno\r\n" +
" ,t.content\r\n" +
" ,t.id\r\n" +
" ,regdate\r\n" +
" , fnc.level\r\n" +
" FROM\r\n" +
" (SELECT fnc_hierarchy() AS id, @level AS level\r\n" +
" FROM (SELECT @start_with:=0, @id:=@start_with, @level:=0) vars\r\n" +
" JOIN t_board\r\n" +
" WHERE @id IS NOT NULL) fnc\r\n" +
" JOIN t_board t ON fnc.id = t.bno";
log.info(sql);
try( //try with resources
Connection conn = DriverManager.getConnection(URL, USER, PW);
PreparedStatement pstmt = conn.prepareCall(sql);
ResultSet rs = pstmt.executeQuery();
) {
log.info("conn : " + conn);
log.info("pstmt : " + pstmt);
assertNotNull(conn);
while(rs.next()) {
int level = rs.getInt("level");
int bno = rs.getInt("bno");
int p_bno = rs.getInt("p_bno");
String title = rs.getString("title");
String content = rs.getString("content");
String id = rs.getString("id");
Date regdate = rs.getDate("regdate");
BoardVO vo = new BoardVO(bno, p_bno, level, title, content, id, regdate);
list.add(vo);
assertNotNull(vo);
log.info("vo : " + vo);
} // while
} catch (Exception e) {
e.printStackTrace();
}
}
}
테스트 코드와 약간 다르다.
JNID사용
package com.coco.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import com.coco.vo.BoardVO;
public class BoardDAO {
private final static Logger log = Logger.getGlobal();
DataSource ds;
public BoardDAO() {
try {
Context ctx = new InitialContext();
Context envCtx = (Context)ctx.lookup("java:/comp/env");
ds = (DataSource)envCtx.lookup("jdbc/mysql");
} catch (NamingException e) {
e.printStackTrace();
}
} //BoardDAO()
public List<BoardVO> getList() {
List<BoardVO> list = new ArrayList<>();
String sql = "SELECT CASE WHEN LEVEL-1 > 0 then CONCAT(CONCAT(REPEAT(' ', level - 1),''), t.title)\r\n" +
" ELSE t.title\r\n" +
" END AS title\r\n" +
" , t.bno\r\n" +
" , t.p_bno\r\n" +
" ,t.content\r\n" +
" ,t.id\r\n" +
" ,regdate\r\n" +
" , fnc.level\r\n" +
" FROM\r\n" +
" (SELECT fnc_hierarchy() AS id, @level AS level\r\n" +
" FROM (SELECT @start_with:=0, @id:=@start_with, @level:=0) vars\r\n" +
" JOIN t_board\r\n" +
" WHERE @id IS NOT NULL) fnc\r\n" +
" JOIN t_board t ON fnc.id = t.bno";
log.info(sql);
try( //try with resources
Connection conn = ds.getConnection();
PreparedStatement pstmt = conn.prepareCall(sql);
ResultSet rs = pstmt.executeQuery();
) {
while(rs.next()) {
int level = rs.getInt("level");
int bno = rs.getInt("bno");
int p_bno = rs.getInt("p_bno");
String title = rs.getString("title");
String content = rs.getString("content");
String id = rs.getString("id");
Date regdate = rs.getDate("regdate");
BoardVO vo = new BoardVO(bno, p_bno, level, title, content, id, regdate);
list.add(vo);
} // while
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
} //getList()
}
public interface BoardService {
List<BoardVO> getList();
}
public class BoardServiceImpl implements BoardService {
private BoardDAO dao;
public BoardServiceImpl() {
dao = new BoardDAO();
}
@Override
public List<BoardVO> getList() {
return dao.getList();
}
}
모든 요청과 응답이 해당 필터를 거치도록 만들었다.
컨트롤러에서 직접 인코딩 해주지 않아도 된다.
@WebFilter("/*")
public class SetFilter implements Filter {
public void init(FilterConfig fConfig) throws ServletException {
System.out.println("set Encoding");
System.out.println("set ContentType");
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
request.setCharacterEncoding("utf-8");
chain.doFilter(request, response);
response.setContentType("text/html;charset=utf-8");
}
public void destroy() {
}
}
//'/board' 경로로 들어오는 모든 요청은 이 컨트롤러를 거친다.
@WebServlet("/board")
public class BoardController extends HttpServlet {
private final static Logger log = Logger.getGlobal();
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 {
//서비스 - 컨트롤러 연결
BoardService boardService = new BoardServiceImpl();
String nextPage = ""; //이동할 페이지
String action = request.getPathInfo(); //어떤 url을 요청했는가?
log.info("action : " + action);
/* 클라이언트가 요청한 URL이 '/board' 또는 '/board/list'일 경우
* service에서 게시글 목록을 불러오는 메서드를 바인딩 해서 화면으로 보낸다.
*/
if(action == null || action == "/list") {
request.setAttribute("boardList", boardService.getList());
nextPage="list";
}
RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/board/"+nextPage+".jsp");
dispatcher.forward(request, response);
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시판</title>
<style>
.cls1 {
text-decoration:none;
}
.cls2 {
text-align:center;
font-size:30px;
}
</style>
</head>
<body>
<h1 align='center'>자유 게시판</h1>
<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='${boardList == null}'>
<td colspan="4">
<p align="center">
<b><span style='font-size=9pt;'>등록된 글이 없습니다.</span></b>
</p>
</td>
</c:when>
<c:when test="${boardList != null }">
<c:forEach var="post" items="${boardList }" varStatus="postNum">
<tr align="center">
<td width="5%">${postNum.count }</td>
<td width="10%">${post.id }</td>
<td align='left' width="35%">
<span style='padding-right:30px'></span>
<c:choose>
<c:when test="${post.level > 1 }">
<c:forEach begin="1" end="${post.level }">
<span style='padding-left:20px'></span>
</c:forEach>
<span style='font-size:12px;'>[답변]</span>
<a class="cls1" href='/board/post?no=${post.bno }'>${post.title }</a>
</c:when>
<c:otherwise>
<a class='cls1' href='/board/post?no=${post.bno }'>${post.title }</a>
</c:otherwise>
</c:choose>
</td>
<td width="10%">
<fmt:formatDate value="${post.regdate }"/>
</td>
</tr>
</c:forEach>
</c:when>
</c:choose>
</table>
<a class='cls1' href='#'><p class='cls2'>글쓰기</a>
</body>
</html>
출력하는 태그를 하나씩 뜯어보면
<c:choose>
<c:when test='${boardList == null}'>
<td colspan="4">
<p align="center">
<b><span style='font-size=9pt;'>등록된 글이 없습니다.</span></b>
</p>
</td>
</c:when>
자세한 JSTL 사용법은 링크
<c:when test="${boardList != null }">
<c:forEach var="post" items="${boardList }" varStatus="postNum">
<tr align="center">
<td width="5%">${postNum.count }</td>
<td width="10%">${post.id }</td>
<td align='left' width="35%">
<span style='padding-right:30px'></span>
2번에는 두 가지 경우가 있다. 답글이 있는 경우와 없는 경우.
<c:choose>
<c:when test="${post.level > 1 }">
<c:forEach begin="1" end="${post.level }">
<span style='padding-left:20px'></span>
</c:forEach>
<span style='font-size:12px;'>[답변]</span>
<a class="cls1" href='/board/post?no=${post.bno }'>${post.title }</a>
</c:when>
답글이 없는 경우는 그냥 출력한다.
```html
<c:otherwise>
<a class='cls1' href='/board/post?no=${post.bno }'>${post.title }</a>
</c:otherwise>
fmt태그로 날짜 출력
<fmt:formatDate value="${post.regdate }"/>
최종