게시판 글쓰기 추가기능
일반게시판을 만들면서,
글쓸때에 카테고리를 선택하고, 파일을 첨부하는 기능을 추가해보자
![]() |
<< 이전포스팅에서 완료된 요청 | 이번포스팅에서 구현할 요청 >>
| 기능 | url요청 | 요청시 전달값 | 응답페이지 또는 url재요청 | |
| /web | index.jsp | |||
| 로그인요청 | /login.me | userId=?&userPwd=? | 실패시 | views/common/errorPage.jsp |
| 성공시 | /web url재요청=> index.jsp | |||
| 로그아웃요청 | /logout.me | /web url재요청=> index.jsp | ||
| 회원가입페이지 | /enrollForm.me | views/member/memberEnrollForm.jsp | ||
| 회원가입요청 | /insert.me | userId=? ... 등 | 실패시 | views/common/errorPage.jsp |
| 성공시 | /web url재요청 => index.jsp | |||
| 마이페이지요청 | /myPage.me | 로그인전 | /web url재요청 => index.jsp | |
| 로그인후 | views.member/myPage.jsp | |||
| 정보변경요청 |
/update.me |
userId=? ... 등 | 실패시 | view/common/errorPage.jsp |
| 성공시 | /myPage.me url재요청 | |||
| 공지사항 목록 | /list.no | views/notice/noticeListView.jsp | ||
| 공지사항글쓰기 | /enrollForm.no | views/noticeEnrollForm.jsp | ||
| 공지사항등록 | /insert.no | title=?&content=? | 실패시 | views/common/errorPage.jsp |
| 성공시 | /list.no url재요청 | |||
| 공지 - 상세페이지 | /detail.no | no=? (수기기재) | 실패시 | views/common/errorPage.jsp |
| 성공시 | views/notice/noticeDetailView.jsp | |||
| 공지 - 수정페이지 | /updateForm.no | no=? | views/notice/noticeUpdateForm.jsp | |
| 공지 - 수정요청 | /update.no | no=?&title=?&content=? | 실패시 | views/common/errorPage.jsp |
| 성공시 | /detail.no?no=XX url재요청 | |||
| 일반게시판 목록 | /list.bo | cpage=? | views/board/boardListView.jsp | |
| 일반 글쓰기 | /enrollForm.bo | views/board/boardEnrollForm.jsp | ||
| 일반글 등록 | /insert.bo | 실패시 | views/common/errorPage.jsp | |
| 성공시 | /list.bo url 재요청 |
카테고리 설정 기능
글쓰기 버튼 클릭시 첫번째로 카테고리를 가져와서
뿌려주는 역할을 해야한다.
이때 select태그 안에 option으로 뿌려준다.
STEP1) boardEnrollForm.jsp
- 생성위치 : webProject/src/main/webapp/views/board
- 글작성을 누를 시 들어오게 되는 글작성 페이지를 가작성한다.
.....
<%@ include file="../common/menubar.jsp" %>
.....
<form action="" method="post">
카테고리
<select name="category">
<!-- db로부터 category테이블에 존재하는 값들 조회해와서 뿌려야함 -->
ex)
<option value="10">공통</option>
<option value="20">운동</option>
<option value="30">등산</option>
...
</select>
제목 <input type="text" name="title" required>
내용 <textarea name="content" rows="10" style="resize:none" required></textarea>
첨부파일 <input type="file" name="upfile">
<button type="submit">작성하기</button>
<button type="reset">취소하기</button>
</form>
.....
STEP2) boardListView.jsp(수정)
- 게시글 리스트 페이지에서, 로그인한 상태에서만 보이는 글작성 버튼을 만든다
- 클릭시 서블릿 enrollForm.bo 로 이동
<% if(loginUser != null) { %>
<a href="<%=contextPath%>/enrollForm.bo">글작성</a>
<% } %>
STEP3) BoardEnrollFormController.java
- 생성위치 : com.br.board.controller
서블릿/매핑값enrollForm.bo - 글작성 버튼 클릭시 글작성 상세페이지로 포워딩하는 서블릿
- 글작성 시에는 db의 카테고리 데이터들이 필요하다!
>>카테고리 정보를 가져오는 Service메소드 실행
void doGet(HttpServletRequest request, HttpServletResponse response) {
ArrayList<Category> list = new BoardService().selectCategoryList();
request.setAttribute("list",list);
request.getRequestDispatcher("views/board/boardEnrollForm.jsp").forward(request, response);
}
STEP4) BoardServie.java(수정)
- 카테고리 데이터 조회하는 selectCategoryList() 메소드생성
public ArrayList<Category> selectCategoryList() {
Connection conn = getConnection();
ArrayList<Category> list = new BoardDao().selectCategoryList(conn);
close(conn);
return list;
}
STEP5) board-mapper.xml (수정)
- 카테고리 데이터 조회기능 sql문 작성
<entry key="selectCategoryList">
SELECT
CATEGORY_NO
, CATEGORY_NAME
FROM CATEGORY
</entry>
STEP6) BoardDao.java(수정)
- 카테고리 데이터 조회기능 selectCategoryList(conn) 메소드생성
public ArrayList<Category> selectCategoryList(Connection conn) {
ArrayList<Category> list = new ArrayList();
PreparedStatement pstmt = null;
ResultSet rset = null;
String spl = prop.getProperty("selectCategoryList");
try {
pstmt = conn.prepareStatement(spl);
rset = pstmt.executeQuery();
while(rset.next()) {
list.add(new Category(rset.getInt("category_no"), rset.getString("category_name")));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
close(rset);
close(pstmt);
}
return list;
}
STEP7) boardEnrollForm.jsp (수정)
- 가지고온 카테고리 데이터를 option태그 content부에 뿌려준다
- 이때, 사용자는 옵션들 중에 하나를 선택한다
>> 요청처리시 넘길 키값(value속성)은 카테고리의 번호를 넣는다
<select name="category">
<!-- db로부터 category테이블에 존재하는 값들 조회해와서 뿌려야함 -->
<% for(Category c : list) { %>
<!--value에는 카테고리 명이 아닌 번호를 넘겨야한다!(db에 기록?_) -->
<option value="<%=c.getCategoryNo()%>"><%= c.getCategoryName() %></option>
<% } %>
</select>
파일업로드 하여 글작성
일반적인 글작성 과정을 복습해보면서
첨부파일 업로드 기능을 추가해보자
STEP1) Attachment.java
- 생성경로 : com.br.board.model.vo
- 첨부파일에 대한 정보를 보관할 vo 클래스를 만든다.
- db에서도 Board테이블이 아닌 별도 테이블(Attachment)에 별도 보관
(상황에 따라 결정할 수 있음) - 필요한 컬럼 >>
FILE_NO : 파일번호(PK, 시퀀스 생성)
REF_BNO : 참조하는 게시글 번호
ORIGIN_NAME : 파일 원본명
CHANGE_NAME : 서버에 저장할 이름(원본명이 중복될 수 있으므로 변경필수)
FILE_PATH : 파일이 어떤 폴더에 저장되어있는지 경로 저장
UPLOAD_DATE : 업로드시점
FILE_LEVEL : 썸네일로 활용할 대표사진은 1, 아니면 2
STATUS : 상태(삭제 등)
STEP2) boardEnrollForm.jsp (수정)
- 글쓰기 페이지의 input태그를 수정해준다.
1) form요소 클릭시, 처리할 서블릿은 insert.bo
2) enctype="multipart/form-data"
>> 파일을 첨부할때 필요한 form 속성
>> 이걸 안쓰면 파일명만 넘어감
3) method="post"
>>get으로 할 시 파일이 넘어가지 않는다
<form action="<%=contextPath %>/insert.bo" method="post" enctype="multipart/form-data">
STEP3) baord-mapper.xml (수정)
- db에 기록할때는, Board테이블에 먼저 insert 후 Attachement테이블에 insert한다.
(항상 부모데이터를 먼저 insert해야함) - sql작성한것을 보면, Attachment에서 Board의 번호를 가져올때 시퀀스.currval을 사용했다
>>Service단계에서 하나의 트렌젝션 처리해야함
INSERT
INTO BOARD
(
BOARD_NO
, BOARD_TYPE
, CATEGORY_NO
, BOARD_TITLE
, BOARD_CONTENT
, BOARD_WRITER
)
VALUES
(
SEQ_BNO.NEXTVAL
, 1
, 사용자가 선택한 카테고리번호
, 입력한 제목
, 입력한 내용
, 로그인한 회원의 번호(요청에 안넘어옴, 별도로 구해야한다!)
)
INSERT
INTO ATTACHMENT
(
FILE_NO
, REF_BNO
, ORIGIN_NAME
, CHANGE_NAME
, FILE_PATH
)
VALUES
(
SEQ_FNO.NEXTVAL
, SEQ_BNO.CURRVAL /*부모테이블의 번호 가져오기, 이때 하나의 트랜젝션으로 처리해야한다!*/
, 첨부파일의 원본명
, 첨부파일의 실제 업로드된 파일명
, 저장경로
)
STEP4) Cos.jar
- 파일업로드는 자바 자체 클래스로는 불가, 외부 라이브러리를 가져와야 쓸수 있음
>> 다양한 라이브러리중 보편적인 Cos.jar 사용 (com.oreilly.sevlet의 약자) - http://www.servlets.com
>> 왼쪽 Cos File Upload Library
>> 하단 Download 부분 cos-22.05.zip 다운
>> 압축해제 후 lib폴더에 있는 cos.jar 파일을 프로젝트 파일의 lib폴더에 붙여넣기 - 깃허브에는 jar파일이 올라가지 않음(.gitignore문서에 올라가지 않도록 지정해둠)
>> 별도 작업 필요! - 단점 : 1.6G까지만 업로드 할수 있음
STEP5) MyFileRenamePolicy.java
- 생성위치 : com.br.common
- 사용자가 등록한 파일의 명칭은 다른 파일과 겹칠수 있기 때문에 이름을 수정해야한다.
- 파일명을 수정하는 객체 :
>>DefaultFileRenamePolicy (cos.jar에서 제공)
- 해당 클래스 내부에 rename()메소드가 실행되면서 파일명 수정후 업로드
* rename(원본파일){
* 기존에 동일한 파일명이 존재할경우
* 파일명 뒤에 카운팅된 숫자를 붙여줌
* 동일한것이 없을경우 그대로 저장
* return 수정파일;
* }
>> rename() 메소드는 원본 파일을 전달받아서 파일명 수정작업 후 수정된 파일을 반환한다. - 디폴트의 rename 메소드는 한글/특수문자/띄어쓰기는 검사가 안되기 때문에 나만의 객체를 만들어야 한다.
(서버에 따라 문제가 될수 있기 때문)
>> FileRenamePolicy를 상속해서 rename메소드를 완성시킨다
>> 추상메소드 구현방법 : 클래스 생성후 클래스명 뒤에 implements FileRenamePolicy 입력하면 이클립스가 오류만듦. 해당 오류 클릭해서 add unimplemented method 클릭 - 명명계획 : 파입업로드시간(연월일시분초)+5개의 랜덤값+원본파일 확장자
@Override
File rename(File originFile) {
//1) 파일업로드시간
// SimpleDateFormat은 매개변수로 포멧 넣고 .format(Date객체)하면 문자열로 포멧 반환
// 시간은 HH
//import는 java.util.Date로
String currentTime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
//2) 5자리 랜덤값
int ranNum = (int)(Math.random()*90000+10000);
//3) 원본파일 확장자
// 원본파일명.substring(.의 인덱스)로 구한다.
// 단!! lastIndexOf로 뒤에서부터 인덱스를 찾아야한다(종종 이름에 .이 붙는 경우가 있음)
String originName = originFile.getName();
String ext = originName.substring(originName.lastIndexOf('.'));
//바꿀이름저장
String changeName = currentTime + ranNum + ext;
//바꾼이름으로 파일객체 생성 후 리턴하기
return new File(originFile.getParent(), changeName);
}
STEP6) BoardInsertController.java
- 생성위치 : com.br.board.controller
서블릿 / 매핑값 insert.bo - 작성하기 버튼을 누르면 호출되는 서블릿. 게시글 및 첨부파일을 저장한 후 목록페이지로 포워딩한다.
- **처리과정**
- 우선 post타입으므로 request를 인코딩해준다
- enctype으로 지정한 multipart/form-data로 잘 전송된 상태에서 진행되도록 if문 내에서 구문을 작성한다.
if(ServletFileUpload.isMultipartContent(request)) { ... } - 업로드된 파일을 처리하는데 필요한 변수들을 구한다.
- int maxSize :
파일의 용량은 임의로 10mb로 지정한다.
이때, byte단위로 입력을 받으므로 10*1024*1024로 입력해야 한다. - String savePath :
파일을 저장할 폴더 결정하기 : resources/board_upfiles
>> 해당 폴더의 물리적인 경로를 알아내야한다!
2-1) 세션객체.getServletContext() : jsp 내장객체인 application 반환
2-2) app내장객체.getRealPath("/~~~") :
매개변수로 입력된 폴더의 물리정인 경로를 반환한다.
매개변수의 경로 가장 앞에 붙은 '/'는 배포되는 폴더의 최상위 폴더인 webapp을 가르키므로 그 이후의 경로만 입력하면 된다.
>>getRealPath("/resources/board_upfiles/");
**이때! 꼭 폴더명 위에 '/'를 붙여야 파일들이 그 안으로 저장된다.
- int maxSize :
- 서버 업로드(폴더에 저장)는 한줄의 구문으로 완료된다.
MultipartRequest multiRequest =
new MultipartRequest(request, savePath, maxSize, "UTF-8", new MyFileRenamePolicy());
- MultipartRequest 매개변수 생성자로 HttpServletReqest 객체인 request와 파일의 물리적인경로, 최대크기, 인코딩, 파일명명객체를 넘긴다.
- 이때 넘어간 MyFileRenamePolicy객체의 rename()메소드로 저장 이름이 만들어진다.
- request를 MultipartRequest타입인 multiRequest로 변환하는 작업이기도 하다.
이렇게 생성된 multiRequest 객체는 기존의 서블릿에서 사용하던 request 객체를 대신하게 된다.
>> multipart/form-data로 전송하는 경우 request로부터 바로 값을 뽑을 수 없기 때문(getParameter쓰면 null반환)
- 이제, 기존에 하던 작업들을 진행하면 된다.
이때, 데이터를 뽑을 때에는 multiRequest 객체를 사용해야하는데, 활용하는 메소드에 차이가 있다.
- multiRequest.getParameter("키") :
넘어온 데이터들 중 파일과 관련되지 않은 일반적인 데이터를 뽑을때는 request때처럼 getParameter를 사용한다. - multiRequest.getOriginalFileName("키") :
넘어온 파일의 본래이름을 확인할때는 getOriginFileName을 사용한다.
넘어온 파일이 없다면 null을 리턴하기때문에 파일이 넘어왔는지 여부를 검사할때도 활용된다. - multiRequest.getFilesystemName("키") :
넘어온 파일의 저장명을 리턴하는 메소드이다. - 파일의 저장경로는?
메소드를 쓸 것 없이 직접입력한다.
이때 주의할것은 폴더명 마지막에 '/'를 써야한다는 것이다.
ex) "resources/board_upfiles/"
- multiRequest.getParameter("키") :
- db에 insert하는 과정이 실패되었다면, 이미 올라간 파일은 삭제해주어야한다.
>> new File(파일경로).delete()
>> new File(savePath + at.getChangeName()).delete();
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//post방식이고 한글을 썼으므로 인코딩작업한다.
request.setCharacterEncoding("UTF-8");
// enctype이 mutipart/form-data로 잘 전송되었을 경우 잘 수행되어야함(오류나는지 확인)
if(ServletFileUpload.isMultipartContent(request)) {
//1) 전달되는 파일들을 처리할 작업내용 (파일용량제한, 전달된 파일을 저장할 폴더 경로)
//1-1) 파일의 용량제한 지정 >> int maxSize에 byte단위로 담기
int maxSize = 10*1024*1024;
//1-2) 전달된 파일을 저장시킬 폴더의 물리적인 경로 알아내기
String savePath = request.getSession().getServletContext().getRealPath("/resources/board_upfiles/");
//System.out.println(savePath); 어떻게 출력되나 확인
//C:\workspaces\06_web-workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\webProject\resources\board_upfiles\
//2) 전달된 파일의 파일명 수정 및 서버에 업로드(폴더에 저장) 작업
MultipartRequest multiRequest = new MultipartRequest(request, savePath, maxSize, "UTF-8", new MyFileRenamePolicy());
//3) DB에 기록할 데이터를 뽑아서 vo에 주섬주섬 담기
// > 카테고리번호, 제목, 내용, 로그인한회원번호 => Board테이블 insert
// > ^넘어온 첨부파일이 있다면^ 원본명, 실제파일명, 저장경로 => Attachment 테이블 insert
String category = multiRequest.getParameter("category");
String boardTitle = multiRequest.getParameter("title");
String boardContent = multiRequest.getParameter("content");
//회원번호 알아오기
HttpSession session = request.getSession();
int userNo = ((Member)session.getAttribute("loginUser")).getUserNo();
//Board담기
Board b = new Board();
b.setCategory(category);
b.setBoardTitle(boardTitle);
b.setBoardContent(boardContent);
b.setBoardWriter(String.valueOf(userNo));
//첨부파일 있을때만 Attachment담기
Attachment at = null;
//multiRequest.getOriginalFileName("키") :
//키값(input의 name)으로 넘어온 첨부파일의 원본명 문자열 리턴(없을시 null)
if(multiRequest.getOriginalFileName("upfile") != null) {
at = new Attachment();
at.setOriginName(multiRequest.getOriginalFileName("upfile"));
//실제 저장된 파일명을 알아내는 메소드
at.setChangeName(multiRequest.getFilesystemName("upfile"));
//절대경로 말고 상대경로로, 마지막은 /
at.setFilePath("resources/board_upfiles/");
}
//4) 서비스요청 (insert하러가기)
int result = new BoardService().insertBoard(b,at);
//5) 응답뷰 지정
if(result > 0) {
//성공 => 목록페이지의 1번
session.setAttribute("alertMsg", "일반게시글 작성 성공");
response.sendRedirect(request.getContextPath()+"/list.bo?cpage=1");
}else {
//실패 => 에러페이지
//첨부파일 업로드는 진행이 됐음. 단 db에 기록이 실패함
//첨부파일이 있었다면 찾아서 삭제해야한다
if(at != null) {
//savePath는 폴더까지의 경로가 담겨으므로 이름까지 합쳐준다
new File(savePath + at.getChangeName()).delete();
}
request.setAttribute("errorMsg", "일반게시글 작성 실패");
request.getRequestDispatcher("views/common/errorPage.jsp").forward(request, response);
}
}
}
STEP7) BoardService.java (수정)
- insertBoard(b,at)메소드 만들기
- 이때 Dao는 메소드를 각각 만들어야한다
>>단, commit은 한번에!
public int insertBoard(Board b,Attachment at) {
Connection conn = getConnection();
//부모테이블 먼저 insert해야 .currval이 잘 먹힌다.
int result1 = new BoardDao().insertBoard(conn, b);
//**첨부파일이 없는 게시글도 있기때문에 0이 아닌 1로 초기화해야한다!
int result2 = 1;
if(at != null) {
result2 = new BoardDao().insertAttachment(conn, at);
}
//둘중 하나라도 실패했다면 롤백해야한다
if(result1>0 && result2>0) {
commit(conn);
}else {
rollback(conn);
}
close(conn);
return result1*result2;
}
STEP8) BoardDao.java (수정)
- dao메소드는 따로따로 만든다
- insertBoard(conn, b)
- insertAttachment(conn, at)
public int insertBoard(Connection conn, Board b) {
int result=0;
PreparedStatement pstmt = null;
String sql = prop.getProperty("insertBoard");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, b.getCategory());
pstmt.setString(2, b.getBoardTitle());
pstmt.setString(3, b.getBoardContent());
pstmt.setString(4, b.getBoardWriter());
result = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
}
return result;
}
public int insertAttachment(Connection conn, Attachment at) {
int result = 0;
PreparedStatement pstmt = null;
String sql = prop.getProperty("insertAttachment");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, at.getOriginName());
pstmt.setString(2, at.getChangeName());
pstmt.setString(3, at.getFilePath());
result = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
close(pstmt);
}
return result;
}

'다양한 기술들 > 레거시 Web 실습' 카테고리의 다른 글
| [11] 게시판심화2 : 썸네일목록, 글작성(+첨부파일 미리보기) (0) | 2023.01.16 |
|---|---|
| [10] 게시판심화1 : 상세보기(+첨부파일 조회 및 다운로드) (0) | 2023.01.12 |
| [08] 게시판심화1 : 페이징처리 (1) | 2023.01.11 |
| [07] 게시판기초 : 상세조회, 수정, 삭제 (0) | 2023.01.09 |
| [06] 게시판기초 : 일반목록, 글쓰기(+권한) (0) | 2023.01.09 |

댓글