[ 요구 사항 ]
Model2 MVC 기반 커뮤니티 게시판
비로그인 상태에서는 '게시판 리스트' 정보만 제공된다.
상단부 화면에서 '로그인 할 수 있는 폼'이 제공된다.
'게시판 리스트'에서는 '게시물 번호', '제목', '작성자', '작성일', '조회수'가 제공된다.
또한 게시물을 최신 등록순으로 정렬되어 제공된다.
로그인 상태에서는 상단부 로그인 폼 대신 '홈' 링크, '글쓰기' 링크, '사용자 이름'과 '로그아웃' 링크가 제공된다.
상단부에 '게시판 리스트' 부분에 '제목'이 링크로 활성화되어, '게시글 상세보기'가 제공된다.
1) '게시물 상세보기'에서는 '게시물 번호','제목', '작성자', '작성일시', '조회수', '게시물 본문 내용'이 제공된다.
2) '게시판 리스트' 화면에서는 '게시글 상세보기'를 위해 '제목'에 클릭하여 상세글을 보는 시점에서 '조회수'가 증가한다.
단, 자신이 쓴 글을 등록하거나 수정할 떄는 게시글 조회수가 증가하지 않는다.
3) 자신이 작성한 글에 대해서만 '삭제'와 '수정' 버튼이 제공된다.
- 자신이 작성한 글에서 '삭제' 버튼을 누르면, cofirm후 1)삭제 + 2)'게시판 리스트'로 이동한다.
- 자신이 작성한 글에서 '수정' 버튼을 누르면, cofirm후 '수정 폼' 제공한다.
'수정 폼'은 '제목'과 '본문내용'을 수정할 수 있고,
'수정 완료' 버튼을 누르면 → 1) '수정' 반영하고, 2)'수정된 글'의 '게시글 상세보기'로 이동한다.
상단부에 '로그아웃' 링크를 누르면 '로그아웃 하시겠습니까?' confirm 후,
확인이면→ 1)로그아웃 처리되고, 2)홈(게시물 리스트 화면)으로 이동한다.
상단부에 '글쓰기' 링크를 클릭해서, 게시글을 작성할 때는, '제목'과 '본문 내용'을 작성해서
'글쓰기' 버튼을 누르면 → 1)게시글이 등록되고, 2)자신이 작성한 '게시글 상세보기' 화면이 제공된다.
+ 추가 요구사항 ) 다음 포스팅에 기재될 예정
페이징 처리
→ 게시물 리스트 하단부에 페이지 그룹은 '4 페이지'로,
→ 페이지 그룹 당 게시물 수는 '5 개'로 처리한다.
[ 요구 분석 - Usecase Diagram (UML) ]
[ 설계 ]
1. DB Modeling (ERD - 논리/물리) ]
데이터 모델링( 논리 / 물리 )에 대한 포스팅 을 참고하세요!
논리 데이터 모델링 : 관계
물리 데이터 모델링 : 특성 및 성능 (속성)
[ 논리 데이터 모델링 ]
멤버 Entity --비식별관계-- 게시물Entity
게시물은 반드시 멤버가 있어야함
멤버는 게시물이 없거나, 한 개이거나, 여러 개일 수 있음.
[ 물리 데이터 모델링 ]
MEMBER table
- ID : NUMBER / PRIMARY KEY
- NAME: VARCHAR2(100)
- PASSWORD: VARCHAR2(100)
BOARD_LIST table
- BOARD_NO: NUMBER / PRIMARY KEY
- TITLE : VARCHAR2(100)
- ID : VARCHAR2(100) / FORIEGN KEY (작성자)
- REGDATE: DATE
- CONTENT: CROB
- VIEW_COUNT: NUMBER
2. Application Modeling
1) Class Diagram (UML) - Model
2) File List - Controller
[ 구상 순서 ]
- > Model2 Pattern 적용을 위한 front Servlet / controller interface / controller 객체 생성 Factory 를 준비한다.
(역순 생성)
HandlerMapping.java > controller.java ( interface ) > DistpatcherSevlet.java 를 controller 폴더에 구성한다. - index.jsp를 만들고, jsp:forward 방식으로 command=home일 때, homeController가 바로 작동되도록 한다.
(*HomeController는 게시글 리스트를 보여준다.) - > DB와 연동되는 Model부분을 만든다.
1) 우선 JDBC 인터페이스에서 Connection Pool을 미리 이용하기 위해, DBCP 방법을 사용한다.
- DataSourceManager.java 생성
2) 그 다음 JavaBeans들을 생성한다.
- MemberVO.java > PostVO.java > MemberDAO.java (로그인을 위해) > BoardDAO.java - BoardDAO에는 HomeController에서 작동 할,
- 게시글(posting)들의 리스트를 보여주는 역할을 하는 메서드를 생성한다. ( getAllList() method )
(*그 전에 DBCP 준비 필요! + singleton pattern 적용!)
(* closeAll method 도 준비 !! ) - HomeController에서 DB(Model)과 View를 연동해준다.
(url에는 list.jsp를 할당, list에는 DAO를 통해 받아온 게시물 리스트를 할당한다.) - list.jsp를 생성하여, 게시물 리스트를 테이블 형식으로 만든다.
- template인 header.jsp와 layout.jsp를 만든다.
(우선은 비로그인 상태라고 가정하고 만든다.) -> login Form - MemberDAO에 login method를 추가한다.
- HandlerMapping에 "login"이 입력되면, LoginController가 작동된다.
- login 실패 시에는 member/login-fail.jsp에서 js가 작동된다. (alert 후, 다시 index로)
- header.jsp에
1) 비로그인 시 - 로그인 폼
2) 로그인 시 - 로그인 + '홈' 링크, '글쓰기' 링크, '사용자 이름'과 '로그아웃' 링크 만들기 - 로그아웃 링크 - command=logout → LogoutController (로그인상태면- 세션 invalidate) → index
- '홈' 링크 - commnad=home
- list.jsp의 '제목' 부분 활성화되어, 게시글 상세보기 제공
- BoardDAO에 getPostDetailByNo() method 만들기
- +조회수 증가 (addHits() method) 추가
- command = PostDetail > PostDetailController > post-detail.jsp - '글쓰기' 링크 - command=WritePostForm
1) WritePostFormController.java -> write-post-form.jsp / command=WritePost
2) WritePostController.java (DB에 쓴 글 저장) : BoardDAO에 writePost() 메서드 추가
- command = PostDetailNoHits
3) PostDetailNoHitsController.java - 조회수 증가 안함 > post-detail.jsp (본인이 쓴 글의 상세)
* 본인 글에는 조회수 증가 X / 본인 글이 아닌 경우 조회수 증가 O
>본인 글인 경우 : command = PostDetail > PostDetailController
> 본인 글이 아닌 경우 : command = PostDetailNoHits > PostDetailNoHitsController - 본인 글일 경우 '삭제'와 '수정'하기
- '삭제' 하기
1) post-detail.jsp 수정 (본인 글인 경우 '수정' '삭제' 버튼 뜨도록)
2) '삭제' 버튼 누르면 (onclick) : js에서 confirm 실행 - 확인 : 삭제 (commnad=RemovePost) / 취소 : return
-> RemovePostController.java / DAO에 removePost() 메서드 추가 -> command=home 으로 - '수정'하기
1) '수정' 버튼에서 확인 누르면 -> command=UpdatePostForm & no = 현재 포스트의 no 전송
2) UpdatePostFormController.java -> update-post.jsp 실행 / command=UpdatePost
3) UpdatePostController.java (글 새로 업데이트) : BoardDAO에 updatePost() 메서드 추가
- redirect 방식으로 command=PostDetailNoHits로 가기 -> post-detail.jsp
3. UI Modeling - 화면 구성
( 생략 )
[ SQL ]
1. CREATE TABLE (board_member -> board)
CREATE TABLE board_member (
id VARCHAR2(100) PRIMARY KEY,
password VARCHAR2(100) NOT NULL,
name VARCHAR2(100) NOT NULL
)
CREATE SEQUENCE board_seq;
CREATE TABLE board(
no NUMBER PRIMARY KEY,
title VARCHAR2(100) NOT NULL,
content CLOB NOT NULL,
hits NUMBER DEFAULT 0,
time_posted DATE NOT NULL,
id VARCHAR2(100) NOT NULL,
CONSTRAINT board_fk FOREIGN KEY(id) REFERENCES board_member(id)
)
2. INSERT VALUES
INSERT INTO board_member(id, password, name) VALUES('green', 'green', '파프리카');
INSERT INTO board_member(id, password, name) VALUES('orange', 'orange', '당근');
INSERT INTO board_member(id, password, name) VALUES('red', 'red', '토마토');
3. board_member TABLE
[ 구현 코드 ]
Model
/DataBaseManager.java
package model;
import javax.sql.DataSource;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;
public class DataSourceManager {
//1. singleton pattern
private static DataSourceManager instance = new DataSourceManager();
//DataSource는 java.sql에서의 interface를 가져온다.
private DataSource dataSource;
//2. constructor에 Connection Pool 생성되도록
private DataSourceManager() {
//was tomcat에서 제공하는 dbcp를 생성한다.
BasicDataSource dbcp = new BasicDataSource();
//dbcp(datasource)에 driver와 db connection 정보를 설정
dbcp.setDriverClassName("oracle.jdbc.OracleDriver");
dbcp.setUrl("jdbc:oracle:thin:@127.0.0.1:1521:xe");
dbcp.setUsername("scott");
dbcp.setPassword("tiger");
//setInitialSize( ) : 처음 로드될 때 생성할 connection 개수 설정
dbcp.setInitialSize(3);
//setMaxTotal( ) : 사용할 최대 connection 개수 설정
dbcp.setMaxTotal(10);
//인스턴스 변수인 dataSource에 위를 통해 설정한 dbcp를 할당한다
this.dataSource = dbcp;
}
//3. singleton pattern
public static DataSourceManager getInstance() {
return instance;
}
//4. DataBase Connection Pool을 만들어둔,
// dpcp(DataSource)에 설정해 놓은 dbcp를 dataSource의 이름으로
// 가져온다.
public DataSource getDataSource() {
return dataSource;
}
}
> Member
/MemebrVO.java
package model;
public class MemberVO {
private String id;
private String password;
private String name;
public MemberVO() {
super();
}
public MemberVO(String id, String password, String name) {
super();
this.id = id;
this.password = password;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "MemberVO [id=" + id + ", password=" + password + ", name=" + name + "]";
}
}
/MemberDAO.java
package model;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.sql.DataSource;
public class MemberDAO {
//singleton pattern
private static MemberDAO instance = new MemberDAO();
private DataSource dataSource;
private MemberDAO() {
dataSource = DataSourceManager.getInstance().getDataSource();
}
public static MemberDAO getInstance() {
return instance;
}
//closeAll(rs, pstmt, con) method
public void closeAll(ResultSet rs, PreparedStatement pstmt, Connection con)
throws SQLException {
if(rs != null)
rs.close();
if (pstmt != null)
pstmt.close();
// Connection을 dbcp에 반납한다(소멸하는 것이 아니다!)
if (con != null)
con.close();
}
/*
* login method
* login(String id, String password) : MemberVO
*/
public MemberVO login(String id, String password) throws SQLException {
MemberVO memberVO = null;
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = dataSource.getConnection();
String sql = "SELECT * FROM board_member "
+ "WHERE id=? AND password=?";
pstmt = con.prepareStatement(sql);
pstmt.setString(1, id);
pstmt.setString(2, password);
rs = pstmt.executeQuery();
if (rs.next())
memberVO = new MemberVO(id, password, rs.getString("name"));
} finally {
closeAll(rs, pstmt, con);
}
return memberVO;
}
}
> Board
/PostVO.java
package model;
public class PostVO {
private String no;
private String title;
private String content;
private int hits ;
private String timePosted;
private MemberVO memberVO;
public PostVO() {
super();
}
public PostVO(String no, String title, String content, int hits, String timePosted, MemberVO memberVO) {
super();
this.no = no;
this.title = title;
this.content = content;
this.hits = hits;
this.timePosted = timePosted;
this.memberVO = memberVO;
}
public String getNo() {
return no;
}
public void setNo(String no) {
this.no = no;
}
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 int getHits() {
return hits;
}
public void setHits(int hits) {
this.hits = hits;
}
public String getTimePosted() {
return timePosted;
}
public void setTimePosted(String timePosted) {
this.timePosted = timePosted;
}
public MemberVO getMemberVO() {
return memberVO;
}
public void setMemberVO(MemberVO memberVO) {
this.memberVO = memberVO;
}
@Override
public String toString() {
return "PostVO [no=" + no + ", title=" + title + ", content=" + content + ", hits=" + hits + ", timePosted="
+ timePosted + ", memberVO=" + memberVO + "]";
}
}
/BoardDAO.java
package model;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import javax.sql.DataSource;
public class BoardDAO {
private static BoardDAO instance = new BoardDAO();
//DBCP에서 connection pool을 이용한다
//(dataSource는 dbcp에 여러 정보들을 담아놓은 변수이다)
private DataSource dataSource;
// 생성자에 private를 명시하여 외부해서 생성하지 못하게 막는다
private BoardDAO() {
/* DBCP를 통해 driver loading & connection pool 생성 */
//이 DataSourceManager에 있는 dataSource는
// 이미 driver/user/password로 연결이 되어있는 상태이다.
this.dataSource = DataSourceManager.getInstance().getDataSource();
}
// public static 메서드로 외부에 객체 생성을 공유한다.
public static BoardDAO getInstance() {
return instance;
}
//closeAll(pstmt, con) method
public void closeAll(PreparedStatement pstmt, Connection con)
throws SQLException {
if (pstmt != null)
pstmt.close();
// Connection을 dbcp에 반납한다(소멸하는 것이 아니다!)
if (con != null)
con.close();
}
//closeAll(rs, pstmt, con) method
public void closeAll(ResultSet rs, PreparedStatement pstmt, Connection con)
throws SQLException {
if (rs != null)
rs.close();
this.closeAll(pstmt, con);
}
/**
* board에 있는 '게시판 리스트' 정보만 제공
* ('게시판 리스트'에서는 '게시물 번호', '제목', '작성자', '작성일', '조회수')
* getAllList() : PostVO
* @throws SQLException
*/
public ArrayList<PostVO> getAllList() throws SQLException {
ArrayList<PostVO> list = new ArrayList<PostVO>();
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = dataSource.getConnection();
StringBuilder sql = new StringBuilder();
sql.append("SELECT b.no, b.title, m.name, m.id, ");
sql.append("TO_CHAR(time_posted, 'YYYY-MM-DD') AS time_posted, b.hits ");
sql.append("FROM board b, BOARD_MEMBER m ");
sql.append("WHERE b.id = m.id ");
sql.append("ORDER BY b.no DESC");
pstmt = con.prepareStatement(sql.toString());
rs = pstmt.executeQuery();
while(rs.next()) {
MemberVO memberVO = new MemberVO();
memberVO.setId(rs.getString("id"));
memberVO.setName(rs.getString("name"));
PostVO postVO = new PostVO();
postVO.setNo(rs.getNString("no"));
postVO.setTitle(rs.getNString("title"));
postVO.setTimePosted(rs.getString("time_posted"));
postVO.setHits(rs.getInt("hits"));
postVO.setMemberVO(memberVO);
list.add(postVO);
}
} finally {
closeAll(rs, pstmt, con);
}
return list;
}
/**
* list.jsp에서 title을 누르면 게시글 상세보기 제공
* '게시물 번호','제목', '작성자', '작성일시', '조회수', '게시물 본문 내용'
* getPostDetailByNo(String No) : PostVO
* @throws SQLException
*/
public PostVO getPostDetailByNo(String no) throws SQLException {
PostVO postVO = null;
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = dataSource.getConnection();
StringBuilder sql = new StringBuilder();
sql.append("SELECT b.no, b.title, m.name, m.id, ");
sql.append("TO_CHAR(time_posted, 'YYYY-MM-DD HH24:MI:SS') AS time_posted, ");
sql.append("b.hits, b.content ");
sql.append("FROM board b, BOARD_MEMBER m ");
sql.append("WHERE b.id = m.id AND b.no=? ");
pstmt = con.prepareStatement(sql.toString());
pstmt.setString(1, no);
rs = pstmt.executeQuery();
if(rs.next()) {
MemberVO memberVO = new MemberVO();
memberVO.setId(rs.getString("id"));
memberVO.setName(rs.getString("name"));
postVO = new PostVO();
postVO.setNo(rs.getString("no"));
postVO.setTitle(rs.getString("title"));
postVO.setTimePosted(rs.getString("time_posted"));
postVO.setHits(rs.getInt("hits"));
postVO.setContent(rs.getString("content"));
postVO.setMemberVO(memberVO);
}
} finally {
closeAll(rs, pstmt, con);
}
return postVO;
}
/**
* title을 누르면 조회 수 증가 (hits)
* addHits(String no) : void
* @throws SQLException
*/
public void addHits(String no) throws SQLException {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = dataSource.getConnection();
String sql = "UPDATE BOARD SET hits=1 WHERE no=?";
pstmt = con.prepareStatement(sql);
pstmt.setString(1, no);
pstmt.executeUpdate();
} finally {
closeAll(pstmt, con);
}
}
/**
* 새로운 게시글 등록 기능
* + 게시물 등록 후 생성된 시퀀스(게시글 no)를 BoardVO에 setting 한다
* writePost(PostVO) : void
* @throws SQLException
*/
public void writePost(PostVO postVO) throws SQLException {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = dataSource.getConnection();
StringBuilder sql=new StringBuilder();
sql.append("insert into board(no,title,content,id,time_posted) ");
sql.append("values(board_seq.nextval,?,?,?,sysdate)");
pstmt = con.prepareStatement(sql.toString());
pstmt.setString(1, postVO.getTitle());
pstmt.setString(2, postVO.getContent());
pstmt.setString(3, postVO.getMemberVO().getId());
pstmt.executeUpdate();
pstmt.close();
String sql2 = "SELECT board_seq.currval FROM dual";
pstmt = con.prepareStatement(sql2);
rs = pstmt.executeQuery();
if(rs.next())
//기존에 받아온 postVO의 no를
//새로 만든 게시글 번호(현재 시퀀스)로 재할당해준다.
postVO.setNo(rs.getString(1));
} finally {
closeAll(rs, pstmt, con);
}
}
/**
* '삭제'버튼을 누르면 해당 게시글 삭제 기능
* removePost(String no) : void
* @throws SQLException
*/
public void removePost(String no) throws SQLException {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = dataSource.getConnection();
String sql = "DELETE board WHERE no=?";
pstmt = con.prepareStatement(sql);
pstmt.setString(1, no);
pstmt.executeUpdate();
} finally {
closeAll(pstmt, con);
}
}
/**
* 게시글 수정하기
* updatePost(PostVO postVO) : void
* @throws SQLException
*/
public void updatePost(PostVO postVO) throws SQLException {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = dataSource.getConnection();
String sql = "UPDATE BOARD SET title=?, content=? "
+ "WHERE no=?";
pstmt = con.prepareStatement(sql);
pstmt.setString(1, postVO.getTitle());
pstmt.setString(2, postVO.getContent());
pstmt.setString(3, postVO.getNo());
pstmt.executeUpdate();
} finally {
closeAll(pstmt, con);
}
}
}//class
> Paging
- 다음 포스팅에 기재 -
/PagingBean.java
/ListVO.java
View
[ template ]
/index.jsp
index를 실행하면 <jsp:forward> 방식으로 layout.jsp로 간다
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<jsp:forward page="front">
<jsp:param value="home" name="command"/>
</jsp:forward>
/template/header.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<script type="text/javascript">
function logout() {
if (confirm("로그아웃 하시겠습니까?")) {
location.href = "${pageContext.request.contextPath}/front?command=logout";
}
}
</script>
<c:choose>
<c:when test="${sessionScope.memberVO == null}">
<form action="front" method="POST">
<input type="hidden" name="command" value="login">
<input type="text" name="id" required="required" placeholder="아이디">
<input type="password" name="password" required="required"
placeholder="패스워드">
<input type="submit" value="로그인">
</form>
</c:when>
<c:otherwise>
<a href="${pageContext.request.contextPath}/front?command=home">HOME</a>
<a href="${pageContext.request.contextPath}/front?command=WritePostForm">글쓰기</a>
<strong>${sessionScope.memberVO.name}</strong>님 로그인
<a href="javascript:logout()">로그아웃</a>
</c:otherwise>
</c:choose>
/template/layout.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" session="false"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시판 HOME</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script
src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script
src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/myhome.css"/>
</head>
<body>
<div class="container-fluid">
<%-- "row", "col-[size]-[num] : 한 행의 열을 잡아줌 --%>
<%-- Header 영역 --%>
<div class="row header">
<div class="col-sm-8 col-sm-offset-2" align="right">
<%-- JSTL import --%>
<c:import url="/template/header.jsp"></c:import>
</div>
</div>
<%-- Main 화면 영역 --%>
<div class="row main">
<%-- Main 동작 영역 --%>
<div class="col-sm-8 col-sm-offset-2">
<%--
메인 화면에 대한 view 정보(url or jsp파일명)를 컨트롤러에서
동적으로 할당받는다
--%>
<c:import url="${requestScope.url}"/>
</div>
</div>
</div>
</body>
</html>
[ CSS ]
/css/myhome.css
@charset "UTF-8";
.header {
padding-top: 20px;
padding-bottom: 30px;
}
.title {
width: 50%;
}
.boardlist th,.boardlist td,.btnArea,.pagingArea{
text-align: center;
}
-- 기능 1 : 게시판 리스트
'게시물 번호', '제목', '작성자', '작성일', '조회수' 제공
로그인 상태일 때, '제목'이 링크 활성화 되어, 게시글 상세보기로 넘어간다.
/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"%>
<!DOCTYPE html>
<html>
<head>
<title>board</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/myhome.css">
</head>
<body>
<table class="table table-hover boardlist">
<thead>
<tr align="center" class="warning">
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>작성일</th>
<th>조회수</th>
</tr>
</thead>
<tbody>
<c:forEach items="${requestScope.list}" var="post">
<tr>
<td>${post.no}</td>
<c:choose>
<c:when test="${sessionScope.memberVO != null}">
<td>
<a href="${pageContext.request.contextPath}/front?command=PostDetail&no=${post.no}">
${post.title}
</a>
</td>
</c:when>
<c:otherwise>
<td>${post.title}</td>
</c:otherwise>
</c:choose>
<td>${post.memberVO.name}</td>
<td>${post.timePosted}</td>
<td>${post.hits}</td>
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>
기능 2 : 게시글 상세보기
/board/post-detail.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<script>
function removePost() {
if(confirm("게시글을 삭제하시겠습니까?")){
document.removeForm.submit();
} else {
return;
}
}
function updatePost() {
if(confirm("게시글을 수정하시겠습니까??")){
document.updateForm.submit();
} else {
return;
}
}
</script>
<h3 align="left"><${requestScope.postVO.title}>의 상세정보</h3>
<ul class="list-group detailList">
<li class="list-group-item"><strong>게시물 번호</strong></li>
<li class="list-group-item">${requestScope.postVO.no}</li>
<li class="list-group-item"><strong>제목</strong></li>
<li class="list-group-item">${requestScope.postVO.title}</li>
<li class="list-group-item"><strong>작성자</strong></li>
<li class="list-group-item">${requestScope.postVO.memberVO.name}</li>
<li class="list-group-item"><strong>작성일시</strong></li>
<li class="list-group-item">${requestScope.postVO.timePosted}</li>
<li class="list-group-item"><strong>조회수</strong></li>
<li class="list-group-item">${requestScope.postVO.hits}</li>
<li class="list-group-item"><strong>줄거리</strong></li>
<li class="list-group-item"><pre>${requestScope.postVO.content}</pre></li>
<c:if test="${requestScope.postVO.memberVO.id == sessionScope.memberVO.id}">
<form name="removeForm"
action="${pageContext.request.contextPath}/front" method="POST">
<input type="hidden" name="command" value="RemovePost">
<input type="hidden" name="no" value="${requestScope.postVO.no}">
</form>
<form name="updateForm"
action="${pageContext.request.contextPath}/front" method="POST">
<input type="hidden" name="command" value="UpdatePostForm">
<input type="hidden" name="no" value="${requestScope.postVO.no}">
</form>
<li class="list-group-item" align="right">
<button type="button" class="btn btn-info" onclick="updatePost()">
수정</button>
<button type="button" class="btn btn-danger" onclick="removePost()">
삭제</button>
</li>
</c:if>
</ul>
기능 3 : 로그인 + 로그아웃
로그인 실패 시
/member/login-fail.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<script type="text/javascript">
alert("로그인에 실패했습니다!\n\n아이디와 비밀번호를 확인해주세요.");
location.href ="${pageContext.request.contextPath}/index.jsp";
</script>
로그아웃 클릭하면, 여부 물어볼 javascript
/member/logout.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<script type="text/javascript">
if (confirm("로그아웃 하시겠습니까?")) {
location.href = "${pageContext.request.contextPath}/front?command=home"
} else {
location.href = "${pageContext.request.contextPath}/index.jsp"
}
</script>
기능 4 : 게시글 등록
/board/write-post-form.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<form action="${pageContext.request.contextPath}/front" method="POST">
<input type="hidden" name="command" value="WritePost">
<table class="table">
<tr>
<td>
<h4>제목</h4>
<input type="text" name="title"
placeholder="게시글 제목을 입력하세요"
required="required">
</td>
</tr>
<tr>
<td>
<h4>본문 내용</h4>
<textarea cols="90" rows="15" name="content"
required="required"
placeholder=" 본문내용을 입력하세요"></textarea>
</td>
</tr>
<tr>
<td>
<div class="btnArea">
<button type="submit" class="btn-success">게시글 올리기</button>
<button type="reset" class="btn-warning">다시 쓰기</button>
</div>
</td>
</tr>
</table>
</form>
기능 5 : 게시글 수정
/board/update-post-form.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<form action="${pageContext.request.contextPath}/front" method="POST" >
<input type="hidden" name="command" value="UpdatePost"></input>
<input type="hidden" name="no" value="${postVO.no}"></input>
<table class="table">
<tr>
<td>
<strong>제목</strong>
<br>
<input type="text" name="title" value="${postVO.title}"
required="required">
</td>
</tr>
<tr>
<td>
<strong>줄거리</strong>
<br>
<textarea cols="90" rows="15" name="content"
required="required">${postVO.content}</textarea>
</td>
</tr>
<tr>
<td colspan="2">
<div class="btnArea">
<button type="submit" class="btn btn-success">수정 완료</button>
<button type="reset" class="btn btn-warning">글 초기화</button>
</div>
</td>
</tr>
</table>
</form>
Controller
/Controller.java
package controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface Controller {
//execute(request, response) : String -> url
public String execute(HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
/ HandlerMapping.java
package controller;
public class HandlerMapping {
private static HandlerMapping instance = new HandlerMapping();
private HandlerMapping() {}
public static HandlerMapping getInstance() {
return instance;
}
public Controller create(String command) {
Controller controller = null;
if (command.contentEquals("home"))
controller = new HomeController();
else if (command.contentEquals("login"))
controller = new LoginController();
else if (command.contentEquals("logout"))
controller = new LogoutController();
else if (command.contentEquals("PostDetail"))
controller = new PostDetailController();
else if (command.contentEquals("PostDetailNoHits"))
controller = new PostDetailNoHitsController();
else if (command.contentEquals("WritePostForm"))
controller = new WritePostFormController();
else if (command.contentEquals("WritePost"))
controller = new WritePostController();
else if (command.contentEquals("RemovePost"))
controller = new RemovePostController();
else if (command.contentEquals("UpdatePostForm"))
controller = new UpdatePostFormController();
else if (command.contentEquals("UpdatePost"))
controller = new UpdatePostController();
return controller;
}
}
/DispatcherServlet.java
handleRequest method 작동 방식
1. 에러(예외) 발생 시, 콘솔에 메세지 발생 경로 출력 후, error.jsp로 redirect 방식으로 view로 이동한다.
2. 클라이언트가 전송한 command 정보를 받는다.
3. HandlerMapping 을 이용해 개별 컨트롤러 객체를 컨트롤러 인터페이스 타입으로 반환받는다.
4. 개별 컨트롤러를 실행한다.
5. 실행 후 반환된 String url 정보를 이용해,
forward 방식 or redirect 방식으로 view로 이동해 응답하게 한다.
package controller;
import java.io.IOException;
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("/front")
public class DispatcherServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.HandleRequest(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
this.HandleRequest(request, response);
}
public void HandleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
//view에서 보낸 command를 받는다.
String command = request.getParameter("command");
//HandlerMapping을 이용해 개별 컨트롤러 객체를 컨트롤러 인터페이스 타입으로 반환받는다.
Controller controller = HandlerMapping.getInstance().create(command);
//개별 컨트롤러를 실행한다.
String url = controller.execute(request, response);
// 실행 후 반환된 String url 정보를 이용해,
//forward 방식 or redirect 방식으로 view로 이동해 응답하게 한다.
if(url.startsWith("redirect:")) {
response.sendRedirect(url.substring(9));
} else {
request.getRequestDispatcher(url).forward(request, response);
}
} catch (Exception e) {
//error 발생 시, console에 에러 메세지를 표기하고,
e.printStackTrace();
//error.jsp로 이동한다.
response.sendRedirect("error.jsp");
}
}
}
기능 1 : 게시물 리스트 (command=home)
/HomeController.java
package controller;
import java.util.ArrayList;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import model.BoardDAO;
import model.PostVO;
public class HomeController implements Controller {
@Override
public String execute(HttpServletRequest request,
HttpServletResponse response) throws Exception {
//list에 게시물 목록 리스트를 담음
ArrayList<PostVO> list = BoardDAO.getInstance().getAllList();
//request에 담아 list로 공유
request.setAttribute("list", list);
//url은 list.jsp로 하고, layout이 적용되도록 한다.
request.setAttribute("url", "/board/list.jsp");
return "/template/layout.jsp";
}
}
기능 2 : 게시물 상세 보기 Controller
command = postDetail : 본인 외의 게시물을 보았을 때, 조회수가 증가함
command = postDetailNoHits : 본인의 게시물을 보았을 때, 조회수가 증가하지 않음
/PostDetailController.java
package controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import model.BoardDAO;
import model.PostVO;
public class PostDetailController implements Controller {
@Override
public String execute(HttpServletRequest request,
HttpServletResponse response) throws Exception {
HttpSession session=request.getSession(false);
if(session==null||session.getAttribute("memberVO")==null){
return "redirect:index.jsp";
}
String no = request.getParameter("no");
//조회수 증가
BoardDAO.getInstance().addHits(no);
//게시글 상세정보
PostVO postVO = BoardDAO.getInstance().getPostDetailByNo(no);
//상세정보 공유 (조회수 증가함)
request.setAttribute("postVO", postVO);
//url에 상세정보 페이지로 이동
request.setAttribute("url", "/board/post-detail.jsp");
return "/template/layout.jsp";
}
}
/PostDetailNohitsController.java
package controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import model.BoardDAO;
import model.PostVO;
public class PostDetailNoHitsController implements Controller {
@Override
public String execute(HttpServletRequest request,
HttpServletResponse response) throws Exception {
HttpSession session=request.getSession(false);
if(session==null||session.getAttribute("memberVO")==null){
return "redirect:index.jsp";
}
//WritePostController에서 보낸 no받음
String no = request.getParameter("no");
//조회수 증가 안함
//게시글 상세정보
PostVO postVO = BoardDAO.getInstance().getPostDetailByNo(no);
//상세정보 공유 (조회수 증가 안함)
request.setAttribute("postVO", postVO);
//url에 상세정보 페이지로 이동
request.setAttribute("url", "/board/post-detail.jsp");
return "/template/layout.jsp";
}
}
기능 3 : 로그인 + 로그아웃 Controller
/LoginController.java
package controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import model.MemberDAO;
import model.MemberVO;
public class LoginController implements Controller {
@Override
public String execute(HttpServletRequest request,
HttpServletResponse response) throws Exception {
String id = request.getParameter("id");
String password = request.getParameter("password");
MemberVO memberVO = MemberDAO.getInstance().login(id, password);
//memberVO가 있다면
if (memberVO != null) {
//session 새로 만들기
HttpSession session = request.getSession();
//session에 로그인 정보 넣기
session.setAttribute("memberVO", memberVO);
//다시 index로 보내기
return "redirect:index.jsp";
} else { //memberVO가 없다면
return "redirect:member/login-fail.jsp";
}
}
}
/LogoutController.java
package controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LogoutController implements Controller {
@Override
public String execute(HttpServletRequest request,
HttpServletResponse response) throws Exception {
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("memberVO")!= null) {
session.invalidate();
}
return "redirect:index.jsp";
}
}
기능 4 : 게시글 등록 기능 Controller
/WritePostFormController.java
package controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class WritePostFormController implements Controller {
@Override
public String execute(HttpServletRequest request,
HttpServletResponse response) throws Exception {
//로그인 여부 체크
HttpSession session=request.getSession(false);
if(session==null||session.getAttribute("memberVO")==null){
return "redirect:index.jsp";
}
request.setAttribute("url", "/board/write-post-form.jsp");
return "/template/layout.jsp";
}
}
/WritePostController.java
package controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import model.BoardDAO;
import model.MemberVO;
import model.PostVO;
public class WritePostController implements Controller {
@Override
public String execute(HttpServletRequest request,
HttpServletResponse response) throws Exception {
HttpSession session=request.getSession(false);
//로그인 여부 체크 + POST방식으로 form을 보냈는지 체크
if(session==null||session.getAttribute("memberVO")==null ||
request.getMethod().equals("POST")==false) {
return "redirect:index.jsp";
}
/*게시글 등록*/
//1. write-post-form.jsp에서 title과 content 받아오기
String title = request.getParameter("title");
String content = request.getParameter("content");
//2. postVO에 새로 입력받은 글로 새로운 postVO 만들기
PostVO postVO = new PostVO();
postVO.setTitle(title);
postVO.setContent(content);
//3. session에 있는 MemberVO도 postVO에 넣어주기
// (다운캐스팅 필요)
postVO.setMemberVO((MemberVO)session.getAttribute("memberVO"));
//4. 로그인 정보와 글 정보를 담은 postVO를 작성하기
// - 이 과정에서 자동으로 no와 date가 postVO에 입력된다
BoardDAO.getInstance().writePost(postVO);
//5. veiw-post-detail-noHit.jsp (조회수 없는 게시판 리스트 뷰 페이지)에
// PostVO의 no 정보를 담아 보내기 (redirect 방식으로)
// -> why? 해당 게시물의 no 정보를 받기 위해서
// **path에 담아 return 한다!**
String path = "redirect:front?command=PostDetailNoHits&no"+postVO.getNo();
return path;
}
}
기능 5 : 본인 게시물 수정 기능 Controller
/UpdatePostFormController.java
package controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import model.BoardDAO;
import model.PostVO;
public class UpdatePostFormController implements Controller {
@Override
public String execute(HttpServletRequest request,
HttpServletResponse response) throws Exception {
HttpSession session=request.getSession(false);
if(session==null||session.getAttribute("memberVO")==null){
return "redirect:index.jsp";
}
String no = request.getParameter("no");
PostVO postVO = BoardDAO.getInstance().getPostDetailByNo(no);
request.setAttribute("postVO", postVO);
request.setAttribute("url", "/board/update-post-form.jsp");
return "/template/layout.jsp";
}
}
/UpdatePostController.java
package controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import model.BoardDAO;
import model.PostVO;
public class UpdatePostController implements Controller {
@Override
public String execute(HttpServletRequest request,
HttpServletResponse response) throws Exception {
HttpSession session=request.getSession(false);
if(session==null||session.getAttribute("memberVO")==null||
request.getMethod().equals("POST")==false){
return "redirect:index.jsp";
}
String title = request.getParameter("title");
String content = request.getParameter("content");
PostVO postVO = new PostVO();
postVO.setNo(request.getParameter("no"));
postVO.setTitle(title);
postVO.setContent(content);
BoardDAO.getInstance().updatePost(postVO);
String path = "redirect:front?command=PostDetailNoHits&no="+postVO.getNo();
return path;
}
}
기능 6 : 본인 게시물 삭제 기능 Controller
/RemovePostController.java
package controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import model.BoardDAO;
public class RemovePostController implements Controller {
@Override
public String execute(HttpServletRequest request,
HttpServletResponse response) throws Exception {
HttpSession session=request.getSession(false);
if(session==null||session.getAttribute("memberVO")==null){
return "redirect:index.jsp";
}
String no = request.getParameter("no");
BoardDAO.getInstance().removePost(no);
return "redirect:front?command=home";
}
}
[ 브라우저 화면 ]
기능 1. 게시물 리스트가 보이는 첫 화면 (로그인X)
-> 로그인 상태 (제목이 링크로 활성화됨 & header가 변화함)
기능 2. 게시물 상세보기 (제목을 클릭해서 들어감)
2_1. 본인 글이 아닌 경우
2_2. 본인 글인 경우 하단에 '삭제'와 '수정'버튼이 있음
기능 3. 로그인 / 로그아웃
- 로그인은 로그아웃 폼을 통해 할 수 있음
- 로그아웃 버튼을 누른 후, 아래와 같은 화면이 나온 뒤 '확인'을 누르면, index.jsp로 이동한다.
기능 4. 게시글 등록 (게시글 폼)
4_1. '글쓰기'버튼을 누르면, 글쓰기 폼이 제공된다.
4_2. '게시글 올리기'를 누르면, 내가 쓴 게시글의 상세보기 화면이 나온다.
4_2. '다시 쓰기'버튼을 누르면, 지금까지 쓴 내용이 사라진다.
기능 5. 게시글 수정 (본인의 게시물인 경우)
5_1. 게시글 수정 FORM
5_2. '수정 완료'를 누르면, 방금 수정한 글의 '게시물 상세보기'가 뜬다.
5_3. '글 초기화' 버튼을 누르면, 수정 전 초기 상태의 글이 나온다.
기능 6. 게시글 삭제 (본인의 게시물인 경우)
6_1. '삭제' 버튼을 누르면, '삭제하시겠습니까?'하는 javaScript가 뜬다.
6_2. '확인'을 누르면, home화면으로 돌아간다.
'Java Web Programming > 4. JSP' 카테고리의 다른 글
[JSP] 장바구니 웹 어플리케이션 (세션 이용) (0) | 2020.09.23 |
---|---|
[JSP] 회원관리 웹 어플리케이션 3 (layout-한가지로, home에 기본 main화면 제공) + (로그인, 로그아웃, 회원가입, (2) | 2020.09.22 |
[JSP] 회원관리 웹 어플리케이션2 - layout 두 가지 (로그인 + 회원가입 + 회원정보 수정 + 경로 + EL/JSTL + BootStrap + Model2 설계) (0) | 2020.09.21 |
[JSP] 회원 관리 웹 어플리케이션 (EL/JSTL 적용, 웹 프로그램 경로, BootStrap + Model2 설계) (0) | 2020.09.18 |
[JSP] EL / JSTL 표현식 한 방에 정리 ! (문법과 사용방법) (3) | 2020.09.16 |