본문 바로가기
다양한 기술들/레거시 Web 실습

[10] 게시판심화1 : 상세보기(+첨부파일 조회 및 다운로드)

by 예스p 2023. 1. 12.

첨부파일 게시글

첨부파일이 있을 수도 있는 게시글을 조회하면서

첨부된 파일을 다운로드할 수 있는 페이지를 구성해보자


 

 

<< 이전포스팅에서 완료된 요청     |    이번포스팅에서 구현할 요청 >>

기능 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 재요청
일반 - 상세페이지 /detail.bo no=? 실패시  view/common/errorPage.jsp
성공시 views/board/boardDetailView.jsp
일반 - 수정페이지 /updateForm.bo no=   view/board/boardUpdateForm.jsp
일반 - 수정요청 /update.bo   실패시 view/common/errorPage.jsp
성공시 /detail.bo   url 재요청

 


목록클릭시 상세페이지

게시글 목록에서 한줄을 클릭하면 해당글의 상세페이지가 뜨도록 한다.


STEP1) boardDetailView.jsp

  • 생성위치 : webProject/src/main/webapp/views/board
  • 게시판 목록을 클릭했을때 뜨는 상세페이지를 가작성한다.
.....
<h2>일반게시판 상세보기</h2>
카테고리 : ~~~
제목 : ~제목자리~
작성자 : ~아이디~
작성일 : ~yyyy/mm/dd~
내용 :  ~내용자리~
첨부파일 : ~~
<!-- case1. 첨부파일이 없을경우-->
첨부파일이 없습니다
<!-- case2. 첨부파일이 있을 경우-->
<a href="첨부파일의 저장경로, 첨부파일의 실제 저장된 파일명">~원본명~</a>
<a href="">목록가기</a>
<!-- 로그인한 회원이 게시글을 쓴 회원일경우에만 보이는 버튼 -->
<a href="">수정하기</a>
<a href="">삭제하기</a>

 

 

STEP2) board-mapper.xml (수정)

  • 첨부파일이 있는 게시글을 조회할 때는 세가지 sql문이 필요하다
  • 1) 조회수 증가시키기 
    - 정상적으로 조회할 수 있는 게시글인가를 판단 후 조회수를 1 증가시킨다
  • 2) Board로부터 게시글 정보 조회 
    - 함수를 쓴 칼럼은 반드시 별칭을 지어준다(ResultSet에 칼럼명으로 저장되기 때문에)
    - 게시글번호는 노출되지 않지만 수정 삭제에 쓰이므로 가져온다.
    - 1번이 수행된 상황에서만 수행될것이기때문에 status조건은 넣지 않아도 된다.
  • 3) Attachment로부터 첨부파일 정보 조회
    - 0행이 조회된다면 첨부파일이 없는 것.
--1) 조회수 증가시키기
UPDATE
       BOARD
   SET COUNT = COUNT +1
 WHERE BOARD_NO = 조회할 글번호
   AND STATUS = 'Y'

--2) BOARD로부터 게시글 정보 조회
SELECT
       BOARD_NO
     , CATEGORY_NAME
     , BOARD_TITLE
     , BOARD_CONTENT
     , USER_ID
     , TO_CHAR(CREATE_DATE, 'YYYY/MM/DD') "CREATE_DATE"
  FROM BOARD
  JOIN CATEGORY USING (CATEGORY_NO)
  JOIN MEMBER ON (BOARD_WRITER=USER_NO)
 WHERE BOARD_NO = 조회할 글번호
 
--3) ATTACHMENT로부터 첨부파일 정보 조회
SELECT
       FILE_NO
     , ORIGIN_NAME
     , CHANGE_NAME
     , FILE_PATH
  FROM ATTACHMENT
 WHERE REF_BNO = 조회할 글번호

 

 

STEP3) boardListView.jsp (수정)

  • 게시글을 클릭했을때 detail.bo서블릿 호출
    >>이때, 클릭한 게시글의 첫번째 td태그에 담겨있는 글번호를 쿼리스트링으로 함께 넘긴다.
.....
<% for(Board b : list) { %>
    <tr>
        <td><%=b.getBoardNo() %></td>
        <td><%=b.getCategory() %></td>
    .....
    </tr>
<%} %>
.....
<script>
    $(function(){
        $(".list-area>tbody>tr").click(function(){
            location.href = '<%=contextPath%>/detail.bo?no=' + $(this).children().eq(0).text();
        })
    })
