해당 포스팅에서는 로직을 MVC로 나누는 연습을 위해 미니멀 게시판을 구현해볼 예정이다. 작업 순서는 다음과 같다.
[MVC 작업]
1. 설계
1) ERD
2) 비즈니스메서드
3) 필요 페이지
2. model
1) DB생성
2) JDBCUtill
3) VO
4) DAO
3. index.jsp
4. controller.jsp
1) 분기점 설정
2) 에러페이지
5. 메인페이지
6. 상세보기 페이지
7. 글 작성 페이지
[MVC 작업]
자바를 활용하여 MVC작업을 진행했을때의 과정을 다시 상기시켜보며 내용을 이해하는게 좋다. 순서는 다음과 같다.
controller에서 시작 메서드를 작동하는 순간 V에서 화면 출력 및 입력값을 받아온다.
view의 입력값을 controller에서 어떤 model에 적용할지 생각해서 알맞는 모델 로직에 그것을 전달하면
모델은 그에 맞게 출력해주고, 출력값은 컨트롤러가 전달받아 다시 view에게 전달한다.
1. 설계
코드를 작성하기 전 기본적인 설계 진행이 필요하였다.
1) ERD
사용되는 테이블은 board테이블 하나로, 구성요소는 pk, 작성자, 내용, 작성일로 총 4개이다. 정리하면 아래와 같다.
board테이블생성
bid int pk
writer varchar(20) not null
content varchar(50) not null
regdate date default sysdate()
2) 비즈니스메서드
C, RR, U, D로 총 5개의 핵심로직을 포함한다. 메서드명은 기능으로 한다.
insert
selectAll
selectOne
update
delete
3) 필요 페이지
필요한 페이지의 개수를 대략적으로 설정하였고, 사용되는 비즈니스메서드를 정리해보았다.
메인페이지 : selectAll
상세페이지 : selectOne / update / delete
글작성 : insert
에러페이지
2. model
1) DB생성
mysql을 사용하여 작업하였다. 기존 사용되는 board테이블이 존재하는 관계로 board2로 명명하였다.
create table board2(
bid int primary key auto_increment,
writer varchar(20) not null,
content varchar(50) not null,
regdate datetime default now()
);
2) JDBCUtill
package common;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCUtill {
//static변수를 사용하여 자원으로 선언
static final String driverName="com.mysql.cj.jdbc.Driver";
static final String url="jdbc:mysql://localhost:3306/hamdb"; //hamdb부분은 본인이 사용중인 db이름 입력
static final String user="root";
static final String passwd="1234";
// DB에 연결 == connection 확보하기에 ouput으로 설정
public static Connection connect() {
Connection conn = null;
try {
Class.forName(driverName);
conn=DriverManager.getConnection(url, user, passwd);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return conn;
}
// conn, stmt 닫는 메서드
public static void disconnect(PreparedStatement pstmt, Connection conn) {
try {
pstmt.close();
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
3) VO
로깅목적으로 toString메서드를 오버라이딩 하였다.
package board;
public class BoardVO {
//멤버변수
private int bid;
private String writer;
private String content;
private String regdate;//외부 입력을 받지 않고, DB에서 값을 받을 예정
//getter & setter
public int getBid() {
return bid;
}
public String getWriter() {
return writer;
}
public String getContent() {
return content;
}
public String getRegdate() {
return regdate;
}
public void setBid(int bid) {
this.bid = bid;
}
public void setWriter(String writer) {
this.writer = writer;
}
public void setContent(String content) {
this.content = content;
}
public void setRegdate(String regdate) {
this.regdate = regdate;
}
//로깅목적
@Override
public String toString() {
return "[bid=" + bid + ", writer=" + writer + ", content=" + content + ", regdate=" + regdate + "]";
}
}
4) DAO
package board;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import common.JDBCUtill;
public class BoardDAO {
Connection conn;
PreparedStatement pstmt;
final String insert="insert into board2 (writer,content) values(?,?)";//writer, content
final String update="update board2 set writer=?,content=? where bid=?";
final String delete="delete from board2 where bid=?";
final String selectOne="select * from board2 where bid=?";
final String selectAll="select * from board2";
//insert로직
public boolean insert(BoardVO vo) {
conn=JDBCUtill.connect();
try {
pstmt=conn.prepareCall(insert);
pstmt.setString(1, vo.getWriter());
pstmt.setString(2, vo.getContent());
pstmt.executeUpdate();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}finally {
JDBCUtill.disconnect(pstmt, conn);
}
return true;
}
//update로직
public boolean update(BoardVO vo) {
conn=JDBCUtill.connect();
try {
pstmt=conn.prepareStatement(update);
pstmt.setString(1, vo.getWriter());
pstmt.setString(2, vo.getContent());
pstmt.setInt(3, vo.getBid());
pstmt.executeUpdate();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}finally {
JDBCUtill.disconnect(pstmt, conn);
}
return true;
}
//delete로직
public boolean delete(BoardVO vo) {
conn=JDBCUtill.connect();
try {
pstmt=conn.prepareStatement(delete);
pstmt.setInt(1, vo.getBid());
pstmt.executeUpdate();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}finally {
JDBCUtill.disconnect(pstmt, conn);
}
return true;
}
//selectOne로직
public BoardVO selectOne(BoardVO vo) {
BoardVO data=null;
conn=JDBCUtill.connect();
try {
pstmt=conn.prepareStatement(selectOne);
pstmt.setInt(1, vo.getBid());
ResultSet rs= pstmt.executeQuery();
if(rs.next()) { //반환 데이터가 1개라 while쓰지 않아도 됨
data = new BoardVO(); //데이터가 존재할때 new
//빈 멤버변수에 하나씩 찍어가며 저장
data.setBid(rs.getInt("bid"));
data.setWriter(rs.getString("writer"));
data.setContent(rs.getString("content"));
data.setRegdate(rs.getString("regdate"));
}
rs.close();//닫기
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
JDBCUtill.disconnect(pstmt, conn);
}
return data;
}
//selectAll로직
public ArrayList<BoardVO> selectAll(BoardVO vo) {
conn=JDBCUtill.connect();
ArrayList<BoardVO> datas=new ArrayList<BoardVO>();
try {
pstmt=conn.prepareStatement(selectAll);
ResultSet rs = pstmt.executeQuery();
//rs한개를 vo로 바꿔 저장하고, 그걸 datas에 덮어쓰기
while(rs.next()) {
//글이 여러개 존재하니 new도 여러번 진행되어야한다.
BoardVO data=new BoardVO();
data.setBid(rs.getInt("bid"));
data.setWriter(rs.getString("writer"));
data.setContent(rs.getString("content"));
data.setRegdate(rs.getString("regdate"));
datas.add(data);
}
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
JDBCUtill.disconnect(pstmt, conn);
}
return datas;
}
}
3. index.jsp
자바 MVC작업시 사용하였던 controller의 AppStart메서드와 같은 동작을 하는 개념이다.
일반적으로 사용자에게 view를 보여주기 위해서는 DB방문(페이지구성내용)이 필요하기 때문에 controller를 필수로 지나쳐와야한다.(c-index.jsp->m->c-indexNew.do->v순서)
모든 웹들은 index를 필수로 가져야한다.
index는 시작페이지 혹은 표지페이지라고 하며, DB에 처음 방문할 수 있도록 요청하는 역할을 한다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
//페이지 이동에도 값을 유지할 목적으로 forward방식으로 사용할 예정
pageContext.forward("controller.jsp?action=main");
//"controller.jsp로 이동할건데요? action에는 main이라는 파라미터를 넣어주세요"
//이때 C에서 사용자의 요청을 분기처리(Act==1이면?) 하기 위해 action의 파라미터를 사용할 예정이다.
%>
이때 pageContext는 내장객체로, HTTP 요청을 처리하는 제어권을 다른 페이지로 넘길 때 사용한다.
4. controller.jsp
1) 분기점 설정
String action을 이전페이지(index.jsp)에서 요청받은 action의 값으로 저장하게 되는데, 이때 앞에서 이미 action은 main으로 설정되었기에 필수적으로 첫 실행시 main으로 이동하게 된다. 각각의 페이지에서 action이라는 파라미터의 value를 새로 설정하여 요청에 맞게 페이지를 변경할 예정이다. 분기처리 이후 각각의 페이지에 맞게 m과 v를 연결하는 로직을 작성하면 된다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" errorPage="error.jsp" import="java.util.*,board.*" %>
<%
request.setCharacterEncoding("UTF-8");
%>
<jsp:useBean id="dao" class="board.BoardDAO" />
<jsp:useBean id="vo" class="board.BoardVO" />
<jsp:setProperty property="*" name="vo"/>
<%
// action 파라미터의 값에따라 분기처리
String action=request.getParameter("action");
if(action.equals("main")){// index.jsp로부터 넘어온 최초 DB연결요청
//1. DB연결 -> selecAll()요청 -> datas받아오기 [M]
ArrayList<BoardVO> datas=dao.selectAll(vo);
// 2. datas setAttribute() [C]
request.setAttribute("datas", datas);
// 3. main.jsp로 이동 [V]
pageContext.forward("main.jsp");
} else if(action.equals("board")){// 메인페이지에서 상세페이지보여달라고 요청
//1. bid의 값을 vo에 저장 [C]
//vo.setBid(Integer.parsInt(request.getParameter("bid")))를 setParameter로 해결
//2. DAO selectOne(vo) [M]
BoardVO data=dao.selectOne(vo);
if(data==null){//bid이상한거 입력하면 메인으로 이동하도록
response.sendRedirect("a_controller.jsp?action=main");
}
//3. data setAttribute() [C]
request.setAttribute("data", data);
//4. board.jsp로 이동 [V]
pageContext.forward("board.jsp");
} else if(action.equals("update")){// 상세페이지에서 내용변경 요청
if(dao.update(vo)){
response.sendRedirect("controller.jsp?action=main");
} else{
throw new Exception("update중에 오류발생!");
}
} else if(action.equals("delete")){// 상세페이지에서 내용삭제 요청
if(dao.delete(vo)){
response.sendRedirect("controller.jsp?action=main");
}
else{
throw new Exception("delete중에 오류발생!");
}
} else if(action.equals("insert")){// 글 작성페이지에서 내용생성 요청
System.out.println(vo);
if(dao.insert(vo)){
response.sendRedirect("controller.jsp?action=main");
}
else{
throw new Exception("insert중에 오류발생!");
}
} else{
//out.println("<script>alert('잘못된 action 파라미터');</script>");혹은
//에러페이지연결
pageContext.forward("error.jsp");
//throw new Exception("잘못된 action 파라미터입니다");
}
%>
* forward와 sendRedirect의 차이 *
타겟페이지가 존재하는 경우 forward로 사용하고 이때, url이 변경되지 않는다.
오류같은거라서 새로 요청해야하는 경우는 sendRedirect사용하는데 이때는 url이 변경된다.
2) 에러페이지
분기처리시 action파라미터를 잘못 요청하였을 경우(404error)도 대비하여 에러페이지를 생성해두는 것이 바람직하다.
url요청이 잘못되어 페이지를 찾을 수 없을 경우에 네이버 웹툰측에서는 아래와 같은 화면을 확인할 수 있는데, 404에러발생시 아래의 내용을 담은 페이지가 출력되도록 설정하였음을 알 수 있다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" isErrorPage="true" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>죄송합니다. 관리자에게 문의주세요...</h1>
<%= exception %>
</body>
</html>
5. 메인페이지(main.jsp)
main페이지에서 DB에 저장된 글목록을 출력하기 위해서는 selectAll에서 전달받은 datas를 사용하여 for each로 출력하면 된다. 이때 datas를 불러오기 위해서 datas가 존재하는지를 먼저 확인해야하는데, 해당 작업을 useBean액션을 통해 진행하였다.
controller.jsp에 main부분에서 data는 request로 setAttribute되었고, request 내장객체는 해당 datas를 그대로 유지한 채로 pageContext.forward를 통해 해당 페이지로 전달하게 된다. 따라서 useBean액션이 선언되어있는 라인에서는 "request한테 datas가 저장되어있니??"라는 의미가 담겨있다고 할 수 있다.
td에 v.get메서드를 활용하여 각각 알맞은 칸에 삽입하였고, <a>태그를 활용하여 bid를 클릭하면 해당 bid의 상세보기 페이지로 이동하게 된다. 큰 범주에서 작은 범주 순으로 작성하면 된다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" import="java.util.*,board.*"%>
<jsp:useBean id="datas" class="java.util.ArrayList" scope="request" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<table border="1">
<tr>
<th>글 번호</th><th>글 작성자</th><th>작성일</th>
</tr>
<%
for(BoardVO v:(ArrayList<BoardVO>)datas){//datas=request.getAttribute("datas", datas)를 usebean으로 처리함으로써 자동 처리됨
%>
<tr>
<td><a href="controller.jsp?action=board&bid=<%=v.getBid()%>"><%=v.getBid()%></a></td><td><%=v.getWriter()%></td><td><%=v.getRegdate()%></td>
</tr>
<%
}
%>
</table>
<hr>
<a href="insert.jsp">글 작성 페이지</a>
</body>
</html>
index페이지를 실행하면 action이 main으로 기본 설정되어 main.jsp를 출력하는 것을 알 수 있다.
6. 상세보기 페이지(board.jsp)
해당 페이지에서는 글 내용 확인과 동시에 수정이 가능하다. input에 수정할 내용을 입력하고 '내용 변경하기' 버튼을 누르면 form의 내용이 submit되어 현재 상세보기중인 bid(설정 안하면 0전달됨)의 값과, action을 update로 설정하는 명령이 함께 전달된다. 이때, input type="hidden"속성을 사용하여 사용자가 수정할 수 없도록 사용자에게 드러나지 않은 채 전달된다.
1개의 form당 하나의 submit만 수용할 수 있으므로 나머지 하나의 버튼은 button타입 onclick시 del( )가 동작할 수 있도록 구현되었다. confrim창으로 삭제여부 재확인을 boolean타입으로 응답받고, 해당응답을 기준으로 하여 알맞은 로직을 수행하도록 한다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" import="board.*" %>
<jsp:useBean id="data" class="board.BoardVO" scope="request" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<script type="text/javascript">
//삭제하기 버튼 onclick시 수행할 메서드
function del(){
ans=confirm("정말 삭제하시겠습니까? 복원되지않습니다!");
if(ans==true){
document.checkForm.action.value="delete";
document.checkForm.submit();
}
else{
return;
}
}
</script>
<form name="checkForm" action="controller.jsp" method="post">
<input type="hidden" name="bid" value="<%=data.getBid()%>"><!-- 상세보기중인 bid -->
<input type="hidden" name="action" value="update"><!-- 수정하기버튼 클릭시 action의 값은 update -->
<table border="1">
<tr>
<td>작성자</td>
<td><input type="text" name="writer" value="<%=data.getWriter()%>"></td>
</tr>
<tr>
<td>내용</td>
<td><input type="text" name="content" value="<%=data.getContent()%>"></td>
</tr>
<tr>
<td>작성일</td>
<td><input type="text" name="regdate" value="<%=data.getRegdate()%>"></td>
</tr>
<tr>
<td colspan="2" align="right"><input type="submit" value="내용 변경하기"> <input type="button" value="글 삭제하기" onClick="del()"></td>
</tr>
</table>
</form>
<hr>
<a href="controller.jsp?action=main">메인페이지로 이동하기</a>
</body>
</html>
내용 변경하기 버튼 클릭시 변경된 내용이 DB에 반영된 채 main.jsp로 이동된다.
삭제시 취소 버튼을 누르면 해당 화면이 유지되고, 확인 버튼을 누르면 삭제된 내용이 DB에 반영된채 main.jsp로 이동한다
7. 글 작성 페이지
해당 페이지의 내용이 controller를 통해 model로 전달되고, 그 결과를 다시 controller에서 boolean으로 반환받아 명령에 맞는 행동을 수행하게 된다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" import="board.*" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form name="checkForm" action="controller.jsp" method="post">
<input type="hidden" name="action" value="insert">
<table border="1">
<tr>
<td>작성자</td>
<td><input type="text" name="writer"></td>
</tr>
<tr>
<td>내용</td>
<td><input type="text" name="content"></td>
</tr>
<tr>
<td colspan="2" align="right"><input type="submit" value="글 등록하기"></td>
</tr>
</table>
</form>
<hr>
<a href="controller.jsp?action=main">메인페이지로 이동하기</a>
</body>
</html>
글등록시 등록한 글이 DB에 반영되어 메인페이지로 이동한다
'JSP' 카테고리의 다른 글
[JDBC] MVC나누기_2 : 게시판+로그인 (0) | 2022.03.06 |
---|---|
[View업그레이드] EL & JSTL (0) | 2022.03.03 |
[JDBC] Bean & JDBC연결 (0) | 2022.03.01 |
[JSP] 내장객체 (0) | 2022.02.25 |
[JSP] 서버프로그래밍_기초2 (0) | 2022.02.24 |