📡 JSP로 간단한 CRUD 사이트를 MVC 패턴으로 구축해보기 part 2

August 13, 2024


안녕하세요? 이번엔 저번 글을 이어서.. 서블릿과 WAS에 대해서 알아보고 Controller 폴더에 들어갈 코드들을 마저 작성해보도록 하겠습니다. 이번 글은 코딩과 동시에 실시간으로 작성했기 땜시.. 의식의 흐름이 될 것 같습니다.

Controller 🔧 컨트롤러 작성하기 (1)

먼저 컨트롤러 기본 베이스 코드를 작성해보겠습니다.

인터페이스를 먼저 작성해줍시다!

public interface WebtoonController {
    void process(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException;
}

짠.. 이제 WebtoonController하나만 있으면 Controller 무한 확장 가능입니다 ,, 처음 다형성을 접했을때는 뭐라고,,? (동태눈알..) 이었는데 쓰다보니 이보다 섹시한 문법 또 없는 것 같습니다 ㅎㅋ 아무튼!

public class ListController implements WebtoonController{
    @Override
    public void process(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        System.out.println("연결 되었습니다.");
    }
}

다음으로 웹툰 리스트를 보여줄 ListController를 작성했습니다. 아직 구현한 기능은 없구요.. 화면에 출력만 해줄 거라서 콘솔창만 찍어봅시다!

아 참 그 전에.. HttpServletRequest, HttpServletResponse가 뭐길래 Controller들에 전달해주고 있는지 한 번 알아봅시다.

HttpServletRequest, Response 란?

흐름은 위의 사진과 같습니다. 즉 브라우저가 무언가 요청을 보내면 WAS에서 HttpServletRequest, HttpServletResponse 객체를 만들고 Servlet에 파라미터로 전달을 합니다. 그럼 Servlet에서 필요한 메소드를 override 해 사용할 수 있게 됩니다.

궁금하면 오백원.. 은 아니고요 이 링크에 들어가서 찍어보고 싶은 메소드를 찍어보시면 좀 더 와닿을 것 같습니다.

이 메소드를 찍어보시면 URI가 출력이 됩니다.

Controller 🔧 servlet 작성하기

대망의 서블릿 작성입니다! 서블릿을 이용해 url과 controller을 mapping을 해줄건데요.. HttpServlet을 상속받아 만들것입니다.

HttpServlet 클래스는 HTTP 통신과 관련된 데이터를 처리할 수 있습니다. 이곳에서 관련 자료를 찾아볼 수 있는데요! 메소드 중 하나를 필수적으로 오버라이딩을 해야한다고 합니다. 일반적으로 service 메소드는 개발자가 재정의 해줄 필요는 없다곤 하지만.. 메인 서블릿에서는 http 요청을 바로 처리할 것이 아니기 때문에 service 메소드를 이용해서 컨트롤러와 uri를 매핑해주도록 해봅시다!

@WebServlet("/webtoons/*")
public class WebtoonsServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        Map<String, WebtoonController> mapController = new HashMap<>();
        mapController.put("/webtoons/list", new ListController());

        String reqURI = req.getRequestURI();
        WebtoonController controller = mapController.get(reqURI);

        if (controller == null) {
            System.out.println("아아 오류입니다");
            return;
        }

        controller.process(req, res);
    }
}

이렇게 해두면 사용자가 입력하는 것에 따라 webtoons에 따라서 들어온거면 그에 맞춰서 controller가 연결이 됩니다. 만약 컨트롤러가 존재하지 않는다면 콘솔창에 오류라고 뜨게 됩니다.

이제 아까 만들어준 ListController가 제대로 불러와지는지 확인하기 위해 "/webtoons/list"를 입력해보면 화면도 이동이 되고 서버 로그에도 연결 되었습니다 가 뜨게 됩니다. 얏호!

Controller 🔧 컨트롤러 작성하기 (2)

연결이 잘 된 것을 확인했으니 컨트롤러를 마저 작성해봅시다! 앞서 써둔 코드에서도 알 수 있듯 Controller에서는 HttpServletRequest, HttpServletResponse를 받아서 처리를 하도록 되어있는데요.. ListController는 화면에 전체를 출력하는 것입니다.

