일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- mybatis
- 로그인
- Linked List
- 오라클
- 서블릿
- 미로 생성 알고리즘
- 회원가입
- 백엔드
- css3
- 스프링
- 웹개발
- html5
- 풀스택
- Ajax
- 웹페이지
- 웹서비스
- Binding
- MVC
- 네비게이터
- jQuery
- javascript
- dbms
- jsp
- 제이쿼리
- 프레임워크
- 마이바티스
- 프론트엔드
- c programming
- spring
- 비밀번호찾기
- Today
- Total
Programmer's Progress
도서 정보 제공 웹 서비스 - 게시글 수정 본문
게시글 수정 구현 방법과 관련된 내용을 소개하기 전에 앞서
게시글 수정을 구현하는 것이 얼마나 복잡하고, 어려운 것인지를 설명해야 할 것 같다.
먼저 게시글을 수정하기에 앞서, 수정해야 할 게시글의 제목, 내용, 이미지 등등을 백엔드 서버에 요청해야 할 것이다.
이때 JQuery의 AJAX기능을 활용해 비동기식으로 요청한다.
게시글의 제목이나 내용 자체는 DB에 저장된 내용을
SQL Mapping방식인 MyBatis프레임워크를 이용해 얻어오기만 하면 되므로 간단하다.
그런데 이미지는 이미지 자체를 DB에 저장한 것이 아니라, 서버 컴퓨터의 D:드라이브에 저장되어있고
DB에는 해당 게시글의 번호, 원본 이미지 이름, 난수 이름만을 저장하고 있다.
아래의 릴레이션 스키마를 확인해보자.
하나의 게시글은 여러 개의 이미지 파일을 가질 수 있지만
하나의 이미지 파일은 하나의 게시글에만 속할 수 있다.
즉, 게시글과 이미지 파일은 1:N의 관계를 갖기 때문에 1의 포지션에 속하는
게시글의 기본키인 게시글 ID를 외래 키로써 사용하도록 free_board_file 테이블을 구성해야 한다.
이때, 각 이미지 파일들은 동일한 이름을 가질 수도 있다.
생각해보자, D:드라이브에 있는 abc.jpg 파일과 C:드라이브에 있는 abc.jpg 파일은 서로
이름은 같지만, 그 내용물, 이미지가 다른 경우가 있을 수 있다.
이때, 만약 무작위 이름( UUID와 같은)이 아닌 원본 이름을 기본키로 사용한다면 문제가 될 것이다.
정확히 어떤 튜플을 가리키는지 알 수 없기 때문이다.
따라서 무작위로 이름을 부여하고 외래 키와 묶어 기본키로 사용함으로써 튜플 구별 문제를 해결했다.
이렇게 릴레이션들이 구성되어있음을 알았으니, 이제 본격적으로 게시글 수정 과정을 살펴보자.
1. 게시글 목록에서 게시글을 클릭하여 viewArticleForm.do 요청을 수행한다
이때 파라미터로는 게시글 번호가 주어진다.
2. 백엔드 서버에서 viewAritcleForm.do 요청임을 컨트롤러에서 확인하고
그에 해당하는 뷰를 사용자에게 리턴한다.
이때 전달받은 파라미터는 article_id를 ModelAndView 객체에 담는다.
3. 클라이언트는 전달받은 뷰에서는 비동기식으로 viewArticle.do 요청을 한다.
파라미터로 아까 백엔드 서버에서 전달받은 article_id를 같이 전달한다.
4. 컨트롤러는 viewArticle.do 요청임을 확인하고, 해당 article_id에 맞는 게시글 제목과 내용,
이미지들의 임시 이름 목록을 비동기식으로 응답한다.
5. 클라이언트는 이미지들의 임시 이름들을 가지고 반복해서 백엔드 서버에 download.do 요청을 한다.
JSTL을 이용하였는데, 여기서 눈여겨볼 것은, fileList 클래스를 갖는 div 태그를 반복해서 생성하는데
이때, 해당 div태그 내에 FILE_TEMP_NAME이라는 hidden값을 같이 생성함에 주목하자.
<div id="FILE">
<c:if test="${not empty FILES}">
<c:forEach items="${FILES}" var="file" varStatus="status">
<div class="fileList">
<img name='file${status.index}' class="image" src="${path}/board/download.do?BOARD=${BOARD}&ARTICLE_ID=${file.ARTICLE_ID}&FILE_TEMP_NAME=${file.FILE_TEMP_NAME}">
<input id="file${status.index}" class="v" type="file" name="file${status.index}" onchange="changeFile(this); preview(this);" style="display:none;"disabled>
<input class="FILE_TEMP_NAME" type="hidden" name="FILE_TEMP_NAME" value="${file.FILE_TEMP_NAME}">
<label class='label' for="file${status.index}"></label>
<input type="button" value="✖" class="v remove" onclick="removeFile(this);"disabled>
</div>
</c:forEach>
</c:if>
</div>
6. 컨트롤러는 download.do 요청에 대하여 아래와 같이 응답한다.
@RequestMapping(value="/board/download.do")
public void downloadFile(HttpServletRequest request, HttpServletResponse response){
try {
String BOARD = request.getParameter("BOARD");
String FILE_TEMP_NAME = request.getParameter("FILE_TEMP_NAME");
int ARTICLE_ID = Integer.parseInt(request.getParameter("ARTICLE_ID"));
if(BOARD==null||(!BOARD.equals("free_board")&&!BOARD.equals("qna_board")&&!BOARD.equals("info_board"))||FILE_TEMP_NAME==null) {
return;
}
File file = new File(REPOSITORY_PATH+"\\"+BOARD+"\\"+ARTICLE_ID+"\\"+FILE_TEMP_NAME);
if(file.exists()) {
response.setHeader("Cache-Control", "no-cache");
response.addHeader("Content-disposition", "attachment; fileName="+FILE_TEMP_NAME);
OutputStream out = response.getOutputStream();
byte[] buffer = new byte[1024*1024*10];
FileInputStream in = new FileInputStream(file);
while(true) {
int cnt = in.read(buffer);
if(cnt== -1) {
break;
}else {
out.write(buffer,0,cnt);
}
}
out.close();
in.close();
}
}catch(Exception e) {
e.printStackTrace();
}
}
7. 결과적으로 아래와 같은 상태가 된다.
CSS는 신경 쓰지 말자... 기능만 일단 구현했을 뿐, 아직 깔끔한 디자인을 생각하지 못했다.
그래, 이제 대략적으로 알겠는데, 그래서 뭐 어쩌라고?
라고 생각할 수 있는데, 지금부터가 본론이다.
게시글 수정의 처리과정은 간략하게 설명하면 아래와 같다.
1-1. 파일 추가를 선택하면 fileList클래스를 갖는 div 태그가 생성된다.
그리고 기존에 있던 hidden 타입의 TEMP_FILE_NAME를 삭제한다.
단, hidden 타입의 TEMP_FILE_NAME은 없다.
1-2. 파일 삭제를 선택하면 fileList클래스를 갖는 div 태그가 아예 삭제된다.
그리고 기존에 있던 hidden 타입의 TEMP_FILE_NAME를 삭제한다.
즉, hidden 타입의 TEMP_FILE_NAME은 없다.
1-3. 파일 수정을 선택하면 fileList클래스를 갖는 div 태그는 그대로 남아있다.
다만, input type이 file인 태그에 첨부된 이미지 파일은 달라진다.
그리고 기존에 있던 hidden 타입의 TEMP_FILE_NAME를 삭제한다.
즉, hidden 타입의 TEMP_FILE_NAME은 없다.
1-4. 파일을 수정하지 않고 가만히 놔두면 fileList 클래스를 갖는 div 태그는 그대로 남아있다.
그리고 기존에 있던 hidden 타입의 TEMP_FILE_NAME는 삭제하지 않는다.
즉, hidden 타입의 TEMP_FILE_NAME도 그대로 남아있다.
2. 수정하기 버튼을 클릭하면 백엔드 서버의 컨트롤러에 modifyArticle.do 요청을 수행한다.
이때, 클라이언트는 formdata객체를 통해 파라미터를 전송해야 하며
컨트롤러 역시 MultipartRequest의 형식으로 처리해야 한다.
클라이언트가 요청 시 전달해야 할 파라미터는 아래와 같다.
게시글 제목, 내용, 새로 첨부한 이미지 파일들, TEMP_FILE_NAME값들을 전달한다.
이때, 1-4의 이미지들만 TEMP_FILE_NAME이 서버로 전송된다.
그 외에는 모두 삭제되었기 때문에 전송하지 않는다.
3. 백엔드 서버는 TEMP_FILE_NAME이 그대로 남아있는 1-4의 이미지 파일들은
삭제나 수정 등의 변경사항이 없는 것으로 판단하고 삭제하지 않는다.
그러나 1-1, 1-2, 1-3의 이미지 파일들은 추가, 삭제, 수정 등의 이유로
DB에서의 정보가 변경되어야 하는 이미지 파일들로 판단해야 한다.
<delete id="modifyFiles" parameterType="java.util.HashMap">
DELETE
FROM ${BOARD_FILE}
WHERE ARTICLE_ID = ${ARTICLE_ID} AND FILE_TEMP_NAME NOT IN <foreach collection="FILE_TEMP_NAMES" open="(" separator="," close=")" item="FILE_TEMP_NAME">#{FILE_TEMP_NAME}</foreach>
</delete>
위와 같이 FILE_TEMP_NAME이 사라져 버린 1-1, 1-2, 1-3의 이미지는 DB 테이블에서 제거한다.
4. 백엔드 서버에 저장된 1-4의 이미지들은 우선 TEMP 폴더로 이 동시 킨 후
해당 게시글의 이미지 파일을 저장하는 폴더를 삭제한다.
그렇게 되면 변경이 있었던 1-1, 1-2, 1-3의 이미지들은 완전히 삭제된다.
다시 폴더를 생성하고
TEMP폴더에 있던 이미지 파일들을 다시 새로 생성된 폴더로 이동시킨다.
결과적으로 변경점이 없는 1-4의 이미지만 존재한다.
5. 1-1, 1-2, 1-3의 이미지들은 input file을 통해 컨트롤러에서 addArticle.do
요청을 처리할 때와 마찬가지로 처리하면 된다.
쉽게 말하자면, 게시글을 읽을 때 각 이미지의 원본 이름값을 가지고 있다가
추가, 수정, 삭제되는 이미지들은 원본 이름을 지워버리고
변경점이 없는 이미지들의 원본 이름만을 이용하여 DB, 서버에서 삭제되어야 할 이미지를 구분한 것이다.
그 외에도 DB에서 삭제하지 않고 삭제되어야 할 이미지 파일들의 튜플에 삭제 여부를 Y, N으로 나타내기만 하고
이미지 파일 역시 굳이 삭제하지 않고 내버려 두었다가 MERGE를 이용해 정기적으로 삭제를 한다든지
아니면 첨부파일을 1개로 제한한다든지, 게시글을 수정할 때는 기존 이미지 파일들도 다시 첨부를 하게 한다든지...
여러 가지 구현 방법은 많았지만, 즉시 DB에서 튜플을 삭제하고
서버에서 이미지 파일을 제거함으로써 저장공간 낭비가 없었기 때문에, 이 방식을 택했다.
'Web Service > 도서 정보 제공 웹 서비스' 카테고리의 다른 글
도서 정보 제공 웹 서비스 - 게시글 삭제 (0) | 2022.02.07 |
---|---|
도서 정보 제공 웹 서비스 - 게시글 읽기 (0) | 2022.02.03 |
도서 정보 제공 웹 서비스 - 게시글 쓰기 (0) | 2022.01.20 |
도서 정보 제공 웹 서비스 - Gmail을 활용한 비밀번호 찾기 (0) | 2022.01.09 |
도서 정보 제공 웹 서비스 - 메세지 읽기, Spring, Mybatis 프레임워크 (0) | 2022.01.06 |