이어서 댓글 수정 / 삭제 기능 구현
● 수정과 삭제를 하려면 댓글번호와 댓글내용을 보낼 수 있어야하기에 전체화면에 addEventListener를 걸어주고 버튼을 누르면 내가 누른 버튼의 정보를 가져올 수 있게 만듬. 버튼과 내용에 클래스와 댓글번호 정보를 줄 예정
댓글 내용에는 id로 댓글번호를 줘서 가져올 수 있게함.
html += `<button type="button" data-cno="${result[i].cno}" class="cmtModBtn">수정</button>`;
html += `<button type="button" data-cno="${result[i].cno}" class="cmtDelBtn">삭제</button><br>`;
html += `<input type="text" class="cmtText" id="${result[i].cno}" value="${result[i].content}">`;
이런식으로 댓글번호 정보와 class를 주고
document.addEventListener('click', (e) => {
// 수정
if (e.target.classList.contains('cmtModBtn')) {
// 수정에 대한 처리
let cnoVal = e.target.dataset.cno;
// cno 값을 id로 사용할 경우
let cmtText = document.getElementById(cnoVal).value;
console.log(cmtText);
// 내 타겟을 기준으로 가장 가까운 div를 찾기 closest('div');
//let div = e.target.closest('div');
//console.log(div); // 내 댓글 객체 찾기
//let cmtText2 = div.querySelector('.cmtText').value;
//console.log(cmtText2);
let cmtData = {
cno: cnoVal,
content: cmtText
}
● 수정 버튼을 눌렀을 때 번호와 댓글 객체를 가져올 수있도록해서 데이터를 구성해줌 (cno, content 객체)
● cmtData를 받아서 서버에 post 하는 메서드를 만들어줘야함
async function updateCommentToServer(cmtData) {
// 수정 : cno, content 객체를 보내서 isOk return => post
try {
console.log(cmtData);
const url = "/cmt/modify";
const config = {
method: 'post',
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
body: JSON.stringify(cmtData)
}
const resp = await fetch(url, config);
const result = await resp.text();
return result;
} catch (error) {
console.log(error);
}
};
● 서버로 cmtData를 보내서 isOk를 리턴받는 메서드를 사용해서
updateCommentToServer(cmtData).then(result => {
console.log(result);
if (result == '1') {
alert("댓글 수정 성공!!");
} else {
alert("댓글 수정 실패..");
}
});
// 수정 후 수정된 내용 출력
printList(bnoVal);
수정이 성공했을때와 실패했을때를 나타낼 수 있게하고 수정되면 내용이 다시출력해서 댓글 리스트가 바로 반영될 수 있게함.
● 이제 controller를 만들어야하는데 case : "modify" 일 때 post 했던거처럼 js 가 보낸 데이터를 변환해서 받는 작업을 거쳐주고 서비스 , 다오 연결하고 매퍼에 구문 작성.
case "modify" :
try {
StringBuffer sb = new StringBuffer();
String line ="";
BufferedReader br = request.getReader();
while( (line = br.readLine()) != null ) {
sb.append(line);
}
log.info(">>>> modify sb > {} ", sb.toString());
JSONParser parser = new JSONParser();
JSONObject jsonObj = (JSONObject)parser.parse(sb.toString());
log.info(">>> modify jsonObj >> {}", jsonObj);
int cno = Integer.parseInt(jsonObj.get("cno").toString());
String content = jsonObj.get("content").toString();
CommentVO cvo = new CommentVO(cno, content);
int isOk = csv.modify(cvo);
log.info(" >>>> modify > {}", (isOk > 0 ? "성공" : "실패"));
PrintWriter out = response.getWriter();
out.print(isOk);
} catch (Exception e) {
log.info("cmt modify list error");
e.printStackTrace();
}
break;
@Override
public int modify(CommentVO cvo) {
log.info("comment dao modify in");
int isOk = sql.insert("CommentMapper.modify" , cvo);
if(isOk > 0 ) {
sql.commit();
}
return isOk;
}
<update id="modify">
update comment set content = #{content}, regdate=now() where cno = #{cno}
</update>
● 삭제 기능도 js 부터 구현하는데 cno 만 받아서 cno를 서버로보내 isOk를 리턴하게 만들고
async function deleteCommentToServer(cno) {
try {
const resp = await fetch("/cmt/delete?cno=" + cno);
const result = await resp.text();
return result;
} catch (error) {
console.log(error);
}
};
수정버튼이 아니라 삭제버튼일 때 코드를 작성
else {
// 삭제
if (e.target.classList.contains('cmtDelBtn')) {
// 삭제에 대한 처리
let cnoVal = e.target.dataset.cno;
console.log(cnoVal);
// 삭제 비동기 함수 호출 result 받아서 alert 띄우기.
// 삭제 후 출력 메서드 호출
deleteCommentToServer(cnoVal).then(result => {
console.log(result);
if (result == '1') {
alert("댓글 삭제 성공!!");
} else {
alert("댓글 삭제 실패..");
}
})
// 삭제 후 출력
printList(bnoVal);
}
}
● 삭제는 그냥 cno를 받아와서 cno에 맞는 데이터를삭제만 해주면 됨.
case "delete" :
try {
int cno = Integer.parseInt(request.getParameter("cno"));
int isOk = csv.delete(cno);
log.info(" >>>> delete > {}", (isOk > 0 ? "성공" : "실패"));
PrintWriter out = response.getWriter();
out.print(isOk);
} catch (Exception e) {
log.info("cmt delete list error");
e.printStackTrace();
}
break;
}
@Override
public int delete(int cno) {
log.info("comment dao delete in");
int isOk = sql.insert("CommentMapper.delete" , cno);
if(isOk > 0 ) {
sql.commit();
}
return isOk;
}
<delete id="delete">
delete from comment where cno = #{cno}
</delete>
수정 / 삭제 기능 끝.
조회수 기능 구현
● 조회수 기능을 구현하기위해 board 테이블에 readCount를 add해준다.
alter table board add readCount int default 0;
● boardVO에 readCount 칼럼에 맞게 추가해줌.
● list와 detail 화면에 조회수가 보여질 수 있도록 jsp 파일에 구현해준다.
● 이 후 list에서 detail로 넘어갈때 조회수가 넘어가도록 case 가 detail 일 때 readCount를 update해서 +1 해주게 서비스 다오 , 매퍼를 구축
case "detail":
case "modify":
try {
int bno = Integer.parseInt(request.getParameter("bno"));
BoardVO bvo = bsv.getDetail(bno);
log.info(">>>> detail bvo >> {}", bvo);
if (path.equals("detail")) {
int readCountOk = bsv.readCountPlus(bno);
log.info(">>>> readCount +1 >>> " + (readCountOk > 0 ? "성공" : "실패"));
bvo = bsv.getDetail(bno);
log.info(">>> readCount >> {}", bvo.getReadCount());
}
request.setAttribute("bvo", bvo);
destPage = "/board/"+path+".jsp";
} catch (Exception e) {
log.info("detail error");
e.printStackTrace();
}
break;
if(path.equals("detail") 부터 추가해서 된 내용. getDetail이 readCount가 +1 되기전이므로 다시 호출해줌.
@Override
public int readCountPlus(int bno) {
log.info(">>>> readCount DAO in !!");
int readCountOk = sql.update("BoardMapper.readCount",bno);
if(readCountOk > 0) {
sql.commit();
}
return readCountOk;
}
<update id="readCount" parameterType="int">
update board set
readCount = readCount + 1
where bno = #{bno}
</update>
이러면 조회수 기능도 끝.
첨부 파일 업로드
하나의 게시글에 첨부파일이 여러개 추가가 가능하다면... (테이블을 별도 생성)
일단 하나의 게시글에 첨부파일은 1개만 가능하도록 구현해볼 예정 ( 이미지파일만 )
(프로젝트 내부에 저장) => 프로젝트 외부에 따로 저장
파일 객체가 제공하는 값 ( 파일 테이블을 생성하면 저장 )
파일이름, 파일형식, 크기, 일자...
board 테이블에 imageFile (이름을 저장하는 칼럼 추가) varchar(500)
alter table board add imageFile varchar(500);
BoardVO => imageFile 칼럼 반영하여 수정
내가 첨부한 이미지는 list.jsp => 제목 앞에 그림추가
detail.jsp,modify.jsp => content 위쪽에 그림추가
첨부파일 라이브러리 추가
commons-fileupload-1.4.jar
commons-io-2.11.0.jar
thumbnailator-0.4.17.jar
● register.jsp input 파일형식으로 정보를 받을 수 있도록 넣고 enctype을 파일형식을 인지할 수 있게 바꿔줌.
enctype ="multipart/form-data": 첨부파일을 가져갈 때 사용
첨부파일 있는 경우 처리
- bvo를 구성하여 DB로 전송
- file을 저장하는 작업 => 파일 이름만 DB imageFile에 저장
- 첨부파일 형식으로 들어오게되면 모든 파라미터는 바이트단위로 분해돼서 전송
- 바이트 단위로 전송된 파라미터의 값을 String으로 조합을 해야함.
● 파일을 업로드할 물리적인 경로 설정
savePath = getServletContext().getRealPath("/_fileUpload");
log.info(">>>> savePath > {}" , savePath);
File fileDir = new File(savePath);
DiskFileItemFactory fileItemFactory = new DiskFileItemFactory();
● 파일 저장을 위한 임시 메모리설정, 저장위치를 담은 File 객체
fileItemFactory.setSizeThreshold(1024*1024*3);
fileItemFactory.setRepository(fileDir);
● 빈객체 생성 후 set
BoardVO bvo = new BoardVO();
// multipart/form-data 형식으로 넘어온 request 객체를 다루기 쉽게 변환해주는 객체
ServletFileUpload fileUpload = new ServletFileUpload(fileItemFactory);
// request 객체를 FileItem 형식의 리스트로 리턴
List<FileItem> itemList = fileUpload.parseRequest(request);
// title, writer, content => text / imageFile => image
for(FileItem item : itemList) {
log.info(" >>>> FileItem >> {} " , item.toString());
switch (item.getFieldName()) {
case "title":
bvo.setTitle(item.getString("utf-8"));
break;
case "writer":
bvo.setWriter(item.getString("utf-8"));
break;
case "content":
bvo.setContent(item.getString("utf-8"));
break;
case "imageFile":
// 이미지 파일 여부를 체크
if(item.getSize() > 0 ) {
//파일 이름 추출
// 경로+ ~~~/dog.jpg
String fileName = item.getName();
// 경로 빼고 이름만 가져오기.
// String fileName2 = item.getName().substring(item.getName().lastIndexOf(File.separator)+1);
// File.separator : 파일 경로 기호 => 운영체제마다 다를 수 있어서 자동 변환
// 시스템의 시간을 이용하여 파일을 구분 / 시간 dog.jpg
// UUID를 사용하는 구분도 있음. (=> 많이사용)
fileName = System.currentTimeMillis()+"_"+fileName;
// 경로 만들어놓은 파일 fileDir + / (File.separator) + fileName
File uploadFilePath = new File(fileDir+File.separator+fileName);
log.info(">>> uploadFilePath > {}" , uploadFilePath.toString());
// 저장
try {
item.write(uploadFilePath); // 객체를 디스크에 쓰기
bvo.setImageFile(fileName); // bvo에 저장할 값 (DB에 들어가는 값 )
// 썸네일 작업 : 리스트 페이지에서 트래픽 과다 사용 방지
// fileDir + / + _th_ + fileName
Thumbnails.of(uploadFilePath).size(75, 75).toFile(new File(fileDir+File.separator+"_th_"+fileName));
} catch (Exception e) {
log.info(">>> file writer on disk error");
e.printStackTrace();
}
}
break;
D:\sh_240725\jsp_workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\jsp_study\_fileUpload
안에 이미지가 원본과 썸네일 작업한 이미지 두개가 저장됨. tmp0으로 복사해서 넣어두기때문에 원래 폴더에 표시하지않아도 저 위치에 저장돼있어 쓸 수 있음.
● list, detail , modify에 필요한 위치에 이미지 태그 추가해서 화면에 표시
<img alt="" src="/_fileUpload/_th_${bvo.imageFile }">
파일수정이 가능하도록 구현해보기
● 파일 변경을위해 modify.jsp에 input 파일을 새로운 파일의 이름 newFile로 추가, 그리고 원래 파일의 정보를 가져오기위해 hidden으로 input 추가.
<!-- 파일변경 -->
<tr>
<th>image</th>
<td>
<input type="hidden" name="imageFile" value="${bvo.imageFile }">
<input type="file" name="newFile" accept="image/jpg, image/gif, image/png">
</td>
</tr>
● 파일형식의 정보를 전송할 수 있도록 form 태그에 enctype ="multipart/form-data" 추가 컨트롤러에서 원래있던 String 형식의 정보를 전송해서 받아오는거는 이제 file 형식의 enctype때문에 작동하지않으니 byte로 쪼개서 보내줘야함. 파일추가때랑 똑같음.
// 첨부파일 처리 포함 방식
savePath = getServletContext().getRealPath("_fileUpload");
File fileDir = new File(savePath);
DiskFileItemFactory fileItemFactory = new DiskFileItemFactory();
fileItemFactory.setSizeThreshold(1024*1024*3); // 3MB 정도 설정
fileItemFactory.setRepository(fileDir);
BoardVO bvo = new BoardVO();
ServletFileUpload fileUpload = new ServletFileUpload(fileItemFactory);
List<FileItem> itemList = fileUpload.parseRequest(request);
● 원래 이미지가 있었는데 변경하는 케이스와 이미지가 없었던 케이스를 나누어서 처리해야함.
원래 있었던 이미지 파일을 old_file로 일단 정의하고 파일명만 넣어줌.
● 기존파일이 존재한다면 기존파일을 삭제하는 작업을 해주는 핸들러는 별도로 작업
public class FileRemoveHandler {
private static final Logger log = LoggerFactory.getLogger(FileRemoveHandler.class);
// savePath, imageFileName 매개변수로 받아서 파일을 삭제하는 메서드
public int deleteFile(String savePath, String imageFileName) {
// return 삭제 여부의 값을 리턴
// 파일 삭제시 ... 경로포함 파일명.delete return true/false 로 리턴
boolean isDel = false; // 삭제가 잘 되었는지 체크 변수
log.info(">>> deleteFile method 접근 > {} ", imageFileName);
// 기존 파일 이름 객체 생성
File fileDir = new File(savePath);
File removeFile = new File(fileDir+File.separator+imageFileName);
File removeThFile = new File(fileDir+File.separator+"_th_"+imageFileName);
// 파일이 존재해야 삭제
if(removeFile.exists() || removeThFile.exists()) {
isDel = removeFile.delete(); // 원래파일 삭제
log.info(">>> removeFile !! > {} ", isDel);
if(isDel) {
isDel = removeThFile.delete();
log.info(">>> removeThFile !! > {} ", isDel);
}
}
return isDel? 1 : 0 ;
}
}
핸들러 패키지를 따로만들어서 구성 savePath와 imageFileName을 매개변수로 받아서 삭제.
● for문을 돌려서 itemList에 FileItem 객체에 셋팅.
String old_file = null;
for(FileItem item : itemList) {
switch (item.getFieldName()) {
case "bno":
bvo.setBno(Integer.parseInt(item.getString("utf-8")));
break;
case "title":
bvo.setTitle(item.getString("utf-8"));
break;
case "content":
bvo.setContent(item.getString("utf-8"));
break;
여기까지는 set 그냥해주고 인코딩해주어야함.
case "imageFile":
// 기존 파일 => 있을 수도 있고, 없을 수도 있고
old_file = item.getString("utf-8"); // 파일명만..
break;
기존파일이 있을때에는 선언해둔 old_file에 파일명을 담아줌.
case "newFile":
// 새로 추가된 파일 => 있을 수도 있고, 없을 수도 있고
if(item.getSize() > 0) {
if(old_file != null) {
// 기존파일이 존재한다면 ...
// 파일 삭제작업 : 별도 핸들러로 작업
FileRemoveHandler fileHandler = new FileRemoveHandler();
sOk = fileHandler.deleteFile(savePath, old_file);
}
// 새로운 파일 등록 작업
String fileName = System.currentTimeMillis()+"_"+item.getName();
File uploadFilePath = new File(fileDir+File.separator+fileName);
try {
item.write(uploadFilePath);
bvo.setImageFile(fileName);
Thumbnails.of(uploadFilePath).size(75, 75).toFile(new File(fileDir+File.separator+"_th_"+fileName));
} catch (Exception e) {
log.info("File writer update error!");
e.printStackTrace();
}
} else {
// 기존파일 있지만, 새로운 이미지 파일이 없다면...
// 기존 객체를 담기
bvo.setImageFile(old_file);
}
새로추가된 파일은 만들어둔 핸들러에 메서드를 불러와서 기존 파일이 존재한다면 삭제하고 새로운 파일 등록작업을 거쳐줌 새로운 파일 등록하는 과정은 파일추가했을때랑 똑같음.
그리고 기존파일이 있지만 새로운 파일이 없다면 그냥 그대로 기존 객체를 담아주면서 끝.
'JSP&JSTL 수업 정리' 카테고리의 다른 글
JSP/JSTL 5일차. (0) | 2024.10.25 |
---|---|
JSP/JSTL 3일차. (0) | 2024.10.23 |
JSP/JSTL 2일차. (0) | 2024.10.22 |
JSP/JSTL 1일차. (0) | 2024.10.21 |