명함관리 웹 애플리케이션

본격적으로 서블릿/JSP를 공부하기 전에 JDBC에서 예제로 다루었던 명함관리를 웹 애플리케이션으로 바꾸는 실습을 한다. 이 실습믜 목표는 순수 자바 애플리케이션과 웹 애플리케이션의 차이에 대한 이해와 웹 환경 체험이다.
자세한 설명은 곧 다루니 이 절에선 목표에 충실하자.

명함관리를 웹 애플리케이션으로 바꾸기 위한 준비작업

1. 오라클 JDBC 드라이버를 CATALINA_HOME/lib 에 복사한다.

JDBC 드라이버는 특별한 이유1 때문에 웹 애플리케이션의 WEB-INF/lib 가 아닌 CATALINA_HOME/lib 에 있어야 한다.
다시 말해, WEB-INF/lib 에는 JDBC 드라이버가 없어야 한다.
오라클 JDBC 드라이버인 ojdbc6.jar 파일을 CATALINA_HOME/lib 에 복사한다.

2. 웹 애플리케이션을 위한 디렉토리 구조를 마련한다.

C:/www/namecard 를 명함관리 웹 애플리케이션의 최상위 디렉토리로 정했다면
C:/www/namecard 아래 다음과 같은 서브 디렉토리를 만들어야 한다.

  • WEB-INF
  • WEB-INF/classes
  • WEB-INF/lib

3. web.xml 파일을 WEB-INF 디렉토리에 만든다.

CATALINA_HOME/webapps/ROOT/WEB-INF/web.xml 을 복사하여 C:/www/namecard/WEB-INF/에 붙여넣는다.
복사한 후 C:/www/namecard/WEB-INF/web.xml 파일을 편집기로 열고 web-app 엘리먼트 안에 있는 모든 내용을 지운다.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  version="4.0">

</web-app>

4. namecard.xml 컨텍스트 파일 만든다.

아래 내용대로 namecard.xml 파일을 만든 다음 CATALINA_HOME/conf/Catalina/localhost 로 옮긴다.

<?xml version="1.0" encoding="UTF-8"?>
<Context
    docBase="C:/www/namecard"
    reloadable="true">
</Context>

명함관리 웹 애플리케이션 테스트

첫 번째 테스트

명함관리에서 실습했던 명함관리 Namecard 와 NamecardDao 바이트코드를 WEB-INF/classes 에 복사한다.

C:/www/namecard/WEB-INF/classes
                           └── net
                               └── java_school
                                       └── namecard - Namecard.class
                                                    - NamecardDao.class
						

아래 JSP 파일을 도큐먼트 베이스인 C:/www/namecard에 생성한다.
이클립스가 아닌 일반 에디터로 작업한다.

/list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="net.java_school.namecard.*" %>
<%@ page import="java.util.*" %>    
<!DOCTYPE html>
<%
NamecardDao dao = new NamecardDao();
ArrayList<Namecard> list = dao.selectAll();
%>
<html>
<head>
<meta charset="UTF-8">
<title>명함목록</title>
</head>
<body>
<table border="1">
<tr>
	<td>번호</td>
	<td>이름</td>
	<td>손전화</td>
	<td>이메일</td>
	<td>회사</td>
</tr>
<%
int size = list.size();
for(int i = 0;i < size;i++) {
	Namecard card = list.get(i);
%>	
<tr>
	<td><%=card.getNo() %></td>
	<td><%=card.getName() %></td>
	<td><%=card.getMobile() %></td>
	<td><%=card.getEmail() %></td>
	<td><%=card.getCompany() %></td>
</tr>
<%
}
%>	
</table>
<p>
<input type="button" value="추가" onclick="location.href='write.jsp'" />
</p>
</body>
</html>

톰캣을 재실행한다.
http://localhost:8080/namecard/list.jsp를 방문하여 테스트한다.

두번째 테스트

