DTO, Util
DTO: Board.java
public class Board {
private int no;
private String title;
private User writer;
private String content;
private int likeCount;
private int viewCount;
private String deleted;
private Date createdDate;
public Board() {}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public User getWriter() {
return writer;
}
public void setWriter(User writer) {
this.writer = writer;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getLikeCount() {
return likeCount;
}
public void setLikeCount(int likeCount) {
this.likeCount = likeCount;
}
public int getViewCount() {
return viewCount;
}
public void setViewCount(int viewCount) {
this.viewCount = viewCount;
}
public String getDeleted() {
return deleted;
}
public void setDeleted(String deleted) {
this.deleted = deleted;
}
public Date getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Date createdDate) {
this.createdDate = createdDate;
}
}
Util: ConnectionUtil.java
/* import부분 생략 */
public class ConnectionUtil {
String url = "jdbc:oracle:thin:@localhost:1521:xe";
String id = "hr";
String pw = "zxcv1234";
static {
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
} catch (ClassNotFoundException ex) {
ex.getStackTrace();
}
}
public static Connection getConnection() throws SQLException{
return DriverManager.getConnection(url, id, pw);
}
}
게시글 조회
DAO: BoardDao.java
package com.sample.board.dao;
import static utils.ConnectionUtil.*;
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 com.sample.board.vo.Board;
import com.sample.board.vo.BoardLiker;
import com.sample.board.vo.User;
/**
* 게시글 등록, 게시글 목록조회, 게시글 수정, 게시글 삭제, 추천 추가, 추천인 조회 기능을 제공하는 클래스다.
* @author lee_e
*
*/
public class BoardDao {
private static BoardDao self = new BoardDao();
private BoardDao() {}
public static BoardDao getInstance() {
return self;
}
/**
* 지정된 게시글 정보를 테이블에 저장한다.
* @param board 게시글 정보
* @throws SQLException
*/
public void insertBoard(Board board) throws SQLException {
String sql = "insert into tb_comm_boards (board_no, board_title, board_writer_no, board_content) "
+ "values (comm_board_seq.nextval, ?, ?, ?)";
Connection connection = getConnection();
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, board.getTitle());
pstmt.setInt(2, board.getWriter().getNo());
pstmt.setString(3, board.getContent());
pstmt.executeUpdate();
pstmt.close();
connection.close();
}
/**
* 수정된 정보가 포함된 게시글 정보를 테이블에 반영한다.
* @param board
* @throws SQLException
*/
public void updateBoard(Board board) throws SQLException {
String sql = "update tb_comm_boards "
+ "set "
+ " board_title = ?, "
+ " board_content = ?, "
+ " board_like_count = ?, "
+ " board_view_count = ?, "
+ " board_updated_date = sysdate "
+ "where board_no = ? ";
Connection connection = getConnection();
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, board.getTitle());
pstmt.setString(2, board.getContent());
pstmt.setInt(3, board.getLikeCount());
pstmt.setInt(4, board.getViewCount());
pstmt.setInt(5, board.getNo());
pstmt.executeUpdate();
pstmt.close();
connection.close();
}
/**
* 지정된 번호의 게시글을 삭제처리한다.
* @param no 글번호
* @throws SQLException
*/
public void deleteBoard(int no) throws SQLException {
String sql = "update tb_comm_boards "
+ "set "
+ " board_deleted = 'Y', "
+ " board_deleted_date = sysdate, "
+ " board_updated_date = sysdate "
+ "where board_no = ? ";
Connection connection = getConnection();
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setInt(1, no);
pstmt.executeUpdate();
pstmt.close();
connection.close();
}
/**
* 테이블에 저장된 게시글정보의 갯수를 반환한다.
* @return 게시글 정보 갯수
* @throws SQLException
*/
public int getTotalRecords() throws SQLException {
String sql = "select count(*) cnt "
+ "from tb_comm_boards";
int totalRecords = 0;
Connection connection = getConnection();
PreparedStatement pstmt = connection.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
rs.next();
totalRecords = rs.getInt("cnt");
rs.close();
pstmt.close();
connection.close();
return totalRecords;
}
/**
* 지정된 범위에 속하는 게시글 정보를 반환한다.
* @param begin 시작 순번번호
* @param end 끝 순번번호
* @return 게시글 목록
* @throws SQLException
*/
public List<Board> getBoardList(int begin, int end) throws SQLException {
String sql = "select board_no, board_title, user_no, user_id, user_name, board_content, "
+ " board_view_count, board_like_count, board_deleted, "
+ " board_deleted_date, board_updated_date, board_created_date "
+ "from (select row_number() over (order by B.board_no desc) rn, "
+ " B.board_no, B.board_title, U.user_no, U.user_id, U.user_name, B.board_content, "
+ " B.board_view_count, B.board_like_count, B.board_deleted, "
+ " B.board_deleted_date, B.board_updated_date, B.board_created_date "
+ " from tb_comm_boards B, tb_comm_users U "
+ " where B.board_writer_no = U.user_no) "
+ "where rn >= ? and rn <= ? ";
List<Board> boardList = new ArrayList<>();
Connection connection = getConnection();
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setInt(1, begin);
pstmt.setInt(2, end);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
Board board = new Board();
User user = new User();
board.setNo(rs.getInt("board_no"));
board.setTitle(rs.getString("board_title"));
board.setContent(rs.getString("board_content"));
board.setLikeCount(rs.getInt("board_like_count"));
board.setViewCount(rs.getInt("board_view_count"));
board.setDeleted(rs.getString("board_deleted"));
board.setDeletedDate(rs.getDate("board_deleted_date"));
board.setUpdatedDate(rs.getDate("board_updated_date"));
board.setCreatedDate(rs.getDate("board_created_date"));
user.setNo(rs.getInt("user_no"));
user.setId(rs.getString("user_id"));
user.setName(rs.getString("user_name"));
board.setWriter(user);
boardList.add(board);
}
rs.close();
pstmt.close();
connection.close();
return boardList;
}
/**
* 지정된 번호의 게시글 정보를 반영한다.
* @param no 게시긃ㄴ호
* @return 게시글 정보
* @throws SQLException
*/
public Board getBoardDetail(int no) throws SQLException {
String sql = "select B.board_no, B.board_title, U.user_no, U.user_id, U.user_name, B.board_content, "
+ " B.board_view_count, B.board_like_count, B.board_deleted, "
+ " B.board_deleted_date, B.board_updated_date, B.board_created_date "
+ "from tb_comm_boards B, tb_comm_users U "
+ "where B.board_writer_no = U.user_no "
+ "and B.board_no = ? ";
Board board = null;
Connection connection = getConnection();
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setInt(1, no);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
board = new Board();
User user = new User();
board.setNo(rs.getInt("board_no"));
board.setTitle(rs.getString("board_title"));
board.setContent(rs.getString("board_content"));
board.setLikeCount(rs.getInt("board_like_count"));
board.setViewCount(rs.getInt("board_view_count"));
board.setDeleted(rs.getString("board_deleted"));
board.setDeletedDate(rs.getDate("board_deleted_date"));
board.setUpdatedDate(rs.getDate("board_updated_date"));
board.setCreatedDate(rs.getDate("board_created_date"));
user.setNo(rs.getInt("user_no"));
user.setId(rs.getString("user_id"));
user.setName(rs.getString("user_name"));
board.setWriter(user);
}
rs.close();
pstmt.close();
connection.close();
return board;
}
/**
* 게시글 추천 정보를 저장한다.
* @param boardLiker 게시글 추천정보(게시글번호, 로그인한 사용자번호 포함)
* @throws SQLException
*/
public void insertBoardLiker(BoardLiker boardLiker) throws SQLException {
String sql = "insert into tb_comm_board_like_users (board_no, user_no) "
+ "values (?, ?)";
Connection connection = getConnection();
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setInt(1, boardLiker.getBoardNo());
pstmt.setInt(2, boardLiker.getUserNo());
pstmt.executeUpdate();
pstmt.close();
connection.close();
}
/**
* 지정된 글번호와 사용자번호로 추천정보를 조회해서 반환한다.
* @param boardNo 글번호
* @param userNo 사용자번호
* @return 추천정보
* @throws SQLException
*/
public BoardLiker getBoardLiker(int boardNo, int userNo) throws SQLException {
String sql = "select board_no, user_no "
+ "from tb_comm_board_like_users "
+ "where board_no = ? and user_no = ?";
BoardLiker boardLiker = null;
Connection connection = getConnection();
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setInt(1, boardNo);
pstmt.setInt(2, userNo);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
boardLiker = new BoardLiker();
boardLiker.setBoardNo(rs.getInt("board_no"));
boardLiker.setUserNo(rs.getInt("user_no"));
}
rs.close();
pstmt.close();
connection.close();
return boardLiker;
}
/**
* 지정된 번호의 글에 추천한 사용자의 목록을 반환한다.
* @param boardNo 글번호
* @return 사용자 목록
* @throws SQLException
*/
public List<User> getLikeUsers(int boardNo) throws SQLException {
String sql = "select U.user_no, U.user_id, U.user_name "
+ "from tb_comm_board_like_users L, tb_comm_users U "
+ "where L.user_no = U.user_no "
+ "and L.board_no = ? ";
List<User> userList = new ArrayList<>();
Connection connection = getConnection();
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setInt(1, boardNo);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
User user = new User();
user.setNo(rs.getInt("user_no"));
user.setId(rs.getString("user_id"));
user.setName(rs.getString("user_name"));
userList.add(user);
}
rs.close();
pstmt.close();
connection.close();
return userList;
}
}
게시글 조회 페이지: detail.jsp
<%@page import="com.sample.board.vo.BoardLiker"%>
<%@page import="java.util.List"%>
<%@page import="com.sample.board.vo.Pagination"%>
<%@page import="com.sample.board.vo.Board"%>
<%@page import="com.sample.board.dao.BoardDao"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" >
<title>커뮤니티 게시판::상세</title>
</head>
<body>
<%
// include 시킨 navbar의 nav-item 중에서 페이지에 해당하는 nav-item를 active 시키기위해서 "menu"라는 이름으로 페이지이름을 속성으로 저장한다.
// pageContext에 menu라는 이름으로 설정한 속성값은 navbar.jsp의 6번째 라인에서 조회해서 navbar의 메뉴들 중 하나를 active 시키기 위해서 읽어간다.
pageContext.setAttribute("menu", "board");
%>
<%@ include file="../common/navbar.jsp" %>
<div class="container">
<div class="row mb-3">
<div class="col">
<h1 class="fs-3">게시글 상세</h1>
</div>
</div>
<%
// 클라이언트는 게시글 상세 페이지를 조회할 때 "detail.jsp?no=글번호&pageNo=페이지번호" 로 요청한다.(list.jsp의 78번 라인 참조)
// 요청파라미터에서 글번호와 페이지 번호를 조회한다.
int no = Integer.parseInt(request.getParameter("no"));
String pageNo = request.getParameter("pageNo");
String error = request.getParameter("error");
// 게시글 정보를 제공하는 BoardDao객체를 획득한다.
BoardDao boardDao = BoardDao.getInstance();
// 게시글 번호에 해당하는 글 정보를 조회한다.
Board board = boardDao.getBoardDetail(no);
// 게시글의 조회수를 1 증가시킨다.
board.setViewCount(board.getViewCount() + 1);
// 조회수가 1증가된 글정보를 테이블에 반영시킨다.
boardDao.updateBoard(board);
%>
<div class="row mb-3">
<div class="col">
<%
if ("deny-delete".equals(error)) {
%>
<div class="alert alert-danger">
<strong>삭제 실패!!</strong> 자신이 작성한 글이 아닌 경우 삭제할 수 없습니다.
</div>
<%
} else if ("deny-update".equals(error)) {
%>
<div class="alert alert-danger">
<strong>수정 실패!!</strong> 자신이 작성한 글이 아닌 경우 수정할 수 없습니다.
</div>
<%
} else if ("deny-like".equals(error)) {
%>
<div class="alert alert-danger">
<strong>추천 실패!!</strong> 이미 추천한 글이거나 자신의 글은 추천할 수 없습니다.
</div>
<%
}
%>
<table class="table">
<tbody>
<tr class="d-flex">
<th class="col-2">번호</th>
<td class="col-4"><%=board.getNo() %></td>
<th class="col-2">등록일</th>
<td class="col-4"><%=board.getCreatedDate() %></td>
</tr>
<tr class="d-flex">
<th class="col-2">제목</th>
<td class="col-4"><%=board.getTitle() %></td>
<th class="col-2">작성자</th>
<td class="col-4"><%=board.getWriter().getName() %></td>
</tr>
<tr class="d-flex">
<th class="col-2">조회수</th>
<td class="col-4"><%=board.getViewCount() %></td>
<th class="col-2">추천수</th>
<td class="col-4">
<%=board.getLikeCount() %>
<%
// 이 게시글에 대한 추천이 있는 경우에 버튼을 출력한다.
if (board.getLikeCount() > 0) {
%>
<button type="button" class="btn btn-outline-primary btn-sm" data-bs-toggle="modal" data-bs-target="#board-liker-modal">보기</button>
<%
}
%>
</td>
</tr>
<tr class="d-flex">
<th class="col-2">내용</th>
<td class="col-10"><%=board.getContent() %></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row mb-3">
<div class="col">
<div class="d-flex justify-content-between">
<div>
<%
if (loginUserInfo != null && loginUserInfo.getNo() == board.getWriter().getNo()) { // 로그인한 사용자의 사용자번호와 게시글작성자의 사용자번호가 일치하는 경우 버튼이 출력된다.
%>
<a href="delete.jsp?no=<%=board.getNo() %>&pageNo=<%=pageNo %>" class="btn btn-danger">삭제</a>
<%
}
%>
<%
if (loginUserInfo != null && loginUserInfo.getNo() != board.getWriter().getNo()) { // 로그인한 사용자의 사용자번호와 게시글작성자의 사용자번호가 서로 다른 경우 버튼이 출력된다.
BoardLiker boardLiker = boardDao.getBoardLiker(board.getNo(), loginUserInfo.getNo()); // 게시글번호와 로그인한 사용자번호로 추천정보를 조회한다.
%>
<a href="like.jsp?no=<%=board.getNo() %>&pageNo=<%=pageNo %>" class="btn btn-success <%=boardLiker != null ? "disabled" : "" %>">추천</a>
<%
}
%>
</div>
<a href="list.jsp?pageNo=<%=pageNo %>" class="btn btn-primary">목록</a>
</div>
</div>
</div>
</div>
<%
List<User> boardLikeUserList = boardDao.getLikeUsers(no);
%>
<div class="modal" tabindex="-1" id="board-liker-modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<div class="card bg-primary">
<div class="card-header fs-bold text-white">추천인</div>
<ul class="list-group">
<%
for (User user : boardLikeUserList) {
%>
<li class="list-group-item"><%=user.getName() %></li>
<%
}
%>
</ul>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">닫기</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
list.jsp(게시판)
<%@page import="com.sample.board.vo.Board"%>
<%@page import="java.util.List"%>
<%@page import="com.sample.board.vo.Pagination"%>
<%@page import="com.sample.board.dao.BoardDao"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" >
<title>커뮤니티 게시판::리스트</title>
</head>
<body>
<%
// include 시킨 navbar의 nav-item 중에서 페이지에 해당하는 nav-item를 active 시키기위해서 "menu"라는 이름으로 페이지이름을 속성으로 저장한다.
// pageContext에 menu라는 이름으로 설정한 속성값은 navbar.jsp의 6번째 라인에서 조회해서 navbar의 메뉴들 중 하나를 active 시키기 위해서 읽어간다.
pageContext.setAttribute("menu", "board");
%>
<%@ include file="../common/navbar.jsp" %>
<div class="container">
<div class="row mb-3">
<div class="col">
<h1 class="fs-3">게시글 목록</h1>
</div>
</div>
<%
// 요청파라미터에서 pageNo값을 조회한다.
// 요청파라미터에 pageNo값이 존재하지 않으면 Pagination객체에서 1페이지로 설정한다.
String pageNo = request.getParameter("pageNo");
// 게시글 정보 관련 기능을 제공하는 BoardDao객체를 획득한다.
BoardDao boardDao = BoardDao.getInstance();
// 총 데이터 갯수를 조회한다.
int totalRecords = boardDao.getTotalRecords();
// 페이징 처리 필요한 값을 계산하는 Paginatition객체를 생성한다.
Pagination pagination = new Pagination(pageNo, totalRecords);
// 현재 페이지번호에 해당하는 게시글 목록을 조회한다.
List<Board> boardList = boardDao.getBoardList(pagination.getBegin(), pagination.getEnd());
%>
<div class="row mb-3">
<div class="col">
<table class="table">
<thead>
<tr class="d-flex">
<th class="col-1">번호</th>
<th class="col-5">제목</th>
<th class="col-2">작성자</th>
<th class="col-1">추천수</th>
<th class="col-1">조회수</th>
<th class="col-2">등록일</th>
</tr>
</thead>
<tbody>
<%
if (boardList.isEmpty()) {
%>
<tr>
<td class="text-center" colspan="6">게시글이 존재하지 않습니다.</td>
</tr>
<%
} else {
for (Board board : boardList) {
%>
<tr class="d-flex">
<td class="col-1"><%=board.getNo() %></td>
<td class="col-5">
<%
if ("Y".equals(board.getDeleted())) {
%>
<span><del>삭제된 글입니다.</del></span>
<%
} else {
%>
<a href="detail.jsp?no=<%=board.getNo()%>&pageNo=<%=pagination.getPageNo()%>"><%=board.getTitle() %></a>
<%
}
%>
</td>
<td class="col-2"><%=board.getWriter().getName() %></td>
<td class="col-1"><%=board.getLikeCount() %></td>
<td class="col-1"><%=board.getViewCount() %></td>
<td class="col-2"><%=board.getCreatedDate() %></td>
</tr>
<%
}
}
%>
</tbody>
</table>
</div>
</div>
<div class="row mb-3">
<div class="col-6 offset-3">
<nav>
<ul class="pagination justify-content-center">
<li class="page-item <%=!pagination.isExistPrev() ? "disabled" : "" %>"><a class="page-link" href="list.jsp?pageNo=<%=pagination.getPrevPage()%>" >이전</a></li>
<%
// Pagination 객체로부터 해당 페이지 블록의 시작 페이지번호와 끝 페이지번호만큼 페이지내비게이션 정보를 표시한다.
for (int num = pagination.getBeginPage(); num <= pagination.getEndPage(); num++) {
%>
<li class="page-item <%=pagination.getPageNo() == num ? "active" : "" %>"><a class="page-link" href="list.jsp?pageNo=<%=num%>"><%=num %></a></li>
<%
}
%>
<li class="page-item <%=!pagination.isExistNext() ? "disabled" :"" %>"><a class="page-link" href="list.jsp?pageNo=<%=pagination.getNextPage()%>" >다음</a></li>
</ul>
</nav>
</div>
<div class="col-3 text-end">
<%
// 로그인되지 않은 경우 새 글 버튼이 출력되지않는다.
if (loginUserInfo != null) {
%>
<a href="form.jsp" class="btn btn-outline-primary">새 글</a>
<%
}
%>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>