1219 WebServer 9일차
1218은 테스트때문에 진도를 안나감..
목차
- AdminMemberListServlet
기능 통합 (검색, 페이지바기능 합침)
- BoardDetailServlet 작업
- BoardCreateServlet 추가
BoardDetailServlet
게시판 상세 페이지 작업을 진행하였다.
말 그대로, 게시판 목록에서 내가 원하는 제목을 클릭했을 때 해당 게시글의 세부사항을 볼 수 있는 페이지 작업을 진행.
구성요소는 위에서부터 제목/ 작성자 아이디/ 내용 / 조회수/ 작성일 이다.
글을 썼을 때는 분명
띄우기1
띄우기2
이런식으로 개행을 했는데 ..?
막상 값을 받아와 출력하면 한줄로 이어져있다.
console.log("boardCreate.js - 게시글 작성시 유효성 검사하는 js페이지");
document.boardCreateFrm.addEventListener('submit',()=> {
const frm = e.target;
const title = frm.title;
const content = frm.content;
//제목 유효성 검사
if(!/^.+$/.test(title.value.trim()))
{
alert('제목을 반드시 작성해주세요.');
e.preventDefault();
return; //제출막기..
}
//내용 유효성 검사
//정규표현식의 .에는 \n이 포함되지 않는다.
if(!/^(.|\n)+$/.test(content.value.trim()))
{
alert('내용을 반드시 작성해주세요.');
e.preventDefault();
return; //제출막기..
}
});
유효성 검사 시 \n이 포함되지 않아 문자열에 그대로 반영되면서 개행이 진행되지 않은 것 같은데[?
utilClass를 따로 만들어 내용문자열에 포함된 \n을 <br>로 바꿔주는 메소드를 만들었다.
public static String convertLineFeedToBr(String str)
{
return str.replaceAll("\n","<br/>");
}
그리고 detailServelt에서 해당 메소드를 사용해서 개행 처리를 한다.
해당 내용이 포함된 BoardDetailServlet 단이다.
@WebServlet("/board/BoardDetail")
public class BoardDetailServlet extends HttpServlet {
private BoardService boardService = new BoardService();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.사용자 입력값 처리
long id = Long.parseLong(req.getParameter("id"));
System.out.println(id);
//2.업무로직
BoardVo board = boardService.findById(id);
//개행문자(\n) -> <br>
board.setContent(HelloMvcUtils.convertLineFeedToBr(board.getContent()));
req.setAttribute("board",board);
System.out.println(board);
//3.forward
req.getRequestDispatcher("/WEB-INF/views/board/boardDetail.jsp").forward(req,resp);
}
}
수정 후.
내가 원하는대로 보기 좋게 바뀌었다.
개발노트 :: JSTL 날짜 포맷 <fmt:formatDate> <fmt:parseDate>
JSTL 날짜 포맷 fmt:parseDate - 문자열 -> Date 타입으로 변경 fmt:formatDate - Date 타입 -> 문자열으로 변경 1. JSTL ${date1} 결과값 Tue Jan 01 00:00:00 KST 2019 2. JSTL ${date2} 결과값 2019-01-01 3. pattern 형식 y(The year) 년
jang2r.tistory.com
작성일 날짜 변환
받아온 board.regDate를 yy/MM/dd HH:mm형식으로 바로 변경할 수 없다.
중간에 형변환을 한 번 더 해야 가능함.
즉, String->date형->원하는형 으로 진행한다.
fmt:parseDate - String형을 받아 원하는 포맷 Date형으로 변환
fmt:formatDate - Date형을 받아 원하는 포맷으로 변환
작성일
<fmt:parseDate value="${board.regDate}" pattern="yyyy-MM-dd'T'HH:mm" var="regDate"/>
<fmt:formatDate value="${regDate}" pattern="yy/MM/dd HH:mm"/>
그 외에 상세 페이지에 작성시 첨부한 첨부파일 갯수와 파일명도 보여줄 수 있는데
이건 BoardCreateServlet에서 정리하겠다.
아래는 boardDetail.jsp의 일부인데,
c:foreach를 사용해 attachments의 갯수만큼 반복문을 실행하여 갯수만큼 첨부파일명을 보여주는 코드다.
<c:forEach items = "${board.attachments}" var="attach">
<a href="#" class="flex items-center text-blue-600 hover:underline">
<img src="../images/file.png" class="w-[16px] mr-1">
${attach.originalFilename}
</a>
BoardCreateServlet
boardCreate.jsp 에서 정말 중요한 코드가 있음.
제목,내용,첨부파일등을 전송해주는 form을 아래 형식으로 작성해줘야 한다
1.name은 각자 원하는 것으로 네이밍하면 되고
2.method는 post방식이다. (insert - dml)
3.enctype = "multipart/form-data"를 꼭 써줘야 한다.
input file형식을 전송할 때 필요한 속성값이며, 여러개를 첨부할 수 있다.
★주의점★
이 방법을 사용하면 req.getParameter로 값을 받아 올 수 없다. 확인하면 null값이다.
정 받고 싶다면 해당 멀티파트리퀘스트객체. getParameter로 받아 올 수있다 하는데 나중에 테스트 해보겠다.
<form name="boardCreateFrm" method="post" enctype="multipart/form-data">
유익한 링크들.
https://m.blog.naver.com/bb_/222134798237
[JAVA] 멀티파트 폼(multipart/form-data)으로 넘긴 파라미터 request.getParameter 로 받기
[JAVA] 멀티파트 폼(multipart/form-data)으로 넘긴 파라미터 request.getParameter 로 받기 멀티파트...
blog.naver.com
[JSP] - 파일 enctype="multipart/form-data" 사용 시 request.getParameter null 해결방법
○ 파일 enctype="multipart/form-data" 사용 시 request.getParameter null 해결방법 파일이나 이미지 처리 폼에서 enctype="multipart/form-data" 을 사용하는데,대부분 파일이나 이미지는 게시물 작성같은 다른 입력
riucc.tistory.com
@WebServlet("/board/boardCreate")
public class BoardCreateServlet extends HttpServlet
{
private BoardService boardService = new BoardService();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getRequestDispatcher("/WEB-INF/views/board/boardCreate.jsp").forward(req,resp);
}
/**
* 파일 업로드 처리..
* 1.commons-io,commons-fileupload 의존성 추가(pom.xml)
* 2.form[method=post][enctype=multipart/form-data]
* 3.DiskFileItemFacroty / ServletFileUpload 요청처리
* - 저장경로
* - 파일 최대 크기
*
*
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.사용자 입력값 처리 및 파일 업로드
File repository = new File("C:\\Workspaces\\web_server_workspace\\helloo-mvc\\src\\main\\webapp\\upload\\board");
int sizeThreshold = 10 * 1024 * 1024; //10MB
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(repository);
factory.setSizeThreshold(sizeThreshold);
BoardVo board = new BoardVo();
//실제 요청을 핸들링할 객체..
ServletFileUpload s = new ServletFileUpload(factory);
try {
//전송된 값을 하나의 FileItem으로 처리
List<FileItem> fileItemList = s.parseRequest(req);
for(FileItem item: fileItemList)
{
String name = item.getFieldName();//input의 name
if(item.isFormField()){
//일반 텍스트 : board 객체에 설정
String value = item.getString("utf-8");
System.out.println(name+"/"+value);
//Board객체에 설정자 로직 구현..
board.setValue(name,value);
}
else {
//파일 : 서버 컴에 저장 파일정보를 attachment 객체로 만들어서 db에 저장
if(item.getSize() > 0) {
System.out.println(name);
String originalFileName = item.getName();
System.out.println("원본 파일명: " + originalFileName);
System.out.println("파일크기: " + item.getSize() + "byte");
int dotIndex = originalFileName.lastIndexOf(".");
String ext = dotIndex >- 1? originalFileName.substring(dotIndex) : "";
System.out.println("extextextextextext: " + ext ); //.png
UUID uuid = UUID.randomUUID();//랜덤 uuid 발급 b9c2bc5f-9a0c-4e7f-9d87-7d6feb70e244
String renamedFileName = uuid+ext; //저장된 파일명(덮어쓰기방지,인코딩이슈방지) 위에 발급된 uuid에 원본의 확장자명을 붙여서 새 파일명만든다.
System.out.println("새 파일명 : "+renamedFileName);
//서버 컴퓨터 파일 저장
File upFile = new File(repository,renamedFileName);
item.write(upFile);
//Attachment 객체 생성
Attachment attach = new Attachment();
//현재 할 수 있는건 원본명과, 새이름 set.
attach.setOriginalFilename(originalFileName);
attach.setRenamedFilename(renamedFileName);
board.addAttachment(attach);
}
}
}
}
catch (Exception e)
{
throw new RuntimeException(e);
}
System.out.println("board+attach : " +board);//board객체,attach객체 모두 확인되어야함.
//2.업무로직
int result = boardService.insertBoard(board);
req.getSession().setAttribute("msg","게시글을 성공적으로 등록하였습니다");
//3.게시판 목록페이지로 redirect
resp.sendRedirect(req.getContextPath()+"/board/boardList");
}
}
- 기존 board 객체 클래스에는 첨부파일에 관련된 필드가 없었다.
- board entity는 테이블과 1대1매칭되는 관계라, 새로운 "첨부파일 관련 필드" 를 추가하기 보다는,
- 첨부파일에 관련된 Attachment 테이블과 1대1로 매칭되는 Attachment 클래스를 만든후
(pk,board id,원본파일명,새파일명,등록일)
- board를 상속하는 확장클래스인 boardVo를 만든다.
예전에 jdbc 수업 때 주소록 클래스를 이런식으로 만들었다.
1대 N의 관계 일 때 이런 구조로 클래스를 짜면 될 것 같다.
한 회원은 여러 주소를 가질 수 있다 , 한 게시글은 첨부파일을 안가질 수도, 1개,2개 등 여러 개 가질 수있다.
/**
* vo 클래스 value object
* - immutable한 value객체(수정하지않는,불변)
* - entity 클래스를 확장한 객체
* - 기존 entity는 수정하지말고 상속하여 확장/사용
*/
public class BoardVo extends Board {
//보드를 상속하고, 원래 보드에 없는 첨부파일 갯수 필드를 하나 추가함
private int attachCount;
private List<Attachment> attachments = new ArrayList<>();
.
.
.
생략
기존 board 객체를 사용한 메소드는 boardVo로 변경해주고,
BoardService에서 insertBoard(게시글 추가기능)은 아래와 같이 작성해준다.
첨부파일은 n개 일 수도있으니 반복문을 사용하여 insert해준다.
그리고 dml의 특성상, 전부 성공 commit / 전부 실패 rollback 처리를 해줘야 한다.
보드객체/첨부파일객체를 따로 insert하여 모두 성공했을 때만 session.commit 처리를 해야한다.
=>만약 보드객체값만 성공하고 첨부파일은 실패한다면 일관성에 위배 되지않을까?
그럴바엔 아예 실패로 처리해서 게시글 자체가 insert되지 않도록 해야한다.
:★하나의 세션,하나의 메소드 안에서 관리하기
public int insertBoard(BoardVo board)
{
int result = 0;
SqlSession session = getSqlSession();
try
{
//board + attach 둘 다 insert하고 commit or rollback 처리 해야함
//board 테이블에 등록
result = boardDao.insertBoard(session,board);
System.out.println("board get id())" + board.getId());
//attach 테이블에 등록
List<Attachment> attachments = board.getAttachments();
if(!attachments.isEmpty())
{
for(Attachment attach : attachments)
{
attach.setBoardId(board.getId());
result = boardDao.insertAttachment(session,attach);
}
}
session.commit();
}
catch(Exception e)
{
//에러가 발생할 경우 롤백하기
session.rollback();
throw e;
}
finally
{
session.close();
}
return result;
}
BoardDao
//게시글추가
public int insertBoard(SqlSession session, Board board)
{
return session.insert("board.insertBoard",board);
}
//첨부파일추가
public int insertAttachment(SqlSession session, Attachment attach) {
return session.insert("board.insertAttachment",attach);
}
board-mapper.xml
<insert id="insertBoard">
insert into
board (id, title, member_id, content)
values(
seq_board_id.nextval,
#{title},
#{memberId},
#{content}
)
<selectKey order="AFTER" resultType="_int" keyProperty="id">
select
seq_board_id.currval
from
dual
</selectKey>
</insert>
<insert id="insertAttachment">
insert into
attachment(id,board_id,original_filename,renamed_filename)
values(
seq_attachment_id.nextval,
#{boardId},
#{originalFilename},
#{renamedFilename}
)
</insert>
board insert 후 해당 board의 pk값인 id를 fk로 써야 하는 attachment를 insert 해야 하는데
(어느 게시글에 무슨 첨부파일이 있는지 알아야 하니까..^^)
selectKey 태그를 사용하면 board insert 이후(after) 해당 id 값을 알 수 있다.
그리고 이 값을 사용해 attachment insert 시 사용 할 수 있다!
boardService에서 attach.setBoardId(board.getId());를 보면
board의 아이디를 받아 attach에 setBoardId 한다. 그리고 다음 줄에서 바로 dao insertAttachment 진행.
System.out.println("board get id())" + board.getId());
//attach 테이블에 등록
List<Attachment> attachments = board.getAttachments();
if(!attachments.isEmpty())
{
for(Attachment attach : attachments)
{
attach.setBoardId(board.getId());
result = boardDao.insertAttachment(session,attach);
}
}
selectKey 에 관한 도움글
https://yookeun.github.io/java/2014/07/11/mybatis-selectkey/
mybatis에서 selectKey 사용법
DB작업을 하다보면 먼저 사전에 어떤 키값을 가져와서 증가시켜서 입력하거나 혹은 입력후에 증가된 키값을 가져올 필요가 있다. 이럴때 mybatis에서 제공하는 selectKey를 이용하면 별도의 쿼리로
yookeun.github.io