근데 출력이고 나발이고~ 먼저 화면에 연결될 jsp가 뭔지 알려주면 화면에도 뜨고.. 좀 더 코딩할 맛이 날 것 같습니다! 그래서 저희는 아래의 메소드를 사용해 주도록 하겠습니다.

이 메소드는 resource가 있는 path를 넘겨주면 이걸 감싸주는 역할을 하는 RequestDispatcher를 반환한다고 합니다. RequestDispatcher에서 보면 알 수 있듯, 브라우저가 보낸 요청을 다른 서블릿, jsp 등으로 포워딩이 가능하다고 합니다.

String view = "/WEB-INF/views/WebtoonList.jsp";  
req.getRequestDispatcher(view).forward(req, res);

그래서 이런씩으로 포워딩을 해주면

요로코롬..path에 있는 jsp가 화면에 잘 뜨는 것을 확인할 수 있습니다! 얏호 ..

그럼 이제 다시 화면에 웹툰 전체가 다 뜨게 하면 만들어 봅시다.

        try {
                List<Webtoon> list = webtoonService.getWebtoonList();
                req.setAttribute("webtoonList", list);
        } catch (Exception e) {
            e.printStackTrace();
            req.setAttribute("error", e.getMessage());
        }

이렇게.. 해주면 attribute를 만들어서 httpServletRequest에 설정을 할 수 있게 됩니다. 이제 jsp를 작성해보도록 하겠습니다!

Views 🔧 JSP 작성하기 (1)

attribute를 보내주면 EL(expression language)가 알아서 가져와주고요.. jstl 을 사용해서 간단하게 리스트를 사용해보도록 하겠습니다!

<%@ page isELIgnored="false" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
    <title>Webtoons</title>
</head>
<body>
  <h1>웹툰 리스트입니다!</h1>
  <ul>
      <c:forEach var="webtoon" items="${webtoonList}" varStatus="status">
          <li>
              <div>
                  <h2>${webtoon.getTitle()}</h2>
                  <span>작가: ${webtoon.getAuthor()} / ${webtoon.getPlatform()}</span>
                  <br/>
                  <span>${webtoon.getSummary()}</span>
              </div>
          </li>
      </c:forEach>
  </ul>
</body>
</html>

이렇게 해봤는데요!

이렇게 뜨게 됩니다 ㅎㅎ 어떤가요.. 신나지 않으신가요.. 그러면 이제 저희는 CRUD에서 Read를 완성하게 됐습니다. 그러면 이제.. 나머지 cud도 해보도록 하겠습니다. 조금 귀찮은데요.. 쩝.. 해야하나? 해야죠.. 3해봅시다..

📸 이미지 저장하기

그 전에! 해결해야 할 것이 하나 있습니다.. 바로 이미지를 저장하는 건데요! 간단한 프로젝트인 만큼 로컬에 저장을 해보도록 하겠습니다. 먼저 form을 만들고 어떤씩으로 파일 업로드가 들어오는지 확인해보겠습니다.

    <form
        action="/webtoons/list?action=addWebtoon"
        method="post"
        enctype="multipart/form-data"
    >
        <input id="title" name="title" placeholder="title" required />
        <input id="author" name="author" placeholder="author" required />
        <input id="platform" name="platform" placeholder="platform" required />
        <textarea id="summary" name="summary" placeholder="summary" required></textarea>
        <input id="img" name="img" type="file" required/>
        <button type="submit">저장하기</button>
    </form>

이렇게 폼 태그를 만들어주었습니다. post 방식으로 보내는 걸로 했구요.. text랑 file을 둘 다 보내야해서 Multipart/form-data로 설정해주었습니다!

이제 이걸 프린트 해봅시다.

    String action = req.getParameter("action");
        if (action != null && action.equals("addWebtoon")) {
            String title = req.getParameter("title");
            String author = req.getParameter("author");
            String platform = req.getParameter("platform");
            String summary = req.getParameter("summary");
            System.out.println("제목 : " + title);
            System.out.println("작가: " + author);
            System.out.println("플랫폼: " + platform);
            System.out.println("줄거리: " + summary);

            Part part = req.getPart("img");
            String header = part.getHeader("content-disposition");
            System.out.println(header);
        }