첫 번째 테스트처럼 자바 클래스 소스를 웹 애플리케이션이 위치한 곳과 전혀 상관없는 곳에 두어도 된다.
현재 소스는 'JDBC'에서 실습한 디렉토리(이를테면 C:/java/namecard/src)에 있다.
하지만 이 경우, 시간이 지나면 소스의 위치를 잊을 수 있으며 소스를 지우는 실수를 할 수 있다.
두번째 테스트는 소스를 웹 애플리케이션의 영역안에 옮기고 그 소스를 컴파일하는 테스트이다.
먼저 자바 소스를 어디에 두어야 할지를 정해야 하는데, WEB-INF 아래에 두면 웹브라우저로 직접 접근할 수 없으니 소스 디렉토리를 WEB-INF/src로 정하겠다.
이제 JDBC에서 실습했던 명함관리 src 디렉토리를 복사하여 WEB-INF 에 붙여 넣는다.
다음으로 명령프롬프트에서 C:\www\namecad\WEB-INF\src\net\java_school\namecard 로 이동하여 다음과 같이 컴파일을 수행한다.2

javac -d C:/www/namecard/WEB-INF/classes *.java

http://localhost:8080/namecard/list.jsp를 방문하여 테스트한다.

세번째 테스트

이클립스를 이용해서 작업하는 방법을 설명한다.
이클립스를 실행한다.
워크스페이스를 C:/www로 선택한다.
워크스페이스를 C:/www로 선택한다.
퍼스펙티브가 java3 인 상태에서 File - New - Java Project 메뉴를 차례로 선택하여 새로운 자바 프로젝트를 namecard란 이름으로 만든다.
Java 퍼스펙티브가 선택된 상태에서 Java Project 를 만든다.
우리는 순수 자바 애플리케이션 아닌 웹 애플리케이션을 작성하고 있으므로 이클립스가 디폴트로 셋팅해준 src와 bin를 그대로 사용해서는 안 된다.
프로젝트에 마우스를 선택한 상태에서 마우스 오른쪽 버튼을 클릭한다.
Build Path - Configure Build Path..를 선택한다.
Source탭에서 Source folder 는 WEB-INF/src 를 선택한다.
Default output folder 는 WEB-INF/classes 를 선택해야 한다.
명함관리 웹 APP 이클립스 소스 폴더와 Output 폴더 설정
이제 이클립스에서 소스를 수정하면 따로 컴파일하지 않아도 바이트 코드가 WEB-INF/classes에 생긴다.
http://localhost:8080/namecard/list.jsp를 방문하여 테스트한다.
테스트가 성공했다면 명함을 등록하는 JSP파일을 C:/www/namecard 에 만든다.
먼저 이클립스의 JSP 템플릿의 캐릭터셋을 EUC-KR 에서 UTF-8로 변경한다.
이클립스에서 Windows - Preferences - Web - JSP Files 선택하고 인코딩 박스에서 UTF-8를 선택하고 Apply 클릭한다.
JSP 파일인코딩 UTF-8로
아래 write.jsp 소스에서 강조된 부분이 여러분이 직접 입력해야 하는 부분이다.

/write.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>명함 추가</title>
</head>
<body>
<h1>명함 추가하기</h1>
<form action="write_proc.jsp" method="post">
이름 : <input type="text" name="name" /><br />
손전화 : <input type="text" name="mobile" /><br />
이메일 : <input type="text" name="email" /><br />
회사 : <input type="text" name="company" /><br />
<input type="submit" value="전송" />
<input type="button" value="취소" onclick="location.href='list.jsp'" />
</form>
</body>
</html>

다음은 write_proc.jsp 파일을 만든다.
이 페이지는 write.jsp 에서 전송받은 값으로 명함을 추가한다.
아래 소스에서 강조된 부분이 여러분이 직접 입력해야 하는 부분이다.

/write_proc.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="net.java_school.namecard.*" %>
<!DOCTYPE html>
<%
request.setCharacterEncoding("UTF-8");
String name = request.getParameter("name");
String mobile = request.getParameter("mobile");
String email = request.getParameter("email");
String company = request.getParameter("company");
Namecard namecard = new Namecard(name,mobile,email,company);
NamecardDao dao = new NamecardDao();
dao.insert(namecard);
%>
<html>
<head>
<meta charset="UTF-8">
<title>명함 추가</title>
</head>
<body>
명함이 추가되었습니다.<a href="list.jsp">목록</a>
</body>
</html>

http://localhost:8080/namecard/list.jsp를 방문한다.
명함목록에서 등록버튼을 클릭하여 새로운 명함 등록을 추가하는 테스트를 한다.
다음은 삭제기능을 구현한다.
먼저 list.jsp 에서 아래를 참조해서 테이블의 열를 추가한다.