</script>

 

 

STEP4) BoardDetailController.java

  • 생성위치 : com.br.board.controller
    서블릿 / 매핑값 detail.bo
  • 게시글 목록에서 게시글1줄을 선택했을 때 호출되며, 해당 게시글의 상세페이지를 포워딩한다.
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //요청시 전달값 뽑기 (no키값으로 조회할 글번호)
    int boardNo = Integer.parseInt(request.getParameter("no"));
    //서비스 객체 3군데서 필요하니 미리 정의
    BoardService bService = new BoardService();

    // 1) 조회수 증가 요청
    int result = bService.increseCount(boardNo);
    if(result>0) {
        // >> 조회수 증가 성공시 (유효한 글번호)
        // 2-1) Board 테이블로부터 게시글 정보 조회 요청
        Board b= bService.selectBoard(boardNo);
        // 2-2) Attachment 테이블로부터 첨부파일 정보조회 요청(없으면 null로 돌아오게)
        Attachment at = bService.selectAttachment(boardNo);
        // 2-3) 두 객체 담아서 boardDetailView.jsp로 응담
        request.setAttribute("b", b);
        request.setAttribute("at", at); //null이 담길 수 있다.(검사해서 첨부파일유부판단)
        request.getRequestDispatcher("views/board/boardDetailView.jsp").forward(request, response);
    }else {
        // >> 조회수 증가 실패시 (유효하지 않은 글번호)
        //   에러페이지
        request.setAttribute("errorMsg", "상세조회 실패");
        request.getRequestDispatcher("view/common/errorPage.jsp").forward(request, response);
    }
}

 

 

STEP5) BoardService.java (수정)

  • Controller에 쓰인 세가지 메소드를 작성한다
    - increseCount(boardNo)
    - selectBoard(boardNo)
    - selectAttachment(boardNo)
public int increseCount(int boardNo) {
    Connection conn = getConnection();
    int result = new BoardDao().increseCount(conn, boardNo);
    if(result>0) {
        commit(conn);
    }else {
        rollback(conn);
    }
    close(conn);
    return result;
}
	
public Board selectBoard(int boardNo) {
    Connection conn = getConnection();
    Board b = new BoardDao().selectBoard(conn, boardNo);
    close(conn);
    return b;		
}
	
public Attachment selectAttachment(int boardNo) {
    Connection conn = getConnection();
    Attachment at = new BoardDao().selectAttachment(conn, boardNo);
    close(conn);
    return at;
}

 

 

STEP6) BoardDao.java (수정)

  • Service에 쓰인 세가지 메소드를 작성한다.
    - increseCount(conn, boardNo)
    - selectBoard(conn, boardNo)
    - selectAttachment(conn, boardNo)
public int increseCount(Connection conn, int boardNo) {
    PreparedStatement pstmt = null;
    int result = 0;
    String sql = prop.getProperty("increseCount");
    try {
        pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, boardNo);
        result = pstmt.executeUpdate();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close(pstmt);
    }
    return result;
}
	
public Board selectBoard(Connection conn, int boardNo) {
    PreparedStatement pstmt = null;
    ResultSet rset = null;
    Board b = null;
    String sql = prop.getProperty("selectBoard");
    try {
        pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, boardNo);
        rset = pstmt.executeQuery();
        if(rset.next()) {
            b = new Board(rset.getInt("board_no")
                        , rset.getString("category_name")
                        , rset.getString("board_title")
                        , rset.getString("board_content")
                        , rset.getString("user_id")
                        , rset.getString("create_date"));
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close(rset);
        close(pstmt);
    }
    return b;		
}
	
public Attachment selectAttachment(Connection conn, int boardNo) {
    PreparedStatement pstmt = null;
    ResultSet rset = null;
    Attachment at = null;
    String sql = prop.getProperty("selectAttachment");
    try {
        pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, boardNo);
        rset = pstmt.executeQuery();
        if(rset.next()) {
            at = new Attachment(rset.getInt("file_no")
                              , rset.getString("origin_name")
                              , rset.getString("change_name")
                              , rset.getString("file_path"));
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close(rset);
        close(pstmt);
    }
    return at;
}

 

 

