공부/web-server

1219 WebServer 9일차

삶은고구마 2023. 12. 19. 19:57

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);
    }
}

 

수정 후.

내가 원하는대로 보기 좋게 바뀌었다.

 


 

https://jang2r.tistory.com/27

 

개발노트 :: 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

https://riucc.tistory.com/354

 

[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

구조