/list.jsp
<!-- .. 중간 생략 .. -->

    <td>번호</td>
    <td>이름</td>
    <td>손전화</td>
    <td>이메일</td>
    <td>회사</td>
    <td>관리</td>

<!-- .. 중간 생략 .. -->

    <td><%=card.getNo() %></td>
    <td><%=card.getName() %></td>
    <td><%=card.getMobile() %></td>
    <td><%=card.getEmail() %></td>
    <td><%=card.getCompany() %></td>
    <td><a href="delete.jsp?no=<%=card.getNo() %>">삭제</a></td>
    
<!-- .. 중간 생략 .. -->

다음은 delete.jsp 작성한다.
delete.jsp 는 list.jsp에서 명함의 Primary key 에 해당하는 값을 전달받아 삭제를 수행한다.
아래 소스에서 강조된 부분이 직접 입력해야 하는 부분이다.

/delete.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="net.java_school.namecard.*" %>
<!DOCTYPE html>
<%
int no = Integer.parseInt(request.getParameter("no"));
NamecardDao dao = new NamecardDao();
dao.delete(no);
%>
<html>
<head>
<meta charset="UTF-8">
<title>명함 삭제</title>
</head>
<body>
명함이 삭제되었습니다.<a href="list.jsp">목록</a>
</body>
</html>

http://localhost:8080/namecard/list.jsp를 방문한다.
명함을 삭제하는 테스트를 수행한다.
다음으로 수정기능 구현한다.
NamecardDao.java 에 수정을 담당하는 메소드는 아래와 같다.