STEP7) BoardDetailView.jsp (수정)

  • 넘겨받은 데이터들로 페이지 구성하기
  • 파일은 href에 파일경로를 입력하면 다운받는것이 가능하다.
  • 파일다운로드하기 :
    - href에 파일의 저장위치를 입력한다
    ex) http://localhost:8887/web/resources/board_upfiles/2023011212082884091.jpg
    - <%=contextPath%>  :  /web
    - <%=at.getFilePath() + at.getChangeName()%>  :  resources.board_upfiles/2023011212082884091.jpg
    - 두 출력식 사이에 반드시 ' / ' 를 넣어준다.
    >> 여기까지 해주면 브라우저를 통해서 이미지가 열림
       다운까지 받으려면 a태그 속성에 download 를 넣어준다.
    >>여기까지만 하면 다운받을때는 실제파일명으로 다운로드 받아짐.
      download 속성으로 다운로드받아질 이름을 넣는다.
.....
<%@ page import="com.br.board.model.vo.*" %>
<%
	Board b = (Board)request.getAttribute("b");
	Attachment at = (Attachment)request.getAttribute("at");
%>
.....
카테고리 : <%=b.getCategory()%>
제목 : <%=b.getBoardTitle()%>
작성자 : <%= b.getBoardWriter()%>
작성일 : <%= b.getCreateDate()%>
내용 :  <%=b.getBoardContent()%>
첨부파일 : 
<% if( at==null ){ %>
    첨부파일이 없습니다
<% } else { %>
    <a download="다운받아질 이름.jpg" href="<%=contextPath%>/<%=at.getFilePath() + at.getChangeName()%>"><%= at.getOriginName() %></a>
<% } %>
<a href="<%=contextPath%>/list.bo?cpage=1">목록가기</a>
<% if( loginUser != null && loginUser.getUserId().equals(b.getBoardWriter())) { %>
<!-- 로그인한 회원이 게시글을 쓴 회원일경우에만 보이는 버튼 -->
<a href="">수정하기</a>
<a href="">삭제하기</a>
<% } %>

 


수정

첨부파일이 있는 경우는 수정하는 경우가 다소 까다롭다


STEP1) boardDetailView.jsp (수정)

  • 수정하기 버튼 클릭시 updateForm.bo로 요청
    >> 수정할때 가져가야 할 데이터는 게시글 번호(쿼리스트링으로 전달)
<a href="<%=contextPath%>/updateForm.bo?no=<%=b.getBoardNo()%>">수정하기</a>

 

 

STEP2) BoardUpdateFormController.java

  • 생성위치 : com.br.board.controller
    서블릿 / 매핑값 updateForm.bo
  • 수정하기 눌렀을 때, 필요한 정보들을 확보하여 수정페이지로 넘기는 서블릿
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    int boardNo = Integer.parseInt(request.getParameter("no"));
    BoardService bService = new BoardService();
		
    ArrayList<Category> list = bService.selectCategoryList();
    Board b = bService.selectBoard(boardNo);
    Attachment at = bService.selectAttachment(boardNo); // null가능
		
    request.setAttribute("list", list);
    request.setAttribute("b", b);
    request.setAttribute("at", at);
		
    request.getRequestDispatcher("view/board/boardUpdateForm.jsp").forward(request, response);
}

 

 

STEP3) boardUpdateForm.jsp

  • 생성위치 : webProject/src/main/webapp/views/board
  • 수정하기 버튼을 눌렀을 때 보여지는 화면설계
    >> 작성하기 페이지 골격 + 기존의 데이터 뿌려주기
  • 수정주요내역
    >> 카테고리 option들 중 기존데이터값에 맞는 곳에 selected속성주기(jQuery사용)
    >> 제목입력 태그에 기존값을 value로 넣기
    >> 내용입력 태그 content부에 기존값 넣기
    >> input type=file은 value값 둘 수 없으므로 버튼앞에 기존 첨부파일의 원본명 표기되게 하기 
       (다운로드 기능 원하면 a태그 붙이면 됨)
<%@ page import="com.br.board.model.vo.*, java.util.ArrayList" %>
<%
    ArrayList<Category> list = (ArrayList<Category>)request.getAttribute("list");
    Board b = (Board)request.getAttribute("b");
    Attachment at = (Attachment)request.getAttribute("at");
%>
.....
<select name="category">
    <% for(Category c : list) { %>
        <option value="<%=c.getCategoryNo()%>"><%= c.getCategoryName() %></option>
    <% } %>
</select>
<script>
    $(function(){
        $("#update-form option").each(function(){
            if($(this).text()== '<%=b.getCategory()%>') {
                $(this).attr("selected", true)
            }
        })
    })
