복습
ORM (Object Relational Mapping)프레임워크
- MyBatis, JPA
JSP Model 2 - MyBatis 게시판 프로그램
- 이전에 만들었던 JSP Model 2 게시판 프로그램에서 DB 연동을 Connection Pool 대신 MyBatis 와 연동한 것이다
- Connection Pool 사용한 JSP Model 2 게시판 프로그램 : https://laker99.tistory.com/129
컬럼 설명
-- 모델2 게시판
select * from tab;
select * from seq;
select * from model22;
create table model22(
board_num number,
board_name varchar2(20),
board_pass varchar2(15),
board_subject varchar2(50),
board_content varchar2(2000),
board_file varchar2(50),
board_re_ref number,
board_re_lev number,
board_re_seq number,
board_readcount number,
board_date timestamp,
primary key(board_num)
);
create sequence model22_seq
start with 1
increment by 1
nocache;
출처: https://laker99.tistory.com/140 [레이커 갓생일기:티스토리]
- 번호값이 들어가는 컬럼이 주로 Primary Key 가 된다
- 원문, 댓글 상관없이 board_num 은 sequence 가 들어간다
글 개수 (board.xml 부분)
<!-- 글갯수 -->
<select id="board_count" resultType="int">
select count(*) from model22
</select>
- 돌려줄 값의 자료형이 int 이므로 returnType 에 int 를 써야 한다
JSP Model 2 - MyBatis 게시판 프로그램 : 원문 글 작성 기능
- index.jsp 또는 프로젝트를 실행하면 바로 목록을 가져오는 요청인 "/BoardListAction.do" 로 요청한다
- index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
모델2 게시판
<%
response.sendRedirect("./BoardListAction.do");
%>
<script>
// location.href="./BoardListAction.do";
</script>
</body>
</html>
- location.href 로 목록을 가져오기 위한 "/BoardListAction.do" 요청을 해도 되고, sedRedirect 로 "/BoardListAction.do" 로 포워딩해서 요청해도 된다.
- Controller -> Service -> DAO -> Service -> Controller View 파일로 가면 목록이 나타나고 공유된 값들을 출력시켜줌
- 목록 페이지에서 '글쓰기' 를 누르면 "/BoardForm.do" 로 요청한다
- qna_board_list.jsp 부분
<a href="./BoardForm.do">글쓰기</a> <br>
- Controller 클래스로 간다
- Controller 클래스에서 "/BoardForm.do" 부분만
// 글작성 폼
}else if(command.equals("/BoardForm.do")) {
forward = new ActionForward();
forward.setRedirect(false);
forward.setPath("/board/qna_board_write.jsp");
}
- 바로 View 페이지 qna_board_write.jsp 로 이동한다
- qna_board_write.jsp
<%@ page language="java" contentType="text/html; charset=utf-8"%>
<html>
<head>
<title>MVC 게시판</title>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>
<form action="<%=request.getContextPath() %>/BoardAddAction.do" method="post"
enctype="multipart/form-data">
<table cellpadding="0" cellspacing="0" align=center border=1>
<tr align="center" valign="middle">
<td colspan="5">MVC 게시판</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12" height="16">
<div align="center">글쓴이</div>
</td>
<td>
<input name="board_name" id="board_name" type="text" size="10" maxlength="10"
value=""/>
</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12" height="16">
<div align="center">비밀번호</div>
</td>
<td>
<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10"
value=""/>
</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12" height="16">
<div align="center">제 목</div>
</td>
<td>
<input name="board_subject" id="board_subject" type="text" size="50" maxlength="100"
value=""/>
</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12">
<div align="center">내 용</div>
</td>
<td>
<textarea name="board_content" id="board_content" cols="67" rows="15"></textarea>
</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12">
<div align="center">파일 첨부</div>
</td>
<td>
<input name="board_file" type="file"/>
</td>
</tr>
<tr bgcolor="cccccc">
<td colspan="2" style="height:1px;">
</td>
</tr>
<tr><td colspan="2"> </td></tr>
<tr align="center" valign="middle">
<td colspan="5">
<input type=submit value="등록">
<input type=reset value="취소">
</td>
</tr>
</table>
</form>
</body>
</html>
- 첨부파일이 있을때는 form 에서 반드시 post 방식으로 전송, enctype 코드를 써야한다
- 입력하고 '등록' 을 누르면 "/BoardAddAction.do" 로 전송된다
+ 그럼 이 첨부파일을 원문 글 작성 Service 클래스인 BoardAddAction.java 에서 MultipartRequest 로 받아야함
- "/BoardAddAction.do" 로 요청되었음을 확인 가능
- Controller 클래스에서 "/BoardAddAction.do" 부분만
// 글작성
if(command.equals("/BoardAddAction.do")) {
try {
action = new BoardAddAction();
forward = action.execute(request, response);
}catch(Exception e) {
e.printStackTrace();
}
}
- 원문 글 작성 Service 클래스인 BoardAddAction.java 로 이동
- BoardAddAction.java
package service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;
import dao.BoardDAO;
import model.BoardBean;
public class BoardAddAction implements Action{
@Override
public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
// TODO Auto-generated method stub
System.out.println("BoardAddAction");
String path = request.getRealPath("boardupload");
System.out.println("path:"+path);
int size = 1024 * 1024; // 1MB
MultipartRequest multi =
new MultipartRequest(request,
path,
size,
"utf-8",
new DefaultFileRenamePolicy());
BoardBean board = new BoardBean();
board.setBoard_name(multi.getParameter("board_name"));
board.setBoard_pass(multi.getParameter("board_pass"));
board.setBoard_subject(multi.getParameter("board_subject"));
board.setBoard_content(multi.getParameter("board_content"));
board.setBoard_file(multi.getFilesystemName("board_file"));
BoardDAO dao = BoardDAO.getInstance();
int result = dao.insert(board);
if(result==1) {
System.out.println("insert");
}
ActionForward forward = new ActionForward();
forward.setRedirect(true);
forward.setPath("./BoardListAction.do");
return forward;
}
}
첨부파일을 업로드 하기 위해 할 일
1) 첨부파일이 실제 저장되는 경로인 "boardupload" 의 진짜 path를 구한다
2) 업로드할 첨부파일 최대 크기를 정한다
3) MultipartRequest 객체를 생성하면서 첨부파일을 서버로 업로드시킴
- 매개변수가 5개인 생성자 사용하고 있다
- 여기서 한글값 인코딩을 하므로 request.setCharacterEncoding() 을 쓰지 않아도 됨
- DTO 객체 board 를 생성하고 그 객체에 앞의 원문 글 작성폼으로부터 넘어온 값들 (첨부파일명 포함) 을 MultipartRequest 객체 multi.getParameter() 로 받아서 저장시킨다
- 실제 서버에 저장된 첨부파일명을 multi.getFilesystemName() 으로 구해와서 DTO 객체 board 에 저장
+ 클라이언트가 업로드한 파일명을 구하는 메소드는 따로 있다
- DAO 객체 생성 후 DAO 의 insert() 메소드를 호출함, 매개변수로는 삽입할 데이터를 저장한 객체 board 를 넘김
<DAO insert() 에서 돌아온 후>
- ActionForward 객체 forward 를 생성하고 목록을 가져오는 요청인 "/BoardListAction.do" 로 요청
+ 바로 목록 페이지 qna_board_list 로 가면 아무것도 없다, 먼저 목록을 가져오는 요청을 해야함
+ 첨부파일이 업로드되는 경로가 콘솔창에 찍혀 나온다
- MemberDAO.java 에서 SqlSession 객체를 구해오는 getSession() 부분
public SqlSession getSession() {
SqlSession session=null;
Reader reader=null;
try {
reader = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory sf = new SqlSessionFactoryBuilder().build(reader);
session = sf.openSession(true); // auto commit
}catch(Exception e) {
e.printStackTrace();
}
return session;
}
- MyBatis의 환경설정 파일을 읽어오면서 DB연동을 수행한다
- Builder 객체로 read 객체를 읽어서 SqlSessionFactory 객체 sf 를 생성
- sf 객체로 openSession(true) 로 세션을 구해옴, 이때 자동 커밋한다
- MemberDAO.java 에서 getMember() 메소드 부분만
//글작성(원문작성)
public int insert(BoardBean board) throws Exception {
int result=0;
SqlSession session = getSession();
result = session.insert("board_insert", board);
System.out.println("result:"+result);
return result;
}
- DAO 클래스에서 SQL문을 실행하는 것은 맞다! Mapper 파일에서 SQL문을 가져와서 여기서 실행함
- 메소드 insert() 는 SqlSession 에서 지원되는 메소드
+ SqlSession 은 MyBatis 에서 지원되는 인터페이스이다
- board.xml 에서 원문 글 작성 처리하는 SQL문 부분 (id 가 board_insert)
<!-- 글작성(원문) -->
<insert id="board_insert" parameterType="board">
insert into model22 values(model22_seq.nextval,#{board_name},
#{board_pass},#{board_subject},#{board_content},
#{board_file,jdbcType=VARCHAR},model22_seq.nextval,0,0,0,sysdate)
</insert>
- paramteterType 속성으로 값을 받는다, 받는 값의 자료형을 써야하므로 DTO 의 alias 값인 "board" 가 들어감
- board_num 자리에는 model22_seq.nextval 로 sequence 로 값을 넣는다
- #{board_name} 은 넘어온 객체 board 에서 이름 컬럼을 가져오는 board.getBoard_Name() 의 의미이다
- 원문 글이므로 board_re_ref 컬럼 자리에는 sequence 로 값이 입력된다
- 원문 글 작성이므로 board_re_lev, board_re_seq,board_readcount 자리에는 0 을 입력
첨부파일명 컬럼 (board.xml 에서 id 가 "board_insert"인 태그의 부분)
#{board_file,jdbcType=VARCHAR}
- 첨부파일은 사용자가 첨부를 선택할 수도 있고, 하지 않을 수도 있다
- 첨부파일을 선택하지 않으면 객체 board의 board_file 에 null 이 들어감, 그럼 DB에 insert 할때 board_file 에 null 값이 들어감
- MyBatis는 null 값이 들어가는 것을 허용하지 않는다
- ,jdbcType=VARCHAR 를 하면 board_file 컬럼에 null 값을 허용도록 만들어주는 코드이다
- 이 jdbcType=VARCHAR 가 없으면 첨부파일을 선택하지 않고 글 작성시 오류가 발생한다
- 원문 글 작성 성공시 콘솔창
JSP Model 2 - MyBatis 게시판 프로그램 : 글 목록 가져오기 기능 / 목록 페이지
- 원문 글 작성 후 목록을 가져오는 요청인 "/BoardListAction.do" 로 포워딩하며 요청함
- Controller 클래스로 이동한다
- Controller 클래스에서 "/BoardListAction.do" 부분만
// 글목록
}else if(command.equals("/BoardListAction.do")) {
try {
action = new BoardListAction();
forward = action.execute(request, response);
}catch(Exception e) {
e.printStackTrace();
}
}
- 목록 가져오기 Service 클래스인 BoardListAction.java 로 이동
- BoardListAction.java
package service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.BoardDAO;
public class BoardListAction implements Action{
@Override
public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
// TODO Auto-generated method stub
System.out.println("BoardListAction");
int page=1;
int limit=10;
if(request.getParameter("page")!=null) {
page = Integer.parseInt(request.getParameter("page"));
}
int startRow = (page-1) * limit + 1;
int endRow = page * limit;
List boardlist = null;
BoardDAO dao = BoardDAO.getInstance();
int listcount = dao.getCount();
// boardlist = dao.getList(startRow, endRow);
// boardlist = dao.getList(page);
// Map 처리 ----------------------------------
Map map = new HashMap();
map.put("start", startRow);
map.put("end", endRow);
// boardlist = dao.getList(map);
boardlist = dao.getList(page);
//-------------------------------------------------
System.out.println("listcount:"+listcount);
System.out.println("boardlist:"+boardlist);
int pageCount = listcount/limit + ((listcount%limit==0) ? 0:1);
int startPage = ((page-1)/10) * limit + 1;
int endPage = startPage + 10 - 1;
if(endPage > pageCount) endPage = pageCount;
request.setAttribute("page", page);
request.setAttribute("listcount", listcount);
request.setAttribute("boardlist", boardlist);
request.setAttribute("pageCount", pageCount);
request.setAttribute("startPage", startPage);
request.setAttribute("endPage", endPage);
ActionForward forward = new ActionForward();
forward.setRedirect(false);
forward.setPath("/board/qna_board_list.jsp");
return forward;
}
}
- 기본변수 3개, 파생된 변수 5개를 여기 글 목록 가져오기 Service 클래스에서 구해와야한다
+ 출력하는 파일은 따로 있다
<기본변수, 파생변수 구하기>
- 현재 페이지 번호 page의 기본값은 1 페이지로 지정, 한 화면에 10개씩 출력하겠다는 의미로 limit 는 10
- 만약 이 Service 클래스로 올때 페이지번호를 가져왔다면 그 번호가 page 의 값이 된다
- startRow, endRow 는 page, limit 에 의해 구해진다
- listcount 변수는 총 데이터 개수를 저장하고 있다, getCount() 로 부터 돌려받은 값
<총 데이터 개수 구하기>
- DAO 객체 생성 후 총 데이터 개수를 구하는 DAO 의 메소드 getCount() 메소드 호출해서 총 데이터 개수를 돌려받음
<목록 구하기>
- 실제 목록을 잘라서 목록을 구해오는 DAO 의 메소드 getList() 메소드를 호출, 매개변수로는 페이지번호 page 를 전달
* 목록을 가져올때 문제점 아래에 설명
- getList() 는 리스트로 리턴하고, 그 결과는 List 객체 boardlist 로 리턴받음
<DAO getList() 에서 돌아온 후>
- 총 페이지수가 저장될 파생변수 pageCount 를 listcount 와 limit 으로 구한다
+ 나눗셈을하면 정수형으로 값이 구해짐을 주의
- 각 블럭을 시작하는 페이지인 startPage 와 각 블럭의 마지막 페이지인 endPage 를 page 와 limit 로 구한다
- View 페이지에서 페이지 메뉴바에서 페이지 번호를 출력할때 무조건 10번 루프를 돌리므로 실제 존재하지 않는 페이지가 나타나는것을 막기 위해 마지막 페이지일땐 endPage 를 pageCount 로 설정해준다
<공유설정>
- 기본 변수 및 파생 변수인 page, listcount, boardlist, pageCount, startPage, endPage 를 request 객체로 공유설정한다
- View 페이지에서 출력할때 boardlist 는 리스트이므로 forEach 의 items 태그에 들어감, 나머지는 기본변수이므로 ${page} 처럼 출력함
<포워딩 방식, 페이지 지정>
- qna_board_list.jsp 로 포워딩, 거기서 목록을 출력할 것
- request 공유설정했으므로 Dispatcher 방식으로 포워딩해야한다
+ Model 2 게시판 프로그램 글 목록 구하기 변수들 설명 : https://laker99.tistory.com/129?category=1080281
목록을 가져올때 문제점
- MyBatis 연동 전에는 startRow 와 endRow 를 getList() 의 매개변수로 전달했었다, select 문의 where 절에 사용했음
- MyBatis 와 연동시 값이든 주소든 단 하나만 SqlSession 지원 메소드 5개의 매개변수로 전달 가능하다
+ Service 클래스에서 DAO 메소드로는 여러개의 값을 매개변수로 전달 가능하지만, SqlSession 지원 메소드의 매개변수로는 첫번째 매개변수에 id 를 넣고, 두번째 매개변수에 값을 전달할 수 있다, 단 하나의 값만 전달 가능
해결방법
- 해결방법이 여러개 있지만 여기선 이 방법을 사용하고 있다
- startRow 와 endRow 를 전달하는 대신 페이지 번호인 page 를 전달한다
- 이 page 번호에 따라 startRow 와 endRow 가 정해지므로, limit 는 정해진 값이므로 그 자리에 10을 쓰면 된다
- SQL문 상에서 page 번호로 startRow 와 endRow를 계산하기
또다른 해결방법
- 이 부분에서만 여러 파일의 내용을 변경할 것(주석 풀기 등), 이 부분을 나가면 다시 원래대로
- Map 객체 map 을 생성하고 put() 으로 startRow, endRow 를 저장 후 Map 객체 map 을 전달하면 됨
// Map 처리 ----------------------------------
Map map = new HashMap();
map.put("start", startRow);
map.put("end", endRow);
- Map 객체 map 에 key , value 값으로 startRow 와 endRow 를 모두 저장시켰다
ex) 사용자가 2 페이지 선택시 11 과 20 을 Map 객체에 저장하는 것
- key 값으로 value 를 가져와서 사용할 것
- 그리고 Map 객체를 DAO의 getList() 로 전달
boardlist = dao.getList(map);
- MemberDAO.java 에서 getList() 메소드 부분에서 주석을 바꿈
// 데이터 목록
// public List getList(int page) throws Exception {
public List getList(Map map) throws Exception {
List list = new ArrayList();
SqlSession session=getSession();
list = session.selectList("board_list", map);
return list;
}
- map 객체를 selectList() 의 매개변수로 전달한다
- Mapper 파일인 board.xml 로 가자
<!-- Map 전달 -->
<select id="board_list" parameterType="Map" resultType="board">
select * from (select rownum rnum, board.* from (
select * from model22 order by board_re_ref desc,board_re_seq asc) board )
where rnum >= #{start} and rnum <= #{end}
</select>
- parameterType 을 Map 으로 써야한다, HashMap 이어도 괜찮다
- #{start} 와 #{end} 의 "start" 와 "end" 는 map 에 저장한 key 값이다
- #{start} 처럼 key 값을 이용해서 value 값을 구해올 수 있다
+ 세번째 방법
- DTO 클래스 안에 startRow 와 endRow 를 저장할 수 있는 필드를 만들고 그 DTO 객체에 startRow, endRow 를 저장해서 넘겨주는 방법도 있다
- 가져올땐 getter 메소드 사용
어떤 방법을 써야할까?
- 우리가 쓰고 있는 페이지번호 page 를 넘기고 SQL문 상에서 startRow, endRow 를 계산하는 방법은 쓰지 못할 때가 있다
- 검색기능을 수행시에 '작성자명', '제목' 등으로 검색한다, 그 검색어도 SQL상으로 전달되어야함, 넘어가는 값이 많아질땐 페이지번호를 넘기는 방법 쓰지 못하게 됨
- 이땐 DTO 객체를 쓰거나, Map 객체안에 저장해서 전달해야함
- 주로 DTO 객체를 넘기는 경우가 많다, Map 은 DTO보다 처리속도가 느림
- MemberDAO.java 에서 getCount() 메소드 부분만
// 총데이터 갯수 구하기
public int getCount() throws Exception{
int result=0;
SqlSession session=getSession();
result = (Integer)session.selectOne("board_count");
return result;
}
- group 함수로 구하기때문에 검색하는 데이터는 1개이므로 selectOne() 메소드 사용
- session.selectOne() 에서 리턴자료형은 Object 이므로 다운캐스팅이 원칙이다
- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_count)
<!-- 글갯수 -->
<select id="board_count" resultType="int">
select count(*) from model22
</select>
- 돌려주는 데이터는 글 개수 이므로 resultType 은 "int" 이다
+ 주의 : returnType 은 무조건 DTO 가 아니다, 돌려주는 데이터의 자료형임
- MemberDAO.java 에서 getList() 메소드 부분만
// 데이터 목록
public List getList(int page) throws Exception {
// public List getList(Map map) throws Exception {
List list = new ArrayList();
SqlSession session=getSession();
list = session.selectList("board_list", page);
return list;
}
- 메소드 selectList() 에서도 다른 SqlSession 지원 메소드처럼 단 하나의 값만 매개변수로 전달 가능하다
+ 첫번째 매개변수는 id 값, 두번쨰 매개변수에만 값을 전달 가능
- page 번호만 있으면 startRow, endRow를 계산할 수 있으므로 페이지번호 page 만 전달한다
+ Map 객체로 넘어올때는 주석처리 되어있음
- board.xml 에서 목록을 자르고 목록을 가져오는 SQL문 부분 (id 가 board_list)
<!-- 글목록 -->
<!-- page 전달-->
<select id="board_list" parameterType="int" resultType="board">
select * from (select rownum rnum, board.* from (
select * from model22 order by board_re_ref desc,board_re_seq asc) board )
where rnum >= (#{page}-1) * 10 + 1 and rnum <= #{page}*10
</select>
- selectList() 메소드를 실행하고 결과를 List 로 돌려줌, 이때 결과를 DTO 객체들을 저장한 List 로 전달한다고 해도 resultType 에는 DTO 인 member 를 써야한다
- 리스트에 알아서 순차적으로 저장해서 돌려준다
- 전달받은 값은 페이지번호 이므로 parameterType 은 int 로 작성
<SQL문>
- 첫번째 서브쿼리는 rownum 컬럼에 대한 별칭 rnum 을 준다
- 두번째 서브쿼리는 필요한 내용을 검색하고 정렬한다, 댓글게시판이므로 board_re_ref 로 최근글이 위로 가도록 정렬하고, 부모글과 자식글은 board_re_ref 가 동일하므로 한번 더 정렬조건으로 board_re_seq 를 준다
- 두번쨰 서브쿼리에 대해서 board 라는 별칭을 주고 첫번째 서브쿼리에서 board 의 모든 컬럼을 검색하고 있다
- startRow, endRow 를 전달받은 page 번호로 계산한다
- startRow = (#{page} -1) * 10 + 1 이다, 10 은 limit 을 의미
- endRow = #{page} * 10 이다, 10은 limit 을 의미
- xml 파일에서는 < 와 > 를 잘 인식하지 못함, > 대신 특수문자 > 를 사용, < 대신 특수문자 &li; 을 사용
+ <와 > 를 대신하는 다른 방법도 있다, 여기서는 > 과 < 을 사용
- 목록을 성공적으로 가져올때 콘솔창
- 이제 목록을 가져오는 작업은 끝났다, Controller 클래스로 갔다가 Dispatcher 방식으로 qna_board_list.jsp 로 포워딩됨
- qna_board_list.jsp
<%@ 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" %>
<a href="./BoardForm.do">글쓰기</a> <br>
데이터 갯수 : ${listcount} 개
<table border=1 width=700 align=center>
<caption>게시판 목록</caption>
<tr>
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>날짜</th>
<th>조회수</th>
</tr>
<c:set var="num" value="${listcount - (page-1) * 10 }"/>
<c:forEach var="b" items="${boardlist}">
<tr>
<td>
${num}
<c:set var="num" value="${num-1}"/>
</td>
<td>
<c:if test="${b.board_re_lev > 0}">
<c:forEach var="i" begin="0" end="${b.board_re_lev}">
</c:forEach>
</c:if>
<c:if test="${b.board_re_lev > 0}">
Re.
</c:if>
<a href="./BoardDetailAction.do?board_num=${b.board_num}&page=${page}">
${b.board_subject}
</a>
</td>
<td>${b.board_name}</td>
<td>
<fmt:formatDate value="${b.board_date}" pattern="yyyy-MM-dd H:mm"/>
</td>
<td>${b.board_readcount}</td>
</tr>
</c:forEach>
</table><br>
<center>
<c:if test="${listcount > 0}">
<a href="./BoardListAction.do?page=1" style="text-decoration:none"> < </a>
<c:if test="${startPage > 10}">
<a href="./BoardListAction.do?page=${startPage - 10}">[이전]</a>
</c:if>
<c:forEach var="i" begin="${startPage}" end="${endPage}">
<c:if test="${i == page}">
[${i}]
</c:if>
<c:if test="${i != page}">
<a href="./BoardListAction.do?page=${i}">[${i}]</a>
</c:if>
</c:forEach>
<c:if test="${endPage < pageCount}">
<a href="./BoardListAction.do?page=${startPage + 10}">[다음]</a>
</c:if>
<a href="./BoardListAction.do?page=${pageCount}" style="text-decoration:none"> > </a>
</c:if>
</center>
- JSTL 태그들을 쓰기 위해 코어 라이브러리와 국제화 라이브러리를 불러온다
- '글쓰기' 를 누르면 원문 글 작성 폼으로 가는 요청인 "/BoardForm.do" 으로 요청
- 글개수 출력은 Service 클래스 BoardListAction.java 에서 request 객체로 공유했던 listcount 를 바로 구해서 출력
- 그 페이지 화면출력번호 num 을 구함 * 아래에 설명
<데이터 뿌리기>
- Service 클래스 BoardListAction.java 에서 request 객체로 공유했던 값들을 EL 태그로 구해서 출력하고 있다
- forEach 태그를 통해서 각 데이터(행)를 출력, forEach 태그의 items 에 목록을 가지고 있는 공유된 리스트 boardlist 가 온다, 그럼 각 데이터 하나가 변수 b 에 저장됨
- 제목을 출력할때 ${b.board_subject} 로 출력, 표기법만 이럴뿐 실제 의미는 b.getBoard_Subject() 를 출력하는 것이다
- 국제화 라이브러리 formatDate 태그로 패턴을 지정해서 날짜 ${b.board_date} 를 원하는 포맷으로 출력
<댓글 관련>
- if 태그를 써서 ${b.board_re_lev} > 0 이면 즉, 댓글이면 깊이만큼 제목 앞에 공백 출력, Re. 출력
<상세페이지로 가는 링크>
- 제목에 링크를 걸어서 글 번호와 원래 페이지번호를 가져가고 있다
<페이지 메뉴바>
- forEach 태그의 begin 과 end 속성으로 공유된 값 startPage 와 endPage 를 사용함, startPage ~ endPage 까지를 페이지 메뉴바에 출력
- 현재 페이지가 아닌 경우 링크를 "/BoardListAction.do" 로 걸고 페이지번호 page 를 get 방식으로 전달, 이렇게 하면 해당 페이지의 목록을 가져와서 다시 출력함
- 이렇게 전달시 BoardListAction.java 의 이 코드에서 페이지번호 page 를 받음 (BoardListAction.java 부분)
if(request.getParameter("page")!=null) {
page = Integer.parseInt(request.getParameter("page"));
}
- < 클릭시 page 번호를 1 을 넘겨주며 "/BoardListAction.do" 로 간다
- > 클릭시 page 번호를 마지막 페이지(= 총 페이지 수) 를 넘겨주며 "/BoardListAction.do" 로 간다 (총 페이지수는 공유된 pageCount값을 가져온 것이다)
- [이전] 메뉴는 startPage > 10 인 경우, 즉 첫번째 페이지가 아닌 경우에만 출력, 링크는 현재 블럭의 startPage 에서 10 을 빼서 이전 블럭의 가장 작은 페이지로 이동
- [다음] 메뉴는 endPage < pageCount 인 경우 출력, 링크는 현재 블럭의 startPage 에서 10 을 더해서 다음 블럭의 가장 작은 페이지로 이동
ex) 1page 일떄 endPage 는 10, pageCount 는 13 이므로 [다음] 메뉴 나타남
- 11page일때 endPage 는 13(보정했었다), pageCount 도 13 이므로 만족하지 않음, [다음] 메뉴 나타나지 않는다
+ Model 2 게시판 프로그램 글 목록 출력 설명 : https://laker99.tistory.com/129?category=1080281
화면 출력 번호
- Service 클래스 BoardListAction.java 에서 구하지 않은 마지막 파생변수인 화면 출력 번호를 여기서 정의
<c:set var="num" value="${listcount - (page-1) * 10 }"/>
- 3개의 기본 변수값을 사용해서 화면 출력 번호 num 을 생성, 10 은 limit 값이다
- limit 값은 고정된 값 (10) 이므로 공유설정하지 않았음
- forEach 로 각각의 행을 출력할때마다 1이 감소됨
- 페이징 처리를 보기 위해 글을 여러개 작성해서 많은 데이터를 넣자
+ BoardAddAction.java 에서 포워딩할때 Dispatcher 방식으로 바꾸고, 포워딩 페이지 설정에서 . 을 빼주면 글 작성 후 새로고침 시 같은 데이터가 다시 들어감
- 여기서 새로고침 눌러서 데이터 많이 넣기
- 이렇게 페이지 메뉴바에서 5 를 클릭시
- 페이지번호에 따라 startRow, endRow 가 결정되므로 가져올 글들이 달라짐
JSP Model 2 - MyBatis 게시판 프로그램 : 상세 페이지 기능
- qna_board_list.jsp 에서 제목을 클릭하면 "/BoardDetailAction.do" 로 요청하며 글 번호와 페이지 번호를 가져간다
<a href="./BoardDetailAction.do?board_num=${b.board_num}&page=${page}">
${b.board_subject}
</a>
- 여기가 출발점임, 바로 상세페이지를 실행하면 안된다
- 상세페이지로 가거나, 수정하고 오거나, 삭제하거나 할때 원래 페이지로 돌아가기 위해서 페이지 번호를 가져감
- 하나의 글에 대한 상세 정보를 구해와야 하므로 글 번호가 필요함
- Controller 클래스에서 "/BoardDetailAction.do" 부분만
// 상세 페이지
}else if(command.equals("/BoardDetailAction.do")) {
try {
action = new BoardDetailAction();
forward = action.execute(request, response);
}catch(Exception e) {
e.printStackTrace();
}
}
- 상세 페이지 Service 클래스인 BoardDetailAction.java 로 이동
package service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.BoardDAO;
import model.BoardBean;
public class BoardDetailAction implements Action{
@Override
public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
// TODO Auto-generated method stub
System.out.println("BoardDetailAction");
int board_num = Integer.parseInt(request.getParameter("board_num"));
String page = request.getParameter("page");
BoardDAO dao = BoardDAO.getInstance();
// BoardBean board = dao.updateContent(board_num);
//조회수 증가
dao.updateCount(board_num);
BoardBean board = dao.getContent(board_num); // 상세정보 구하기
request.setAttribute("board", board);
request.setAttribute("page", page);
ActionForward forward = new ActionForward();
forward.setRedirect(false);
forward.setPath("/board/qna_board_view.jsp");
return forward;
}
}
- 목록페이지에서 넘어온 글 번호와 페이지 번호를 받아서 변수 board_num 와 page 에 저장
- 여기서는 2가지 작업을 해야함, 2개의 DAO 메소드를 호출해야한다
1) updateCount() : 조회수 1 증가
2) getContent() : 상세 정보 구해오기
- 이때 글번호 board_num 을 전달해야 그 글의 조회수를 증가시킬 수 있고, 상세 정보를 구할 수 있다
<DAO updateCount() 와 getContent() 에서 돌아온 후>
- 가져온 상세정보를 DTO 객체 board 로 받음
- 그 객체 board 와 페이지번호 page 를 공유설정함
+ 글 번호는 객체 board 안에 board_num 필드안에 저장되어있으므로 공유설정 따로 하지 않음
+ DTO 객체 board 가 공유되면 View 에서 ${board.필드명} 으로 결과를 출력함
<포워딩>
- 상세정보를 보여줄 View 페이지인 qna_board_view.jsp 로 포워딩 페이지 설정
+ 실제 포워딩은 Controller 로 가서 포워딩한다
- MemberDAO.java 에서 updateCount() 메소드 부분만
// 조회수 증가
public void updateCount(int board_num) throws Exception{
SqlSession session = getSession();
session.update("board_updatecount", board_num);
}
- 전달받은 글 번호 board_num 을 session.update() 의 두번째 매개변수로 전달함
- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_updatecontent)
<!-- 조회수 증가 -->
<update id="board_updatecount" parameterType="int">
update model22 set board_readcount=board_readcount+1 where board_num = #{board_num}
</update>
- 글 번호를 전달받았고, parameterType 에는 전달받은 값의 자료형을 써야하므로 "int"
- 조회수 값을 1 증가시켜주는 update 를 한다, where 절에 #{board_num} 으로 넘어온 글 번호를 쓴다
- MemberDAO.java 에서 getContent() 메소드 부분만
// 상세 페이지, 수정 폼
public BoardBean getContent(int board_num) throws Exception {
BoardBean board = new BoardBean();
SqlSession session=getSession();
board = (BoardBean)session.selectOne("board_content", board_num);
return board;
}
- 1개 글에 대한 상세정보를 구해서 돌려줘야하므로 selectOne() 메소드 호출
- 두번째 매개변수로 글 번호 board_num 을 넘겨준다
- 검색되는 결과가 1개이므로 DTO 객체가 리턴자료형으로 왔다, 검색되는 결과가 여러개일땐 List 가 온다
+ 무조건 DTO 가 온다는건 Mapper 파일의 SQL문 부분에 해당함, DAO에서는 검색되는 결과가 여러개일땐 List 가 온다
- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_content)
<!-- 상세페이지, 수정폼 -->
<select id="board_content" parameterType="int" resultType="board">
select * from model22 where board_num = #{board_num}
</select>
- #{board_num} 번호값에 만족하는 데이터를 검색해서 데이터 1개에 대한 상세정보를 자동으로 DTO 객체에 매핑해서 그 DTO 객체를 리턴함
+ 테이블 컬럼명과 DTO 프로퍼티명이 일치할때만 자동 매핑 가능
- resultType 은 DTO alias 인 board
- 다음은 이 상세정보를 출력하는 View 페이지인 qna_board_view.jsp
- qna_board_view.jsp
<%@ 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" %>
<table border=1 width=600 align=center>
<caption>상세 페이지</caption>
<tr>
<td>작성자</td>
<td>${board.board_name}</td>
</tr>
<tr>
<td>조회수</td>
<td>${board.board_readcount}</td>
</tr>
<tr>
<td>날짜</td>
<td>
<fmt:formatDate value="${board.board_date}"
pattern="yyyy-MM-dd H:mm"/>
</td>
</tr>
<tr>
<td>제목</td>
<td>${board.board_subject}</td>
</tr>
<tr>
<td>내용</td>
<td><pre>${board.board_content}</pre></td>
</tr>
<tr>
<td>첨부 파일</td>
<td>
<c:if test="${board.board_file != null }">
<a href="./board/file_down.jsp?file_name=${board.board_file}">${board.board_file}</a>
</c:if>
</td>
</tr>
<tr>
<td colspan=2 align=center>
<input type="button" value="댓글"
onClick="location.href='./BoardReplyAction.do?board_num=${board.board_num}&page=${page}&board_re_ref=${board.board_re_ref}&board_re_lev=${board.board_re_lev}&board_re_seq=${board.board_re_seq}'">
<input type="button" value="수정"
onClick="location.href='./BoardModifyAction.do?board_num=${board.board_num}&page=${page}'">
<input type="button" value="삭제"
onClick="location.href='./BoardDeleteAction.do?board_num=${board.board_num}&page=${page}'">
<input type="button" value="목록"
onClick="location.href='./BoardListAction.do?page=${page}'">
</td>
</tr>
</table>
- Service 클래스 BoardDetailAction.java 에서 공유설정한 객체 board 와 페이지번호 page 를 EL 로 가져와서 활용
ex) 조회수는 ${board.board_readcount} 로 가져와서 출력하고 있다
- if 태그에 ${board.board_file != null} 로, 만약 첨부파일이 있다면 첨부파일명을 출력
- 첨부파일명을 클리하면 file_down.jsp 로 이동하며 다운이 된다
- 내용은 pre 태그로 감싸서 출력해야 줄이 바뀌어서 출력됨
- 상세페이지에 아래에 출력될 버튼을 만들고 링크를 걸었다
'목록' 버튼 클릭시
- 목록을 가져오는 요청인 "/BoardListAction.do" 로 요청, 이때 페이지번호 page 를 전달
+ 페이지 번호 page는 BoardDetailAction.java 에서 공유설정했던 값을 가져왔다
'댓글' 버튼 클릭시
- 댓글을 다는 폼으로 가기 위해 "/BoardReplyAction.do" 로 요청, 이때 5가지의 값을 가져가고 있다
- 부모글이 될 현재 글의 글 번호, 현재 페이지 번호 뿐 아니라 부모글이 될 현재 글의 ref, lev, seq 값도 가져간다
- 만약 글 번호, 페이지 번호만 넘길때는 Service클래스로 가서 부모글에 대한 정보(ref,lev,seq)를 DB 에서 가져와야한다
- 여기선 5가지 값을 다 가져가므로 Service 클래스로 갈 필요 없이 바로 댓글 작성 폼 View 페이지로 가도 된다
- '수정' 버튼 / '삭제' 버튼
JSP Model 2 - MyBatis 게시판 프로그램 : 댓글 작성 기능
- qna_board_view 에서 출력된 버튼 중 '댓글' 버튼을 클릭시 "/BoardReplyAction.do" 로 요청
<input type="button" value="댓글"
onClick="location.href='./BoardReplyAction.do?board_num=${board.board_num}&page=${page}&board_re_ref=${board.board_re_ref}&board_re_lev=${board.board_re_lev}&board_re_seq=${board.board_re_seq}'">
- "/BoardReplyAction.do" 로 가면서 글번호, 페이지번호 뿐 아니라 부모글에 대한 정보인 board_re_ref, board_re_lev, board_re_seq 도 전달
- 그러므로 Service 클래스로 가서 이 정보를 가져오기 위해 DB 연동할 필요 없이 바로 댓글 작성 폼으로 이동 가능
- Service 클래스로 안가고 바로 댓글 작성 폼으로 이동한다
- Controller 클래스에서 "/BoardReplyAction.do" 부분만
// 댓글 폼
}else if(command.equals("/BoardReplyAction.do")) {
forward = new ActionForward();
forward.setRedirect(false);
forward.setPath("/board/qna_board_reply.jsp");
}
- 즉 댓글 작성 폼인 View 페이지 qna_board_reply.jsp 로 이동한다
- Model 2 게시판 프로그램 작성시에는 댓글작성폼으로 갈때 2개의 값만 전달하고 나머지는 DB와 연동해서 부모글의 정보를 구하는 작업을 선행했었다, 검색어 (게시판 프로그램 : 댓글 작성폼 Service 클래스)
- 여기서는 부모글의 정보도 qna_board_view 에서 전달하므로 DB연동 필요없이 바로 View페이지로 가고 있다
- qna_board_reply.jsp
<%@ page language="java" contentType="text/html; charset=utf-8"%>
<html>
<head>
<title>댓글 게시판</title>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>
<form action="<%=request.getContextPath() %>/BoardReply.do" method="post">
<input type="hidden" name="board_num" value="${param.board_num}">
<input type="hidden" name="page" value="${param.page}">
<input type="hidden" name="board_re_ref" value="${param.board_re_ref}">
<input type="hidden" name="board_re_lev" value="${param.board_re_lev}">
<input type="hidden" name="board_re_seq" value="${param.board_re_seq}">
<table cellpadding="0" cellspacing="0" align=center border=1>
<tr align="center" valign="middle">
<td colspan="5">댓글 게시판</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12" height="16">
<div align="center">글쓴이</div>
</td>
<td>
<input name="board_name" id="board_name" type="text" size="10" maxlength="10"
value=""/>
</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12" height="16">
<div align="center">비밀번호</div>
</td>
<td>
<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10"
value=""/>
</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12" height="16">
<div align="center">제 목</div>
</td>
<td>
<input name="board_subject" id="board_subject" type="text" size="50" maxlength="100"
value=""/>
</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12">
<div align="center">내 용</div>
</td>
<td>
<textarea name="board_content" id="board_content" cols="67" rows="15"></textarea>
</td>
</tr>
<tr bgcolor="cccccc">
<td colspan="2" style="height:1px;">
</td>
</tr>
<tr><td colspan="2"> </td></tr>
<tr align="center" valign="middle">
<td colspan="5">
<input type=submit value="댓글">
<input type=reset value="취소">
</td>
</tr>
</table>
</form>
</body>
</html>
- qna_board_view 에서 get 방식으로 전달된 값을 param EL 내장객체로 가져오고 있다
ex) ${param.board_re_ref} 는 request.getParamter("board_re_ref") 를 의미
- get 방식으로 전달된 5개의 값을 qna_board_reply 의 form 에서 "/BoardReply.do" 로 요청하며 hidden 을 통해서 전달
- 댓글을 작성하고 '댓글' 버튼을 누르면 "/BoardReply.do"로 요청
- Controller 클래스에서 "/BoardReply.do" 부분만
// 댓글
}else if(command.equals("/BoardReply.do")) {
try {
action = new BoardReply();
forward = action.execute(request, response);
}catch(Exception e) {
e.printStackTrace();
}
}
- 댓글 작성 Service 클래스인 BoardReply.java 로 이동
package service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.BoardDAO;
import model.BoardBean;
public class BoardReply implements Action {
@Override
public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
// TODO Auto-generated method stub
System.out.println("BoardReply");
request.setCharacterEncoding("utf-8");
int board_num = Integer.parseInt(request.getParameter("board_num"));
int board_re_ref = Integer.parseInt(request.getParameter("board_re_ref"));
int board_re_lev = Integer.parseInt(request.getParameter("board_re_lev"));
int board_re_seq = Integer.parseInt(request.getParameter("board_re_seq"));
String page = request.getParameter("page");
BoardBean board = new BoardBean();
board.setBoard_re_ref(board_re_ref);
board.setBoard_re_seq(board_re_seq);
BoardDAO dao = BoardDAO.getInstance();
dao.updateSeq(board); // board_re_seq값 증가
board.setBoard_re_seq(board_re_seq+1);
board.setBoard_re_lev(board_re_lev+1);
board.setBoard_name(request.getParameter("board_name"));
board.setBoard_pass(request.getParameter("board_pass"));
board.setBoard_subject(request.getParameter("board_subject"));
board.setBoard_content(request.getParameter("board_content"));
dao.boardReply(board); // 댓글 작성 : insert SQL
ActionForward forward = new ActionForward();
forward.setRedirect(false);
forward.setPath("/BoardDetailAction.do?board_num="+board_num+"&page="+page); // 상세페이지로 이동
// forward.setPath("/BoardListAction.do?page="+page); // 목록페이지로 이동
return forward;
}
}
- 댓글 작성 폼에서 한글값이 넘어올 수 있으므로 한글값 인코딩
- hidden 으로 넘어오는 5개의 값을 request.getParameter() 로 받아서 변수에 저장
- 또한 사용자가 직접 댓글 작성폼에 작성한 정보 4가지도 받는다
- updateSeq() 메소드에서 부모글의 board_re_ref, board_re_seq 값이 필요하므로 생성한 DTO 객체 board 에 부모글의 board_re_ref, board_re_seq 를 저장하고 그 객체 board 를 updateSeq() 의 매개변수로 전달
<DAO updateSeq() 메소드로부터 돌아온 뒤>
- 댓글을 작성해야하므로 insert 될 데이터를 DTO 객체 board 에 세팅
- 이때 board_re_seq 값과 board_re_lev 값은 부모보다 1 증가된 값으로 세팅
- 또한 사용자가 직접 입력한 4가지 정보도 세팅하고 DAO의 boardReply() 메소드 호출, 매개변수로는 객체 board 를 넘김
<DAO boardReply() 메소드로부터 돌아온 뒤>
- 댓글을 단 이후 상세페이지로 넘어갈땐, "/BoardDetailAction.do" 로 포워딩 페이지 지정하며 글번호와 페이지번호를 전달
+ BoardDetailAction.java 에서 글번호와 페이지번호를 getParameter() 로 받는 작업을 가장 먼저하므로 두 값이 필요
- 댓글을 단 이후 목록페이지로 넘어갈땐, "/BoardListAction.do" 로 포워딩 페이지 지정하며 페이지 번호를 전달
이 Service 클래스에서 2번의 DAO 메소드를 실행해야함
1) updateSeq() 메소드 : update SQL문
- update 문을 수행하기 위해 부모글의 ref 값과 seq 값을 전달
- 댓글의 ref 는 부모글의 ref 값과 같은값을 넣고, 부모글의 seq 보다 큰 글들의 seq 를 +1 함
2) boardReply() 메소드 : insert SQL문
- 댓글을 DB에 insert 함
- Mapper 파일로 처리해야하므로 두 메소드의 SQL문을 하나의 메소드에서 실행 불가, 메소드를 따로 작성 해야함
- MemberDAO.java 에서 댓글 출력순서를 조절하기 위한 updateSeq() 부분
// 댓글 출력 순서 (board_re_seq값 증가)
public void updateSeq(BoardBean board) {
SqlSession session = getSession();
session.update("board_updateseq", board);
}
- 부모글의 board_re_ref 와 board_re_seq 값을 저장한 객체 board 를 두번째 매개변수로 전달한다
- board.xml 에서 원문 글 작성 처리하는 SQL문 부분 (id 가 board_updateseq)
<!-- 댓글 출력순서 -->
<update id="board_updateseq" parameterType="board">
update model22 set board_re_seq=board_re_seq+1
where board_re_ref = #{board_re_ref} and board_re_seq > #{board_re_seq}
</update>
- 부모글과 board_re_ref 값이 같으면서 , 부모글보다 board_re_seq 값이 큰 글의 board_re_seq 값을 1 증가시켜줘야함
의미
1) 부모가 원문이면 그 원문의 모든 댓글의 seq 값을 1 씩 증가
2) 부모가 댓글이면 그 부모보다 먼저 달린 형제 댓글들과 그 형제 댓글들의 대댓글들의 seq 값을 1씩 증가
- MemberDAO.java 에서 댓글을 삽입하는 boardReply() 부분
// 댓글작성
public void boardReply(BoardBean board) {
SqlSession session = getSession();
session.insert("board_reply", board);
}
- board.xml 에서 원문 글 작성 처리하는 SQL문 부분 (id 가 board_reply)
<!-- 댓글 작성 -->
<insert id="board_reply" parameterType="board">
insert into model22 values(model22_seq.nextval,#{board_name},
#{board_pass},#{board_subject},#{board_content},
#{board_file,jdbcType=VARCHAR},#{board_re_ref},#{board_re_lev},#{board_re_seq},0,sysdate)
</insert>
- board_num 은 원문이든 댓글이든 모두 sequence 로 들어간다
- 댓글은 첨부파일을 올리지 못하므로 첨부파일명을 저장하는 컬럼 board_file 이 null 이 되어야함
- MyBatis 는 null 값을 허용하지 않는다
- jdbcType=VARCHAR 속성값을 추가하면 null 값을 허용하게 됨
- 이미 BoardReply.java 에서 부모의 board_re_seq, board_re_lev 에서 1 증가시켜서 board 객체의 board_re_seq 와 board_re_lev에 세팅했으므로 여기선 그대로 가져와서 삽입
- 조회수 컬럼엔 0, 날짜 컬럼엔 sysdate 로 현재 날짜와 시간을 넣어줌
JSP Model 2 - MyBatis 게시판 프로그램 : 글 수정폼 기능
- qna_board_view 에서 출력된 버튼 중 '수정' 버튼을 클릭시 "/BoardModifyAcion.do" 로 요청
<input type="button" value="수정"
onClick="location.href='./BoardModifyAction.do?board_num=${board.board_num}&page=${page}'">
- "/BoardModifyAcion.do" 로 가면서 글번호, 페이지번호 전달
- 이 글번호, 페이지번호는 목록 페이지 -> 상세페이지 -> 수정폼 -> 수정 까지 넘어간다
- 먼저 상세정보를 구해와서 그걸 수정폼에 뿌려야한다
- Controller 클래스에서 "/BoardModifyAcion.do" 부분만
// 수정 폼
}else if(command.equals("/BoardModifyAction.do")) {
try {
action = new BoardModifyAction();
forward = action.execute(request, response);
}catch(Exception e) {
e.printStackTrace();
}
}
- 수정폼에 뿌릴 상세 정보를 가져오는 Service 클래스 BoardModifyAction.java
- BoardModifyAction.java
package service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.BoardDAO;
import model.BoardBean;
public class BoardModifyAction implements Action {
@Override
public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
// TODO Auto-generated method stub
System.out.println("BoardModifyAction");
int board_num = Integer.parseInt(request.getParameter("board_num"));
String page = request.getParameter("page");
BoardDAO dao = BoardDAO.getInstance();
BoardBean board = dao.getContent(board_num);
request.setAttribute("board", board);
request.setAttribute("page", page);
ActionForward forward = new ActionForward();
forward.setRedirect(false);
forward.setPath("/board/qna_board_modify.jsp");
return forward;
}
}
- 상세 페이지 qna_board_view.jsp 에서 get 방식으로 넘어온 글 번호와 페이지 번호를 받아서 변수에 저장
- DAO의 getContent() 메소드로 글 하나에 대한 상세정보를 가져온다, 매개변수로는 글 번호를 넘겨준다
- getContent() 에서 구해온 상세정보를 board 객체에 저장 한다
- board 객체를 공유설정하고 페이지 번호인 page 변수도 request 객체로 공유설정함
+ View인 수정폼에서 가져와서 뿌려줄 수 있도록 공유설정해야한다, DTO 객체가 공유되었다면 View 에서 ${공유되는네임값.필드명} 으로 사용
- 포워딩 페이지는 qna_board_modify.jsp 로 지정한다
- MemberDAO.java 에서 getContent() 메소드 부분만
// 상세 페이지, 수정 폼
public BoardBean getContent(int board_num) throws Exception {
BoardBean board = new BoardBean();
SqlSession session=getSession();
board = (BoardBean)session.selectOne("board_content", board_num);
return board;
}
- 두번째 매개변수로 글 번호 board_num 전달
- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_content)
<!-- 상세페이지, 수정폼 -->
<select id="board_content" parameterType="int" resultType="board">
select * from model22 where board_num = #{board_num}
</select>
- DTO 객체에 상세정보를 매핑해서 리턴해줌
- 포워딩되는 페이지이자 수정폼 View 페이지인 qna_board_modify.jsp 를 보자
- qna_board_modify.jsp
<%@ page language="java" contentType="text/html; charset=utf-8"%>
<html>
<head>
<title>게시판 수정</title>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>
<form action="<%=request.getContextPath() %>/BoardModify.do" method="post">
<input type="hidden" name="board_num" value="${board.board_num}">
<input type="hidden" name="page" value="${page}">
<table cellpadding="0" cellspacing="0" align=center border=1>
<tr align="center" valign="middle">
<td colspan="5">게시판 수정</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12" height="16">
<div align="center">글쓴이</div>
</td>
<td>
<input name="board_name" id="board_name" type="text" size="10" maxlength="10"
value="${board.board_name }"/>
</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12" height="16">
<div align="center">비밀번호</div>
</td>
<td>
<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10"
value=""/>
</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12" height="16">
<div align="center">제 목</div>
</td>
<td>
<input name="board_subject" id="board_subject" type="text" size="50" maxlength="100"
value="${board.board_subject}"/>
</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12">
<div align="center">내 용</div>
</td>
<td>
<textarea name="board_content" id="board_content" cols="67" rows="15">${board.board_content}</textarea>
</td>
</tr>
<tr bgcolor="cccccc">
<td colspan="2" style="height:1px;">
</td>
</tr>
<tr><td colspan="2"> </td></tr>
<tr align="center" valign="middle">
<td colspan="5">
<input type=submit value="수정">
<input type=reset value="취소">
</td>
</tr>
</table>
</form>
</body>
</html>
+ 첨부파일을 수정하는 기능은 빠져있다
- Service 클래스인 BoardModifyAction.java 에서 공유설정했던 DTO 객체 board 의 값을 ${board.필드명} 으로 가져와서 출력하고 있다
ex) ${board.subject} 으로 해당 글의 제목을 가져오고 있다
- 공유설정했던 DTO 객체 board에서 글 번호인 ${board.board_num} 을 hidden 으로 "/BoardModify.do" 로 넘겨준다
- 또한 수정이 끝난 후 원래 페이지로 돌아가기 위해 공유설정된 페이지번호 ${page} 를 hidden 으로 "/BoardModify.do" 로 넘겨준다
- 즉 글 번호와 페이지 번호를 글 수정을 하는 Service 클래스로 넘기고 있음
JSP Model 2 - MyBatis 게시판 프로그램 : 글 수정 기능
- 수정폼 qna_board_modify.jsp 에서 '수정' 을 누르면 form 을 통해 "/BoardModify.do" 로 요청한다
- hidden 으로 글 번호와 페이지 번호가 넘어왔음
- 이 글번호, 페이지번호는 목록 페이지 -> 상세페이지 -> 수정폼 -> 수정 까지 넘어간다
- 또한 수정폼에서 사용자가 입력한 값들도 넘어왔다
- Controller 클래스에서 "/BoardModify.do" 부분만
// 수정
}else if(command.equals("/BoardModify.do")) {
try {
action = new BoardModify();
forward = action.execute(request, response);
}catch(Exception e) {
e.printStackTrace();
}
}
- 수정을 하는 Service 클래스인 BoardModify.java
package service;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.BoardDAO;
import model.BoardBean;
public class BoardModify implements Action{
@Override
public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
// TODO Auto-generated method stub
System.out.println("BoardModify");
response.setContentType("text/html; charset=utf-8");
request.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
String page = request.getParameter("page");
int board_num = Integer.parseInt(request.getParameter("board_num"));
String board_pass = request.getParameter("board_pass");
BoardBean board = new BoardBean();
board.setBoard_num(board_num);
board.setBoard_name(request.getParameter("board_name"));
board.setBoard_subject(request.getParameter("board_subject"));
board.setBoard_content(request.getParameter("board_content"));
BoardDAO dao = BoardDAO.getInstance();
BoardBean old = dao.getContent(board_num);
if(old.getBoard_pass().equals(board_pass)) {//비번 일치시
dao.update(board);
}else { // 비번 불일치시
out.println("<script>");
out.println("alert('비밀번호가 일치하지 않습니다.');");
out.println("history.go(-1);");
out.println("</script>");
out.close();
return null;
}
ActionForward forward = new ActionForward();
forward.setRedirect(false);
forward.setPath("/BoardDetailAction.do?board_num="+board_num+"&page="+page);
return forward;
}
}
- hidden 으로 넘어온 글 번호와 페이지 번호를 받아서 변수에 저장한다
- 수정을 하기 위해 필요한 정보들을 저장한 DTO 객체 board 에 세팅
- DAO의 getContent() 메소드를 호출해서 매개변수로 글번호를 전달, 데이터의 상세정보를 구해와서 객체 old 에 저장
- old.getBoard_pass() 인 DB속 비밀번호와 사용자가 수정폼에 입력한 비밀번호 board_pass 가 일치시 글을 수정하는 DAO의 update() 메소드를 호출해서 매개변수로 객체 board 를 넘겨줌
- 비밀번호가 일치하지 않았다면 이전페이지인 수정폼으로 돌아감
- 비밀번호 일치 후 수정을 성공적으로 완료했다면 상세페이지로 가도록 "/BoardDetailAction.do" 로 요청하며 글 번호, 페이지 번호를 넘긴다
- MemberDAO.java 에서 getContent() 메소드 부분만
// 상세 페이지, 수정 폼
public BoardBean getContent(int board_num) throws Exception {
BoardBean board = new BoardBean();
SqlSession session=getSession();
board = (BoardBean)session.selectOne("board_content", board_num);
return board;
}
- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_content)
<!-- 상세페이지, 수정폼 -->
<select id="board_content" parameterType="int" resultType="board">
select * from model22 where board_num = #{board_num}
</select>
- MemberDAO.java 에서 update() 메소드 부분만
// 글수정
public void update(BoardBean board) throws Exception {
SqlSession session=getSession();
session.update("board_update", board);
}
- session.update() 는 수정한 글 개수를 자동으로 반환해주지만 여기서는 리턴받지 않았다, select 가 아니면 리턴받을 필요 없음
- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_update)
<!-- 글수정 -->
<update id="board_update" parameterType="board">
update model22 set board_name=#{board_name}, board_subject=#{board_subject},
board_content=#{board_content} where board_num=#{board_num}
</update>
- 객체 board 가 넘어왔으므로 paramterType 은 DTO alias 인 "board"
- update SQL문을 사용해서 넘어온 DTO 객체의 값들을 #{필드명} 으로 가져옴
ex) #{board_name} 은 board.getBoard_Name() 을 의미
JSP Model 2 - MyBatis 게시판 프로그램 : 글 삭제폼 / 글 삭제 기능
- qna_board_view 에서 출력된 버튼 중 '삭제' 버튼을 클릭시 "/BoardDeleteAcion.do" 로 요청
<input type="button" value="삭제"
onClick="location.href='./BoardDeleteAction.do?board_num=${board.board_num}&page=${page}'">
- "/BoardDeleteAcion.do" 로 가면서 글번호, 페이지번호 전달
- 이 글번호, 페이지번호는 목록 페이지 -> 상세페이지 -> 삭제폼 -> 삭제 까지 넘어간다
- 먼저 삭제폼으로 가게 된다
- Controller 클래스에서 "/BoardDeleteAcion.do" 부분만
// 삭제 폼
}else if(command.equals("/BoardDeleteAction.do")) {
forward = new ActionForward();
forward.setRedirect(false);
forward.setPath("/board/qna_board_delete.jsp");
}
- 삭제 폼에서는 DB 접속이 필요하지 않으므로 Service 클래스가 아닌 삭제폼 qna_board_delete.jsp 로 바로 포워딩 설정 후 Controller 클래스 하단에서 포워딩
- qna_board_delete.jsp
<%@ page language="java" contentType="text/html; charset=utf-8"%>
<html>
<head>
<title>게시판 삭제</title>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>
<form action="<%=request.getContextPath() %>/BoardDelete.do" method="post">
<input type="hidden" name="board_num" value="${param.board_num}">
<input type="hidden" name="page" value="${param.page}">
<table width=300 cellpadding="0" cellspacing="0" align=center border=1>
<tr align="center" valign="middle">
<td colspan="5">게시판 삭제</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12" height="16">
<div align="center">비밀번호</div>
</td>
<td>
<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10"
value=""/>
</td>
</tr>
<tr bgcolor="cccccc">
<td colspan="2" style="height:1px;">
</td>
</tr>
<tr><td colspan="2"> </td></tr>
<tr align="center" valign="middle">
<td colspan="5">
<input type=submit value="삭제">
<input type=reset value="취소">
</td>
</tr>
</table>
</form>
</body>
</html>
- EL 내장객체 param 으로 상세 페이지 qna_board_view.jsp 에서 get 방식으로 넘어온 글 번호와 페이지 번호를 받음
- 받은 글 번호와 페이지번호를 바로 form의 hidden 을 통해 "/BoardDelete.do" 로 전달함
+ 원하는 글 1개를 삭제하기 위해 반드시 글 번호를 전달해야함, 그리고 삭제 후 원래 페이지로 돌아가기 위해 페이지 번호 전달
- 그리고 사용자가 삭제폼에 입력한 비밀번호도 "/BoardDelete.do" 전달
- Controller 클래스에서 "/BoardDelete.do" 부분만
// 삭제
}else if(command.equals("/BoardDelete.do")) {
try {
action = new BoardDelete();
forward = action.execute(request, response);
}catch(Exception e) {
e.printStackTrace();
}
}
- 글 삭제 Service 클래스 BoardDelete.java
package service;
import java.io.File;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.BoardDAO;
import model.BoardBean;
public class BoardDelete implements Action {
@Override
public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
// TODO Auto-generated method stub
System.out.println("BoardDelete");
response.setContentType("text/html; charset=utf-8");
request.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
String path = request.getRealPath("boardupload");
int board_num = Integer.parseInt(request.getParameter("board_num"));
String page = request.getParameter("page");
String board_pass = request.getParameter("board_pass");
BoardDAO dao = BoardDAO.getInstance();
BoardBean old = dao.getContent(board_num); // 상세정보 구하기
if(old.getBoard_pass().equals(board_pass)) { //비번 일치시
dao.delete(board_num); // delete SQL
if(old.getBoard_file() != null) { // 첨부파일이 있으면
File file = new File(path);
file.mkdirs();
File[] f = file.listFiles();
for(int i=0; i<f.length; i++) {
if(f[i].getName().equals(old.getBoard_file())) {
f[i].delete();
}
}
}
}else { // 비번 불일치시
out.println("<script>");
out.println("alert('비밀번호가 일치하지 않습니다.');");
out.println("history.go(-1);");
out.println("</script>");
out.close();
return null;
}
ActionForward forward = new ActionForward();
forward.setRedirect(false);
forward.setPath("/BoardListAction.do?page="+page);
return forward;
}
}
- qna_board_delete.jsp 에서 hidden 으로 넘어온 글 번호, 페이지 번호와 사용자가 삭제폼에 입력한 비밀번호를 받아서 변수에 저장한다
- DAO의 getContent() 메소드를 호출, 매개변수로 글 번호를 전달해서 해당 글의 상세정보를 구해온다
- 구해온 상세 정보에서 비밀번호를 가져와서 사용자가 삭제폼에 입력한 비밀번호와 비교
- 비번이 일치시 DAO의 delete() 메소드를 호출해서 글을 삭제함, 글 삭제 이후 첨부파일도 지워준다 * 아래에 설명
- 비번이 불일치시 위에서 생성한 out 객체로 alert 창을 출력하고 이전 페이지인 삭제폼으로 돌아감
- 비번이 일치하고 삭제 성공시 목록페이지로 가기 위해 포워딩 페이지를 목록을 가져오는 요청인 "/BoardListAction.do" 로 지정하고 페이지 번호 전달
이 Service 클래스에서 DAO의 메소드 2개를 호출해야한다
1) getContent() : 비번을 가져와서 비교하기 위해 호출
2) delete() : 글 삭제를 위해 호출
첨부파일 삭제 (BoardDelete.java 부분)
String path = request.getRealPath("boardupload");
if(old.getBoard_file() != null) { // 첨부파일이 있으면
File file = new File(path);
file.mkdirs();
File[] f = file.listFiles();
for(int i=0; i<f.length; i++) {
if(f[i].getName().equals(old.getBoard_file())) {
f[i].delete();
}
}
}
- "boardupload" 폴더의 경로로 File 객체 file 을 구한 후 listFiles() 로 File 배열 f 에 모든 파일을 저장
- 배열에 들은 파일명을 getName() 으로 하나씩 가져와서 DB에서 삭제한 글의 첨부파일명과 일치하면 그 첨부파일 삭제
- Model 2 게시판 프로그램에서 첨부파일 삭제 설명하는 부분 : https://laker99.tistory.com/131?category=1080281
- Model 2 게시판 프로그램에서 첨부파일 삭제 설명하는 부분 검색어 : 첨부파일 지우기 (BoardDelete.java 부분)
- MemberDAO.java 에서 getContent() 메소드 부분만
// 상세 페이지, 수정 폼
public BoardBean getContent(int board_num) throws Exception {
BoardBean board = new BoardBean();
SqlSession session=getSession();
board = (BoardBean)session.selectOne("board_content", board_num);
return board;
}
- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_content)
<!-- 상세페이지, 수정폼 -->
<select id="board_content" parameterType="int" resultType="board">
select * from model22 where board_num = #{board_num}
</select>
- MemberDAO.java 에서 delete() 메소드 부분만
// 글삭제
public void delete(int board_num) {
SqlSession session=getSession();
session.delete("board_delete", board_num);
}
- 글 삭제를 위해 글 번호를 넘겨준다
- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_delete)
<!-- 글삭제 -->
<delete id="board_delete" parameterType="int">
delete from model22 where board_num=#{board_num}
</delete>
- 글을 삭제해보자
- 삭제폼으로 갈땐 Service 클래스로 가지 않으므로 콘솔창에 Service 클래스로 간 흔적이 없다
- 삭제 되었음, 후에 목록을 가져오고 목록페이지로 간다