NamecardDao.java
public void update(Namecard card) {
	String sql = "UPDATE namecard " +
			"SET name = ? " +
			",mobile = ? " +
			",email = ? " +
			",company = ? " +
			"WHERE no = ?";
			
	Connection con = null;
	PreparedStatement pstmt = null;
	
	try {
		con = getConnection();
		pstmt = con.prepareStatement(sql);
		pstmt.setString(1, card.getName());
		pstmt.setString(2, card.getMobile());
		pstmt.setString(3, card.getEmail());
		pstmt.setString(4, card.getCompany());
		pstmt.setInt(5, card.getNo());
		pstmt.executeUpdate();
		
//.. 중간 생략 ..

list.jsp 파일에서 수정양식을 보여주는 페이지로 이동하는 링크를 삭제링크 옆에 작성한다.
아래를 참고한다.

/list.jsp
<td>
	<a href="delete.jsp?no=<%=card.getNo() %>">삭제</a>
	<a href="modify.jsp?no=<%=card.getNo() %>">수정</a>
</td>

다음은 modify.jsp 파일을 작성한다.
참고로 modify.jsp 는 사용자 UI 통일성을 주기 위해서 wirte.jsp 소스를 copy & paste 한후 약간의 추가 작업을 하여 만들었다.

/modify.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="net.java_school.namecard.*" %>
<!DOCTYPE html>
<%
int no = Integer.parseInt(request.getParameter("no"));
NamecardDao dao = new NamecardDao();
Namecard card = dao.selectOne(no);
%>
<html>
<head>
<meta charset="UTF-8">
<title>명함 수정</title>
</head>
<body>
<h1>명함 수정하기</h1>
<form action="modify_proc.jsp" method="post">
<input type="hidden" name="no" value="<%=no %>" />
이름 : <input type="text" name="name" value="<%=card.getName() %>" /><br />
손전화 : <input type="text" name="mobile" value="<%=card.getMobile() %>" /><br />
이메일 : <input type="text" name="email" value="<%=card.getEmail() %>" /><br />
이메일 : <input type="text" name="company" value="<%=card.getCompany() %>" /><br />
<input type="submit" value="전송" />
<input type="button" value="취소" onclick="location.href='list.jsp'" />
</form>
</body>
</html>

<input type="hidden" name="no" value="<%=no %>" /> 이 부분이 폼태그에 반드시 있어야 한다.
다음은 modify_proc.jsp 파일을 작성한다.
이 페이지는 modify.jsp에서 전송된 값으로 명함을 수정하는 페이지이다.

modify_proc.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="net.java_school.namecard.*" %>
<!DOCTYPE html>
<%
request.setCharacterEncoding("UTF-8");
int no = Integer.parseInt(request.getParameter("no"));
String name = request.getParameter("name");
String mobile = request.getParameter("mobile");
String email = request.getParameter("email");
String company = request.getParameter("company");
Namecard card = new Namecard();
card.setNo(no);
card.setName(name);
card.setMobile(mobile);
card.setEmail(email);
card.setCompany(company);
NamecardDao dao = new NamecardDao();
dao.update(card);
%>
<html>
<head>
<meta charset="UTF-8">
<title>명함 수정</title>
</head>
<body>
명함이 수정되었습니다. <a href="list.jsp">목록</a>
</body>
</html>

http://localhost:8080/namecard/list.jsp를 방문하여 수정을 테스트한다.

다음으로 list.jsp 에 검색기능을 추가한다.
list.jsp 열고 </body> 전에 다음 폼을 추가한다.

/list.jsp
<form action="list.jsp" method="post">
	<input type="text" name="keyword" />
	<input type="submit" value="검색" />
</form>

검색을 위해서 NamecardDao.java 에 selectByKeyword(String keyword) 메소드를 추가한다.

NamecardDao.java
public ArrayList<Namecard> selectByKeyword(String keyword) {
	keyword = "%" + keyword + "%";
	ArrayList<Namecard> matched = new ArrayList<Namecard>();
		
	String sql ="SELECT no,name,mobile,email,company " + 
				"FROM namecard " +
			"WHERE name LIKE ? " +
				"OR mobile LIKE ? " +
				"OR email LIKE ? " + 
				"OR company LIKE ? " +
			"ORDER BY no DESC";
			
	Connection con = null;
	PreparedStatement pstmt = null;
	ResultSet rs = null;
	
	try {
		con = getConnection();
		pstmt = con.prepareStatement(sql);
		pstmt.setString(1, keyword);
		pstmt.setString(2, keyword);
		pstmt.setString(3, keyword);
		pstmt.setString(4, keyword);
		rs = pstmt.executeQuery();
		
		while(rs.next()) {
			int no = rs.getInt("no");
			String sname = rs.getString("name");
			String mobile = rs.getString("mobile");
			String email = rs.getString("email");
			String company = rs.getString("company");
			Namecard namecard = new Namecard(no,name,mobile,email,company);
			matched.add(namecard);
		}
	} catch (SQLException e) {
		e.printStackTrace();
		System.out.println(sql);
	} finally {
		close(rs,pstmt,con);
	}
	
	return matched;
}

검색을 테스트하기 위해 list.jsp 을 방문하면 null로 검색이 되는 버그가 있다.
list.jsp 를 웹브라우저의 주소창에서 처음 방문할 때는 keyword 가 null 이 되기 때문이다.
그리고 list.jsp 파일에서 검색필드에 아무런 값도 넣지 않고 검색 버튼을 클릭했다면 keyword 는 ""(공백문자)이다.
list.jsp 을 열고 아래 코드를 참고하여 수정한다.

/list.jsp
<%
//기존 코드는 주석처리한다.
//NamecardDao dao = new NamecardDao();
//ArrayList<Namecard> list = dao.selectAll();

request.setCharacterEncoding("UTF-8");
String keyword = request.getParameter("keyword");

NamecardDao dao = new NamecardDao();
ArrayList<Namecard> list = null;

if (keyword == null) {
	keyword = "";
}
if (keyword.equals("")) {
	list = dao.selectAll();
} else {
	list = dao.selectByKeyword(keyword);
}
%>

모두 작성했다면 http://localhost:8080/namecard/list.jsp를 방문하여 테스트한다.

주석
  1. 각각의 웹 애플리케이션의 WEB-INF/lib 에 JDBC 드라이버 파일을 두면 메모리 누수문제가 일어날 수 있다.
  2. 만약 NamecardDao 클래스가 커넥션 풀을 이용한다면 커넥션풀관련 클래스를 앞서 컴파일 해야한다.
  3. 이와는 달리 대부분이 책에서 퍼스펙티브가 Java EE 에서 Dynamic Web Project 로 프로젝트를 생성하는 방법을 설명한다. 본 사이트에서 제공하는 기초 과정을 모두 공부한 다음이 아니고, 이클립스보다 서블릿/JSP에 초점을 맞추려면 본 사이트에서 제시한 방법이 더 낫다.
참고