</script>
.....

 

 

STEP4) board-mapper.xml (수정)

  • 세가지 sql문을 작성해야한다.
    1. 게시글 정보를 업데이트 하는 sql문
    2. 새로운 첨부파일이 있는데 기존 첨부파일이 있을 경우
      Attachment테이블을 update하는 sql문
      이때, primary key로 지정된 컬럼으로 비교하는 것이 검색속도가 빠르다고 한다.
    3.  새로운 첨부파일이 있는데 기존첨부파일이 없는 경우
      Attachment테이블에 insert하는 sql문
      기존에 썼던 insert문은 참조하는 게시글번호를 시퀀스.currval로 썼기때문에 재새용이 불가하다
-- 수정하기 요청시 실행할 sql문
--1) 게시글 정보 update (Board)
UPDATE 
       BOARD
   SET CATEGORY_NO = 사용자가선택한 카테고리번호
     , BOARD_TITLE = 사용자가 입력한 제목
     , BOARD_CONTENT = 사용자가 입력한 내용
 WHERE BOARD_NO = 수정하고자하는 게시글번호;
--2_1) 새로운첨부파일O, 기존첨부파일O
UPDATE
       ATTACHMENT
   SET ORIGIN_NAME = 새로운 첨부파일의 원본명
     , CHANGE_NAME = 새로운 첨부파일의 저장명
     , FILE_PATH = 새로운 첨부파일의 저장경로
 WHERE FILE_NO = 기존의 첨부파일의 고유번호;
--2_2) 새로운첨부파일X, 기존첨부파일X
INSERT
  INTO ATTACHMENT
  (
    FILE_NO
  , REF_BNO
  , ORIGIN_NAME
  , CHANGE_NAME
  , FILE_PATH
  )
  VALUES
  (
    SEQ_FNO.NEXTVAL
  , 현재수정하는 게시글번호
  , 새로운 첨부파일의 원본명
  , 새로운 첨부파일의 저장명
  , 새로운 첨부파일의 저장경로
  )

 

 

STEP5) boardUpdateForm.jsp (수정)

  • sql문 확인시 게시글 번호가 필요하다는것 확인
    >> 폼태그내에 input type=hidden으로 숨겨서 넘기기
  • 마찬가지로 기존에 첨부파일 있을경우 원래명칭 필요함 확인
    >> input type=hidden으로 숨겨서 넘기
  • 폼을 제출할 때 update.bo 서블릿을 호출하도록 action값을 입력한다.
<form id="update-form" action="<%=contextPath%>/update.bo" method="post" enctype="multipart/form-data">
<input type="hidden" name="no" value="<%=b.getBoardNo()%>">
.....
첨부파일
<% if(at != null ) { %>
<!-- 기존의 첨부파일이 있었을 경우 -->
<input type="hidden" name="originFileNo" value="<%=at.getFileNo()%>">
<%=at.getOriginName()%>
<% } %>
.....

 

STEP6) BoardUpdateController.java

  • 생성위치 : com.br.board.controller
    서블릿 / 매핑값 update.bo
  • 게시글수정요청이 들어왔을때  db에 요청내용을 반영한 후 게시글 목록페이지로 포워딩하는 서블릿
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("UTF-8");
    if(ServletFileUpload.isMultipartContent(request)) {
        //1-1) 파일의 용량제한
        int maxSize = 10*1024*1024;
        //1-2) 물리적인 파일저장위치
        //     session객체로부터 getServletContext로 application 객체를 얻어낸다음 getRealPath 사용
        String savePath = request.getSession().getServletContext().getRealPath("/resources/board_upfiles/");
        //2) 전달된 파일 서버에 업로드(한줄로 완료)
        //    request에 담긴것도 multiReqest로 옮긴다.
        MultipartRequest multiRequest = new MultipartRequest(request, savePath, maxSize, "UTF-8", new MyFileRenamePolicy());
        //3) db에 데이터기록
        //   첨부파일이 있든없든 수행하는것 : Board 테이블 업데이트
        int boardNo = Integer.parseInt(multiRequest.getParameter("no"));
        String category = multiRequest.getParameter("category");
        String boardTitle = multiRequest.getParameter("title");
        String boardContent = multiRequest.getParameter("content");
        Board b = new Board();
        b.setBoardNo(boardNo);
        b.setCategory(category);
        b.setBoardTitle(boardTitle);
        b.setBoardContent(boardContent);
			
        // 새로 넘어온 첨부파일 있는지 여부 확인할때 : at의 null여부
        Attachment at = null;
        if(multiRequest.getOriginalFileName("upfile") != null) { 
            //새로운 첨부파일이 있을경우 객체생성
            at = new Attachment();
            //기존 첨부파일이 있건없건 필요한 데이터들 = 새로운 첨부파일 원본명, 저장명, 저장경로
            at.setOriginName(multiRequest.getOriginalFileName("upfile"));
            at.setChangeName(multiRequest.getFilesystemName("upfile"));
            at.setFilePath("resources/board_upfiles/");

            //숨겨서 넘긴 정보를 활용해서 기존파일여부를 확인한다.
            if(multiRequest.getParameter("originFileNo") != null) {
                // 기존의 첨부파일이 있었을 경우 => UPDATE ATTACHMENT (기존파일번호필요)
                at.setFileNo(Integer.parseInt(multiRequest.getParameter("originFileNo")));
            }else {
                // 기존의 첨부파일이 없었을 경우 => INSERT ATTACHMENT (현재게시글번호필요)
                at.setRefBoardNo(boardNo);
            }
        }
        int result = new BoardService().updateBoard(b,at);
        if(result>0) {
            //성공
            request.getSession().setAttribute("alertMsg", "성공적으로 수정되었습니다.");
            response.sendRedirect(request.getContextPath()+"/detail.bo?no="+boardNo);
        }else {
            //실패 => 에러페이지
        }
    }
}

 

 