Partmultipart/form-data를 post로 가져왔을 때 form item을 나타냅니다. 아무튼 이렇게 해서 가져와보면 .. null이 뜹니다! 그 이유는 MultipartConfig를 안 설정해주었기 때문입니다.

다시 Servlet 파일로 돌아가서 @MultipartConfig(location = "{파일을 저장하고 싶은 path}") 이 어노테이션을 추가해주도록 합니다. 여기에 나와있듯! 이 어노테이션을 서블릿에 추가해주면 multipart/form-data 형식으로 들어온 part를 가져올 수 있다고 합니다. 그리고 다양한 옵션을 줄 수 있는데요! 저는 다른 건 딱히 필요 없어서.. Location만 추가해주었습니다.

아무튼 이제 다시 실행해보면

이렇게 뜹니다! 그럼 저희는 이제.. filename을 가져와서 어째저째 처리를 해주면 될 것 같은데요!

이 메소드를 사용하도록 하겠습니다. fileName을 넘겨주면 MultipartConfig에 준 location에 파일이 저장이 된다고 합니다.

    Part part = req.getPart("img");
    String header = part.getHeader("content-disposition");
    int index = header.indexOf("filename=");
    String filename = header.substring(index + 10, header.length() - 1);
    part.write(filename);

이렇게 하고 저장을 하면!

이렇게.. 사진 파일이 저장이 되게 됩니다.

신기하네요... 저만 그런가요? ㅋㅎ 아무튼 저번에 작성하다가 만 Service 파일을 마저 작성해보도록 하겠습니다.

addWebtoon

    @Override
    public void addWebtoon(HttpServletRequest req) throws Exception {
        String title = req.getParameter("title");
        String author = req.getParameter("author");
        String platform = req.getParameter("platform");
        String summary = req.getParameter("summary");

        Part part = req.getPart("img");
        String header = part.getHeader("content-disposition");
        int start = header.indexOf("filename=");
        String filename = header.substring(start + 10, header.length()-1);

        if (filename != null && !filename.isEmpty()) {
            part.write(filename);
        }

        Webtoon webtoon = new Webtoon(title, author, platform, filename, summary);
        webtoonDAO.addWebtoon(webtoon);
    }

updateWebtoon

    @Override
    public void updateWebtoon(HttpServletRequest req) throws Exception {
        int id = Integer.parseInt(req.getParameter("id"));

        Webtoon webtoon = webtoonDAO.getById(id);

        webtoon.setTitle(req.getParameter("title"));
        webtoon.setAuthor(req.getParameter("author"));
        webtoon.setPlatform(req.getParameter("platform"));
        webtoon.setSummary(req.getParameter("summary"));

        Part part = req.getPart("img");
        String header = part.getHeader("content-disposition");
        int start = header.indexOf("filename=");
        String filename = header.substring(start + 10, header.length()-1);

        if (filename != null && !filename.isEmpty()) {
            part.write(filename);
            webtoon.setCover_img(filename);
        }

        webtoonDAO.updateWebtoon(webtoon);
    }

이렇게 해주면 됩니다.. 그런데 여기서 또 문제가.. add가 끝나면 다시 list로 돌아와주었으면 좋겠습니다.

if (action != null && action.equals("addWebtoon")) {
    webtoonService.addWebtoon(req);
    res.sendRedirect(req.getRequestURI());
    return;
}

짠.. 그러면 이제 추가를 한 후에 다시 list로 돌아와 추가된 친구를 확인할 수 있습니다!

근데 너무 허전하지 않나요? 리스트에 이미지도 볼 수 있게 해보겠습니다.

<img src="/{사진 파일 경로}/${webtoon.getCover_img()}" alt="커버 이미지" style="width: 300px; height: auto;"  />

그냥.. 별 거 없습니다! Jsp에 경로를 찾아서 넣어주면 됩니다.

완성.. 됐습니다! 드디어 Create가 완성이 되었습니다. 이제 Update, delete를 해봅시다... 흠흠..

려고 했는데요 ! 햄들어서.. ud는 다음 포스트에서 찾아뵙도록 하겠습니다!


참고 링크

profile picture
@다홍
공부한 것들을 조금씩 정리합니다.