Servlet/JSP로 사용자 관리 시스템 구현하기 과정
강의 링크 : https://www.slipp.net/wiki/pages/viewpage.action?pageId=25526852
※ 개인정보수정 폼 구현하기
아직 화면 상에서 구현되지 않은 것이 하나 있는데요. 바로 '개인정보수정'이죠?
이 부분을 만들어보도록 하겠습니다.
기본적인 틀은 form.jsp와 비슷하기 때문에 복사해서 가져오고, 달라지는 부분만 수정하는 식으로 만들겠습니다.
회원가입에 해당하는 form.jsp는 모두 빈 칸으로 구성되어 있지만, 개인정보수정에 해당하는 update_form.jsp는 현재 로그인되어있는 사용자의 데이터가 채워진 상태로 나와야합니다. 아이디를 제외한 나머지만 수정가능하도록 만들어야겠죠?
데이터를 가져오기 위해서는 아래처럼 jsp에 작성할 수 있습니다.
<%@
String userId = (String)session.getAttribute("userId");
UserDAO userDAO = new UserDAO();
User user = userDAO.findByUserId(userId);
%>
하지만 우리는 jsp에 자바 코드를 최대한 작성하지 않도록 진행 중입니다. jsp가 부담을 덜 갖게 만들기 위해 mvc패턴으로 서블릿을 생성하고 있었죠? 따라서 위처럼 자바코드를 jsp안에 사용하지 않고 데이터를 받아올 수 있도록 만들어보겠습니다.
사용자 유저 데이터를 받아오는 코드를, jsp에서 작성하지 않고 따로 서블릿으로 생성한 후 가져와 뿌려주도록 합니다.
현재 로그인이 되어있는 유저의 데이터를 가져오기 위한 서블릿을 생성해줍시다. 우선 해당 서블릿의 전체 코드는 아래와 같습니다.
UpdateFormUserServlet.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | package net.slipp.user; import java.io.IOException; import java.sql.SQLException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; @WebServlet("/users/updateForm") public class UpdateFormUserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); Object object = session.getAttribute(LoginServlet.SESSION_USER_ID); if(object == null){ resp.sendRedirect("/"); return; } String userId = (String)object; System.out.println("User Id : " + userId); UserDAO userDAO = new UserDAO(); try { User user = userDAO.findByUserId(userId); req.setAttribute("user", user); RequestDispatcher rd = req.getRequestDispatcher("/update_form.jsp"); rd.forward(req, resp); } catch (SQLException e) { } } } | cs |
현재 /users/updateForm으로 WebServlet 어노테이션한 것을 볼 수 있는데요. 우리는 '개인정보수정'이라는 버튼을 클릭했을 때 이 서블릿으로 이동해야 하므로_top.jspf에서 똑같은 경로를 설정해주도록 합시다.
<li><a href="/users/updateForm">개인정보수정</a></li>
위 서블릿 코드에서는 doGet 메소드를 통해서 session을 만들고, object에 현재 로그인 된 아이디를 저장한 후 userId라는 변수에 저장하는 모습을 볼 수 있습니다.
try문에서 findByUserId 메소드를 통해 현재 userId에 대한 유저 데이터가 있는지 확인한 후 해당 유저의 정보를 담고, user라는 이름으로 사용가능하도록 만든 상태입니다.
마지막으로 forward를 통해 update_form.jsp로 페이지 이동을 처리하는 모습을 볼 수 있죠? 이전에 redirect와 forward의 차이점에서 설명했듯이, 이처럼 유저 데이터에 대한 정보를 가지고 페이지 이동을 하기 위해서 forward 방식을 사용하는 것을 볼 수 있습니다.
즉 다시 한번 정리하면, 홈페이지 상에서 사용자가 로그인 후에 '개인정보수정' 버튼을 누르면, /users/updateForm 경로에 해당하는 UpdateFormUserServlet이 실행되고, 사용자의 데이터를 가진 채 forward를 통해 update_form.jsp로 이동하는 전개입니다.
forward를 통해 페이지 이동한 개인정보수정에 해당하는 update_form.jsp는 아래와 같습니다.
update_form.jsp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <%@include file="../commons/_head.jspf"%> </head> <body> <%@include file="../commons/_top.jspf"%> <div class="container"> <div class="row"> <div class="span12"> <section id="typography"> <div class="page-header"> <h1>개인정보수정</h1> </div> <form class="form-horizontal" action="/users/update" method="post"> <input type="hidden" name="userId" value="${user.userId}"/> <div class="control-group"> <label class="control-label" for="userId">사용자 아이디</label> <div class="controls"> ${user.userId} </div> </div> <div class="control-group"> <label class="control-label" for="password">비밀번호</label> <div class="controls"> <input type="password" id="password" name="password" value="${user.password}" placeholder=""> </div> </div> <div class="control-group"> <label class="control-label" for="name">이름</label> <div class="controls"> <input type="text" id="name" name="name" value="${user.name}" placeholder=""> </div> </div> <div class="control-group"> <label class="control-label" for="email">이메일</label> <div class="controls"> <input type="text" id="email" name="email" value="${user.email}" placeholder=""> </div> </div> <div class="control-group"> <div class="controls"> <button type="submit" class="btn btn-primary">수정완료</button> </div> </div> </form> </div> </div> </div> </body> </html> | cs |
기본적은 틀은 form.jsp와 같습니다. 회원가입에 필요한 정보들을 수정하는 곳이니까 형태는 유지되겠죠?
대신 input형으로 만든 text가 현재 로그인 되어있는 유저의 정보로 채워진 상태로 제공되어야 합니다. 채워진 정보를 사용자가 지우고 변경하고 싶은 정보로 수정하는 형태가 되겠죠. 단, 유저의 아이디는 수정이 불가능하기 때문에 text형 박스로 나타내는 것이 아니라, 수정할 수 없도록 그냥 아이디를 출력한 모습으로 나타내야 합니다.
바로 이런 모습으로 나타나야겠죠?
기존의 form.jsp에서 패스워드, 이름, 이메일의 value 값을 {user.~~}로 채울 수 있는 걸 확인할 수 있습니다.
user는 현재 로그인되어 있는 사용자의 정보를 가져오고 있는데 어디서 가져오는 걸까요?
${user.name}에서 user는 UpdateFormUserServlet의 req.setAttribute("user", user);에서 가져오는 것입니다.
하지만 이때, name 값은 User 클래스에서 가져오는데, 단순히 필드에 있는 String 변수 name을 가져오는 것이 아닙니다. 현재 User 클래스에서 name은 private로 지정되어 있기 때문에 바로 가져오는 것은 불가능하겠죠. 따라서 getter로 만들어 놓은 getName 메소드를 통해 가져오게 됩니다.
User.java
private String name; (X)
public String getName() {
return name;
} (O)
근데 위에 말대로라면 실제 jsp 상에서 ${user.getName()}이 되어야할텐데 그냥 ${user.name}이라고 작성해서 가져오고 있는데요. 이유가 뭘까요..?
이는 javabin 규약을 따른 것으로, get은 지워지고 N을 소문자로 바꿔서 가져오게 됩니다. 따라서 getName 메소드를 통해 name이라는 이름으로 꺼내오는 것이 가능해지는 것을 확인할 수 있습니다. name을 포함해, email이나 password도 모두 마찬가지입니다.
이 3가지 데이터를 제외한 유저 아이디는 개인정보수정으로도 변경할 수 없도록 만들어야 합니다.
단순히 텍스트 타입의 input으로 만드는 것이 아니라 그냥 출력문으로 나오도록 ${user.userId}만 작성해주면 되겠습니다.
이렇게 되면, 개인정보 수정 폼이 action을 통해 보내질 때 유저 아이디에 대한 데이터는 넘어갈 수 없습니다. 따라서 input 기능에 해당하는 hidden을 사용하도록 합시다.
<input type="hidden" name="userId" value="${user.userId}"/>
hidden을 사용하면, 실제로 눈으로 보이지는 않지만 데이터를 전달하는 것이 가능해집니다. 따라서 유저 아이디를 input text형태로 나타내지 않고서도 따로 hidden을 만들어 데이터를 전송할 수 있습니다.
이제 한번더 위로 올라가서 update_form.jsp 전체 코드를 살펴본다면, 어떤식으로 작성한건지 이해할 수 있을겁니다!
지금까지 개인정보를 수정하는 jsp 폼을 만들어봤습니다.
이처럼 개인정보를 수정하는 과정의 비즈니스 로직에 해당하는 부분은 서블릿으로 빼서 작성하고, 사용자에게 보여주는 부분은 jsp를 통해 보여주고 있습니다. 바로 MVC패턴을 나타내고 있죠?
즉, UpdateFormUserServlet.java에서 사용자의 데이터를 조회하고 최초의 사용자 요청을 받는 컨트롤러 역할을 하고 있습니다. 이 서블릿은 데이터베이스와 세션 상태 파악, 모든 처리가 끝난 후 jsp로 데이터 전달까지 해주는 역할을 하게 됩니다. 이 데이터를 받은 update_form.jsp는 단순히 컨트롤러인 서블릿이 보내준 유저 데이터를 뿌리는 역할만 합니다. 즉 view에 해당하는 역할을 이 jsp에서 담당하고 있습니다.
그렇다면 모델은 어디에 해당할까요? 모델은 프로젝트의 '핵심 로직을 처리하고 담당'하는 부분입니다.
UserDAO userDAO = new UserDAO();
try {
User user = userDAO.findByUserId(userId);
현재 update_form.jsp가 action으로 데이터가 넘어가는 경로는 /users/update입니다. 이 맵핑에 맞는 서블릿을 작성해서 수정된 정보를 저장할 수 있도록 만들어야겠죠? 이는 아래와 같습니다.
UpdateUserServlet.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | package net.slipp.user; import java.io.IOException; import java.sql.SQLException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/users/update") public class UpdateUserServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); // 한글 깨지지 않도록 String userId = request.getParameter("userId"); String password = request.getParameter("password"); String name = request.getParameter("name"); String email = request.getParameter("email"); User user = new User(userId, password, name, email); UserDAO userDAO = new UserDAO(); try { userDAO.updateUser(user); } catch (SQLException e) { e.printStackTrace(); } response.sendRedirect("/"); } } | cs |
우리가 유저 데이터를 저장했던 SaveUserServlet처럼 아이디, 비밀번호, 이름, 이메일 데이터를 가져와 user로 생성해주는 과정을 진행하면 됩니다. 대신 이 곳에서는 addUser 메소드가 아닌, updateUser 메소드를 새로 만들어 실행하도록 합시다. 왜냐하면, 기존의 데이터를 수정하는 것이기 때문에, 새로운 데이터를 만드는 것이 아닌 기존의 정보를 바꿔주는 메소드가 필요하기 때문입니다.
UserDAO.java의 updateUser 메소드 부분
1 2 3 4 5 6 7 8 9 10 11 12 | public void updateUser(User user) throws SQLException { String sql = "update USERS set password = ?, name = ?, email = ? where userId = ?"; PreparedStatement pstmt = getConnection().prepareStatement(sql); // 쿼리를 전달한 후, 정상적인 쿼리문인지 검증하는 단계 pstmt.setString(1, user.getPassword()); pstmt.setString(2, user.getName()); pstmt.setString(3, user.getEmail()); pstmt.setString(4, user.getUserId()); pstmt.executeUpdate(); // 쿼리 실행 } | cs |
이처럼 sql 문을 활용해 update를 진행합니다. 이때 userId에 따라 update가 진행되므로, sql문에서 마지막에 where절에 userId를 작성하게 되는데요. 따라서 쿼리문 검증단계의 순서도 sql문의 '?' 위치에 맞게 위처럼 수정해주도록 합시다. (비밀번호-이름-이메일-아이디 순서)
이제 서버를 재실행하고, 개인정보수정을 진행해보면 제대로 작동하는 것을 확인할 수 있습니다!
※ UserDAO 자원 반환
우리가 userDAO를 구현하는 과정에서 한가지 문제점이 존재합니다. 데이터베이스의 connection을 얻거나, preparedStatement를 생성하고 resultset을 생성하는 부분에서 자원을 얻어오는 과정을 거치고 있는데요. 이를 사용한 이후에는 반환 과정을 거쳐여만 합니다.
하지만 우리는 여태까지 자원을 반환하는 코드를 하나도 작성하지 않았습니다. 만약 이대로 웹페이지를 구현한다면, 자원을 반환하지 못해 서버가 다운되는 큰 문제가 발생할 수 있으니 userDAO에 자원을 반환하는 과정이 필요합니다.
connection과 preparedStatement 등 자원을 가져오는 기능을 사용할 때는 코드 마지막에 close를 통해서 반드시 반환을 해줘야 합니다.
이 부분으로 인해서 실제 서버가 다운되는 문제가 실제로도 가장 큰 원인 중 하나이기도 하기 때문이죠.
따라서 우리가 웹페이지를 만들 때 이런 문제점이 발생하지 않도록 close로 꼭 자원을 반환하는 습관을 들이도록 합시다.
UserDAO.java의 addUser 메소드 부분
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public void addUser(User user) throws SQLException { String sql = "insert into USERS values(?,?,?,?)"; Connection conn = null; PreparedStatement pstmt = null; try{ conn = getConnection(); pstmt = conn.prepareStatement(sql); // 쿼리를 전달한 후, 정상적인 쿼리문인지 검증하는 단계 pstmt.setString(1, user.getUserId()); pstmt.setString(2, user.getPassword()); pstmt.setString(3, user.getName()); pstmt.setString(4, user.getEmail()); pstmt.executeUpdate(); // 쿼리 실행 } finally { if (pstmt != null){ pstmt.close(); } if (conn != null){ conn.close(); //자원 반환 } } } | cs |
addUser 메소드를 자원 반환이 가능하도록 수정한 코드입니다.
기존에 존재하는 코드를 try에 넣고, 자원을 반환하는 코드는 finally문에 넣었습니다.
try-catch문에서 finally의 역할은, 메소드가 끝날 때 반드시 실행시켜주는 코드를 말합니다. 따라서 자원 반환에 대한 코드는 이 곳에 작성하면 매우 ㅎ율적일 것입니다.
이 형식에 맞게, findByUserId 메소드 등 다른 userDAO의 메소드도 자원을 반환할 수 있도록 수정해보도록 합시다.
수정이 완료된 전체 코드는 아래와 같습니다.
UserDAO.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | package net.slipp.user; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.DriverManager; public class UserDAO { public Connection getConnection() { String url = "jdbc:mysql://localhost:3306/cadi_dev"; String id = "root"; String pw = ""; try{ Class.forName("com.mysql.jdbc.Driver"); return DriverManager.getConnection(url,id,pw); } catch (Exception e) { System.out.println(e.getMessage()); return null; } } public void addUser(User user) throws SQLException { String sql = "insert into USERS values(?,?,?,?)"; Connection conn = null; PreparedStatement pstmt = null; try{ conn = getConnection(); pstmt = conn.prepareStatement(sql); // 쿼리를 전달한 후, 정상적인 쿼리문인지 검증하는 단계 pstmt.setString(1, user.getUserId()); pstmt.setString(2, user.getPassword()); pstmt.setString(3, user.getName()); pstmt.setString(4, user.getEmail()); pstmt.executeUpdate(); // 쿼리 실행 } finally { if (pstmt != null){ pstmt.close(); } if (conn != null){ conn.close(); //자원 반환 } } } public User findByUserId(String userId) throws SQLException { String sql = "select * from USERS where userId = ?"; Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try{ conn = getConnection(); pstmt = conn.prepareStatement(sql); pstmt.setString(1, userId); rs = pstmt.executeQuery(); if(!rs.next()){ return null; } return new User( rs.getString("userId"), rs.getString("password"), rs.getString("name"), rs.getString("email")); }finally{ if (rs != null){ rs.close(); } if (pstmt != null){ pstmt.close(); } if (conn != null){ conn.close(); //자원 반환 } } } public void removeUser(String userId) throws SQLException { String sql = "delete from USERS where userId = ?"; Connection conn = null; PreparedStatement pstmt = null; try{ conn = getConnection(); pstmt = conn.prepareStatement(sql); // 쿼리를 전달한 후, 정상적인 쿼리문인지 검증하는 단계 pstmt.setString(1, userId); pstmt.executeUpdate(); // 쿼리 실행 } finally { if (pstmt != null){ pstmt.close(); } if (conn != null){ conn.close(); //자원 반환 } } } public void updateUser(User user) throws SQLException { String sql = "update USERS set password = ?, name = ?, email = ? where userId = ?"; Connection conn = null; PreparedStatement pstmt = null; try{ conn = getConnection(); pstmt = conn.prepareStatement(sql); // 쿼리를 전달한 후, 정상적인 쿼리문인지 검증하는 단계 pstmt.setString(1, user.getPassword()); pstmt.setString(2, user.getName()); pstmt.setString(3, user.getEmail()); pstmt.setString(4, user.getUserId()); pstmt.executeUpdate(); // 쿼리 실행 } finally { if (pstmt != null){ pstmt.close(); } if (conn != null){ conn.close(); //자원 반환 } } } } | cs |
※ 가입 폼과 수정 폼 중복 제거
현재 회원가입에 해당하는 form.jsp와 개인정보수정에 해당하는 update_form.jsp가 상당히 유사한 코드로 작성되고 있는 모습을 볼 수 있습니다. 중복되는 문제를 해결하기 위해서 이를 하나로 합쳐서 만들고 관리할 수 있을까요?
실제로 홈페이지를 만들 때, 입력 폼과 수정 폼에 대한 코드를 중복을 방지하기 위해 하나의 jsp로 만들어 같이 사용하는 경우가 많다고 합니다. 우리도 한번 두 jsp를 합쳐보도록 합시다.
현재 입력 폼 form.jsp와 수정 폼 update_form.jsp의 가장 큰 차이는, 수정 폼 사이사이에 데이터를 가져오는 ${user.~~}와 같은 코드가 존재하는 것 입니다.
이를 동시에 한 jsp 파일에서 적용시키려면 어떤 방법으로 접근해야 할까요? form.jsp에도 유저 데이터가 전달은 되지만, 유저 객체의 데이터 값이 비어있도록 만들면 가능할 것 같습니다.
만약 데이터가 들어오지 않고 비어있다면, 회원가입을 진행하는 폼 자체는 기존과 똑같은 형태로 사용자에게 보이게 될 것입니다. 대신 로그인을 한 후에 데이터가 있도록 만든다면, 수정 폼 자체에서는 해당 유저의 데이터가 채워진 수정 폼을 제공해 줄 수 있게 됩니다.
이를 시작하기 위해서는, 우리가 회원가입이나 개인정보수정 버튼을 눌렀을 때 바로 jsp를 가는 것이 아니라 각각 가입폼서블릿과 수정폼서블릿을 거쳐서 forward로 form.jsp로 이동하도록 구조를 개선해야 합니다.
가입폼서블릿에 해당하는 CreateFormUserServlet를 만들어봅시다.
(기존에 존재했던 SaveUserServlet.java의 이름도 생성한다는 Create로 맞춰주기 위해 CreateUserServlet으로 수정해줍시다.)
CreateFormUserServlet.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package net.slipp.user; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/users/createForm") public class CreateFormUserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setAttribute("user", new User()); RequestDispatcher rd = req.getRequestDispatcher("/form.jsp"); rd.forward(req, resp); } } | cs |
경로는 폼을 생성한다는 뜻인 /users/createForm으로 작성했구요. 회원가입에 해당하는 하이퍼링크도 이 경로로 맞춰주면 되겠죠?
<li><a href="/users/createForm">회원가입</a></li>
이제 get 메소드를 통해서 form.jsp로 이동하게 됩니다. 회원가입 시에는 입력 전 상황이므로 모든 데이터가 존재하지 않는 빈 칸으로 나와야 합니다. 따라서 User클래스를 빈 데이터로 만들 수 있는 default 생성자로 만들어줍시다.
req.setAttribute("user", new User());
이렇게 만들어준다면, form.jsp로 forward를 통해 이동하게 되었을 때 사용자는 평소와 똑같이 빈 화면에서 자신이 정보를 입력할 수 있는 화면을 제공받을 수 있게 됩니다.
이와는 다르게 수정폼 서블릿에서는 default 생성자가 아닌 실제 데이터가 존재해야하므로 아래와 같이 작성해서 불러올 것 입니다.
User user = userDAO.findByUserId(userId);
req.setAttribute("user", user);
수정폼 서블릿의 전체 소스코드는 아래와 같습니다.
UpdateFormUserServlet.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | package net.slipp.user; import java.io.IOException; import java.sql.SQLException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; @WebServlet("/users/updateForm") public class UpdateFormUserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); Object object = session.getAttribute(LoginServlet.SESSION_USER_ID); if(object == null){ resp.sendRedirect("/"); return; } String userId = (String)object; System.out.println("User Id : " + userId); UserDAO userDAO = new UserDAO(); try { User user = userDAO.findByUserId(userId); req.setAttribute("user", user); RequestDispatcher rd = req.getRequestDispatcher("/form.jsp"); rd.forward(req, resp); } catch (SQLException e) { } } } | cs |
이와 같이 두 서블릿은 똑같이 forward를 통해 form.jsp로 이동하지만, 회원가입에 해당하는 서블릿은 정보가 없는 default User클래스를 통해 form.jsp로 전송되고, 정보수정에 해당하는 서블릿은 현재 로그인 되어있는 User의 데이터를 가져온 User클래스를 통해 form.jsp로 전송되는 것을 확인할 수 있습니다.
이제 form.jsp를 수정할 차례입니다.
우리는 update_form.jsp에서 사용되고 있는 코드를 form.jsp에 합치도록 할 것입니다.
기본적은 틀은 똑같기 때문에, 우리가 저번에 사용해봤던 jstl과 EL을 통해 조건문을 걸어 만들 수 있지 않을까요??
우선, 회원가입과 개인정보수정 시 출력되는 글씨를 아래와 같이 각각 상황에 맞게 출력되도록 만들 수 있습니다.
1 2 3 4 5 6 7 8 | <c:choose> <c:when test="${empty user.userId}"> <h1>회원가입</h1> </c:when> <c:otherwise> <h1>개인정보수정</h1> </c:otherwise> </c:choose> | cs |
유저의 id가 존재하지 않는다면 회원가입을 출력, 존재한다면 개인정보수정이라는 문구가 출력되겠죠?
이처럼 하나의 form.jsp 안에서 회원가입과 개인정보수정으로 달라지는 부분만 jstl을 활용해 두가지를 모두 구현해주면 될 것입니다.
이는 현재 유저의 아이디가 존재유무에 따라 한가지만 작동이 될 것이기 때문에 update_form.jsp와 중복되는 문제를 해결할 수 있습니다!
이 밖에 바꿔야 할 부분이 있는 곳을 생각해봅시다.
회원가입 시에는 유저 아이디도 입력해야 하지만, 개인정보수정 시에는 아이디는 수정하지 않을 것이기 때문에 이 부분도 바꿔줘야겠죠?
1 2 3 4 5 6 7 8 9 | <c:choose> <c:when test="${empty user.userId}"> <input type="text" name="userId" value="${user.userId}" /> </c:when> <c:otherwise> <input type="hidden" name="userId" value="${user.userId}"/> ${user.userId} </c:otherwise> </c:choose> | cs |
유저 아이디가 비어있으면, text 형식의 input 박스가 나오도록 하고 있구요. (유저 아이디가 없으면 default 생성자를 만들기 때문에 value 값이 비어있게 될 것입니다.)
유저 아이디가 있으면, hidden을 통해 보이지 않도록 유저 아이디에 대한 데이터를 가져오고 단순히 ${user.userId}로 출력하는 구조를 볼 수 있습니다.
나머지 패스워드, 이름, 이메일 또한 value 값에 해당하는 ${users.~~}를 작성해주시면 됩니다.
마지막에 submit으로 정보를 전달할 버튼 또한 아래와 같이 바꿔줄 수도 있겠네요!
1 2 3 4 5 6 7 8 | <c:choose> <c:when test="${empty user.userId}"> <button type="submit" class="btn btn-primary">회원가입</button> </c:when> <c:otherwise> <button type="submit" class="btn btn-primary">수정완료</button> </c:otherwise> </c:choose> | cs |
회원가입 시에는 전송버튼에 '회원가입'이, 개인정보수정 시에는 전송버튼이 '수정완료'로 나오게 될 것입니다.
마지막으로 설정해야 할 부분이 바로 이 form.jsp의 action 경로입니다.
입력 폼과 수정 폼마다 다른 경로로 action이 이루어져야 정확한 처리가 될 수 있습니다. 현재 우리가 만든 것으로는 form.jsp가 회원가입일 때는 "/users/create"로, 개인정보수정일 때는 "/users/update"로 이동해야 이에 해당하는 서블릿에서 제대로 처리할 수 있게 됩니다.
이는 jstl의 set기능을 통해서 해결할 수 있습니다.
1 2 3 4 | <c:set var="actionUrl" value="/users/create"/> <c:if test="${not empty user.userId}"> <c:set var="actionUrl" value="/users/update"/> </c:if> | cs |
actionUrl이라는 변수명을 만들어주고, value의 default 값으로 회원가입완료 경로에 해당하는 "/users/create"를 지정해줍니다.
그리고 조건문 if에 따라 유저 아이디가 존재했을 때는 경로를 "/users/update"로 set할 수 있도록 구현해주기만 하면 현재 상황에 맞는 경로로 잘 이동이 될 것입니다.
이제 우리 form.jsp의 action을 위에 set에서 만든 변수명인 "${actionUrl}"로 지정만 해주면 완료됩니다!
서버를 재시작하고 회원가입과 개인정보수정을 해보면 잘 작동하는 것을 확인할 수 있습니다.
form.jsp 전체 소스코드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <%@include file="../commons/_head.jspf"%> </head> <body> <%@include file="../commons/_top.jspf"%> <div class="container"> <div class="row"> <div class="span12"> <section id="typography"> <div class="page-header"> <c:choose> <c:when test="${empty user.userId}"> <h1>회원가입</h1> </c:when> <c:otherwise> <h1>개인정보수정</h1> </c:otherwise> </c:choose> </div> <c:set var="actionUrl" value="/users/create"/> <c:if test="${not empty user.userId}"> <c:set var="actionUrl" value="/users/update"/> </c:if> <form class="form-horizontal" action="${actionUrl}" method="post"> <div class="control-group"> <label class="control-label" for="userId">사용자 아이디</label> <div class="controls"> <c:choose> <c:when test="${empty user.userId}"> <input type="text" name="userId" value="${user.userId}" /> </c:when> <c:otherwise> <input type="hidden" name="userId" value="${user.userId}"/> ${user.userId} </c:otherwise> </c:choose> </div> </div> <div class="control-group"> <label class="control-label" for="password">비밀번호</label> <div class="controls"> <input type="password" id="password" name="password" value="${user.password}" placeholder=""> </div> </div> <div class="control-group"> <label class="control-label" for="name">이름</label> <div class="controls"> <input type="text" id="name" name="name" value="${user.name}" placeholder=""> </div> </div> <div class="control-group"> <label class="control-label" for="email">이메일</label> <div class="controls"> <input type="text" id="email" name="email" value="${user.email}" placeholder=""> </div> </div> <div class="control-group"> <div class="controls"> <c:choose> <c:when test="${empty user.userId}"> <button type="submit" class="btn btn-primary">회원가입</button> </c:when> <c:otherwise> <button type="submit" class="btn btn-primary">수정완료</button> </c:otherwise> </c:choose> </div> </div> </form> </div> </div> </div> </body> </html> | cs |
이제 우리는 form.jsp에 update_form.jsp가 하던 역할까지 모두 적용시키며 중복을 최소화 시키는데 성공했습니다. 과감하게 update_form.jsp를 삭제하고 다시 실행해봅시다.
이처럼 중복되는 코드를 최대한 줄여가는 리팩토링 과정을 거치면서 코드를 더욱 효율성있게 관리하게 된다면 유지보수가 좋은 웹페이지를 만드는 데 큰 도움이 될 수 있을 것 입니다.
'JSP > Servlet과JSP로 사용자 관리 시스템 구현' 카테고리의 다른 글
Servlet/JSP로 사용자 관리 시스템 구현하기 과정 - 7 (0) | 2018.08.02 |
---|---|
Servlet/JSP로 사용자 관리 시스템 구현하기 과정 - 5 (0) | 2018.07.23 |
Servlet/JSP로 사용자 관리 시스템 구현하기 과정 - 4 (0) | 2018.07.11 |
Servlet/JSP로 사용자 관리 시스템 구현하기 과정 - TDD (0) | 2018.07.06 |
Servlet/JSP로 사용자 관리 시스템 구현하기 과정 - 3 (0) | 2018.07.06 |