STEP7) BoardService.java (수정)

  • updateBoard(b,at) 메소드 생성
  • at에 담긴 정보에 따라 세가지 경우를 구분할 수 있음. 
    1) new첨부 X == at는 null 
    2) new첨부 O, old첨부 O  == fileNo에 기존파일정보있음
    3) new첨부 O, old첨부 X  == refBoardNo에 정보 있음
  • Board업데이트를 진행한 후 결과를 result1에 담고, Attachment엡데이트를 진행한 결과를 result2에 담은 후 두 결과의 곱을 리턴한다(두 결과 모두 성공일때만 result>0이다.)
public int updateBoard(Board b,Attachment at) {
    Connection conn = getConnection;
    int result1 = new BoardDao().updateBoard(conn, b);

    int result2 = 1;
    //0으로 초기화해놓으면 첨부파일이 없는 경우에는 그대로 0이므로 1로 초기화
    if(at != null) { //새로운 첨부파일이 있으면서,
        if(at.getFileNo() != 0) { 
        // 기존첨부파일도 있는 경우
        // (fileNo 타입은 숫자이므로 null이 아닌 기본값 0으로 비교)
        // => UPDATE ATTACHMENT
        result2 = new BoardDao().updateAttachment(conn, at);
        }else { 
            // 기존 첨부파일은 없는경우
            // ==> INSERT ATTACHMENT
            result2 = new BoardDao().insertNewAttachment(conn, at);
        }
    }
    if(result1>0 && result2>0) {
        commit(conn);
    }else {
        rollback(conn);
    }
    close(conn);
    return result1*result2;
}

 

 

STEP8) BoardDao.java (수정)

  • updateBoard(conn, b) 메소드와 첨부파일이 있는경우 수행될
    updateAttachment(conn, at) 메소드와 
    insertNewAttachment(conn, at) 메소드를 작성한다.
public int updateBoard(Connection conn, Board b) {
    PreparedStatement pstmt = null;
    String sql = prop.getProperty("updateBoard");
    int result = 0;
    try {
        pstmt=conn.prepareStatement(sql);
        pstmt.setString(1, b.getCategory());
        .....
        result = pstmt.executeUpdate();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close(pstmt);
    }
    return result;
}
public int updateAttachment(Connection conn, Attachment at) {
    int result = 0;
    PreparedStatement pstmt = null;
    String sql = prop.getProperty("updateAttachment");
    try {
        pstmt = conn.prepareStatement(sql);
        pstmt.setString(1, at.getOriginName());
        .....
        result = pstmt.executeUpdate();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close(pstmt);
    }
    return result;
}
public int insertNewAttachment(Connection conn, Attachment at) {
    int result = 0;
    PreparedStatement pstmt = null;
    String sql = prop.getProperty("insertNewAttachment");
    try {
        pstmt = conn.prepareStatement(sql);
        .....
        result = pstmt.executeUpdate();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close(pstmt);
    }
    return result;		
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

댓글