Jakarta Struts 강좌 7 - 스트러츠와 데이터베이스 연결
Summary : 지금까지 6번의 강좌를 통하여 스트러츠의 기본적인 내용에 대하여 살펴보았다. 최근 대부분 웹 애플리케이션은 데이터베이스와의 연동을 통하여 구현이 되고 있다. 따라서 스트러츠 프레임?을 이용하여 관계형 데이터베이스와 연결하여 사용하는 방법에 대하여 살펴보는 것 또한 중요하다. 따라서 이번 강좌에서는 스트러츠 프레임?에서 관계형 데이터베이스와 연동하여 구현하는 방법에 대하여 살펴본다. 이번 강좌에서 사용할 예제는 지난 강좌까지 만들어 왔던 사용자 관리를 mySQL 데이터베이스를 이용할 수 있도록 변경해 보도록 하겠다.
사용자 관리 클래스 다이어 그램
사용자 관리의 클래스는 앞으로 계속해서 진행될 강좌(EJB, JDO)를 위하여 다양한 J2EE패턴을 적용하여 설계하였다. 사용자 관리의 클래스 다이어그램은 다음과 같다.
위 사용자 관리 클래스 다이어그램에서 사용한 패턴은 다음과 같다.
첫째, 다양한 데이터베이스의 지원이 가능하도록 하기 위하여 Factory Method 패턴과 Abstract Factory 패턴을 적용하였다. Abstract Factory 패턴을 제대로 적용하려고 했으나 부족한 부분이 있다.
둘째, 비지니스 로직과 데이터 베이스와의 조작을 분리하기 위하여 데이터 베이스와의 작업을 전담하는 Data Access Object클래스를 따로 두었다. J2EE패턴의 DAO패턴을 적용하였다.
셋째, J2EE패턴의 Transfer Object, Value Object를 만들어 적용해 보았다. 굳이 J2SE만을 이용하는 프로젝트에서는 필요없는 부분이지만 앞으로의 예제를 위하여 미리 구현해봤다.
강좌를 단순히 하기 위하여 패턴의 적용없이 구현하려고 했으나 앞으로 계속되는 강좌를 위해 패턴을 적용하여 확장성을 기해보았다.
강좌를 위한 초기 설정
이번 강좌에서는 데이터베이스와 연관되는 부분이 있기 때문에 초기에 설정해야 하는 작업이 있다.
첫번째로 데이터베이스는 mySQL을 사용하고 있다. http://www.mysql.com에서 3.23.X버전 이상을 다운받아 설치하기 바란다. mySQL에 대한 자세한 설명은 웹상의 문서들을 참조하기 바란다.
두번째는 http://homepages.nildram.co.uk/~slink/java/DBPool/에서 제공하는 Connection Pooling을 사용하고 있다. zigimall/WEB-INF/classes아래에 보면 dbpool.properties파일을 자신의 데이터베이스 환경에 맞도록 수정한다. dbpool.properties의 내용은 다음과 같다.
#JDBC Driver 등록
drivers=org.gjt.mm.mysql.Driver
#초기 Connection Pool 사이즈
mysql.initsize=10
#최대 Connection Pool 사이즈
mysql.maxpool=20
#Mysql의 비밀번호
mysql.password=javajigi
#MySQL URL
mysql.url=jdbc:mysql://127.0.0.1:3306/zigimall?useUnicode=true&characterEncoding=KSC5601
#MySQL의 User ID
mysql.user=javajigi
fdsdf
세번째는 사용자 관리에 사용할 테이블의 생성이다. 사용자 관리 테이블은 단순히 사용자 아이디, 비밀번호, 이름, 이메일 정보만을 가지도록 생성하였다.
CREATE TABLE userInfo ( userId varchar(12) NOT NULL, password varchar(12) NOT NULL, name varchar(10) NOT NULL, email varchar(50), PRIMARY KEY (userId), INDEX userInfo_userId_idx (userId) );
이상으로 이번강좌에서 사용자 관리를 만들기 위하여 필요한 데이터베이스 설정이 완료되었다. 다음은 데이터베이스와의 작업을 전담하는 클래스 내부에 대하여 살펴보도록 하겠다.
데이터베이스 전담 클래스들
데이터베이스 전담 클래스는 섹션 1에서 살펴본 클래스 다이어그램에 해당하는 클래스들이다. 이번 섹션에서는 데이터베이스와의 작업을 전담하는 클래스 각각의 내부에 대하여 살펴보도록 하겠다.
package net.javajigi.mall.db; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import snaq.db.ConnectionPoolManager; /** * 데이터베이스의 Connection객체를 얻는 작업과 * Connection, Statement, ResultSet를 닫는 역할을 하는 클래스. */ public class RDBDatabase { protected Connection getConnection() throws SQLException { ConnectionPoolManager conMgr = ConnectionPoolManager.getInstance(); return conMgr.getConnection("mysql"); } protected void cleanUp(Connection con, Statement stmt) throws SQLException { cleanUp(con, stmt, null); } protected void cleanUp(Connection con, Statement stmt, ResultSet rs) throws SQLException { if ( rs != null ) { rs.close(); } if ( stmt != null ) { stmt.close(); } if ( con != null ) { con.close(); } } }
package net.javajigi.mall.user; import java.sql.SQLException; import java.util.List; /** * 사용자 관리에 필요한 일들을 정의하는 인터페이스 */ public interface UserDAO { public UserVO getUser(String userId) throws SQLException; public int addUser(UserTO userTO) throws SQLException; public List getUsers() throws SQLException; public int updateUser(UserTO userTO) throws SQLException; public int removeUser(String userId) throws SQLException; }
package net.javajigi.mall.user.rdb; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import net.javajigi.mall.db.RDBDatabase; import net.javajigi.mall.user.UserDAO; import net.javajigi.mall.user.UserTO; import net.javajigi.mall.user.UserVO; /** * UserDAO를 구현하는 클래스. * MySQL데이터베이스와의 작업을 전담하는 클래스. */ public class UserMysqlDAO extends RDBDatabase implements UserDAO { /** * @see net.javajigi.mall.user.UserDAO#getUser(String) */ public UserVO getUser(String userId) throws SQLException { Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; try { String findUserQuery = "SELECT " + "userId, password, " + "name, email " + "FROM userinfo " + "WHERE userId = ?"; pstmt = getConnection().prepareStatement(findUserQuery); pstmt.setString(1, userId); rs = pstmt.executeQuery(); if (rs.next()) { return new UserVO( (String) rs.getString("userId"), (String) rs.getString("password"), (String) rs.getString("name"), (String) rs.getString("email")); } else { return null; } } finally { cleanUp(con, pstmt, rs); } } /** * @see net.javajigi.mall.user.UserDAO#addUser(UserTO) */ public int addUser(UserTO userTO) throws SQLException { Connection con = null; PreparedStatement pstmt = null; try { String insertQuery = "INSERT INTO userinfo VALUES (?, ?, ?, ?)"; pstmt = getConnection().prepareStatement(insertQuery); pstmt.setString(1, userTO.getId()); pstmt.setString(2, userTO.getPassword()); pstmt.setString(3, userTO.getName()); pstmt.setString(4, userTO.getEmail()); int result = pstmt.executeUpdate(); return result; } finally { cleanUp(con, pstmt); } } /** * @see net.javajigi.mall.user.UserDAO#getUsers() */ public List getUsers() throws SQLException { Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; try { String findUsersQuery = "SELECT " + "userId, password, " + "name, email " + "FROM userInfo "; pstmt = getConnection().prepareStatement(findUsersQuery); rs = pstmt.executeQuery(); List users = new ArrayList(); while (rs.next()) { UserVO userVO = new UserVO( (String) rs.getString("userId"), (String) rs.getString("password"), (String) rs.getString("name"), (String) rs.getString("email")); users.add(userVO); } return users; } finally { cleanUp(con, pstmt, rs); } } /** * @see net.javajigi.mall.user.UserDAO#updateUser(UserTO) */ public int updateUser(UserTO userTO) throws SQLException { Connection con = getConnection(); PreparedStatement pstmt = null; try { String updateQuery = "UPDATE userinfo SET " + "password = ?, " + "name = ?, " + "email = ? " + "WHERE userId = ?"; pstmt = con.prepareStatement(updateQuery); pstmt.setString(1, userTO.getPassword()); pstmt.setString(2, userTO.getName()); pstmt.setString(3, userTO.getEmail()); pstmt.setString(4, userTO.getId()); int result = pstmt.executeUpdate(); return result; } finally { cleanUp(con, pstmt); } } /** * @see net.javajigi.mall.user.UserDAO#removeUser(String) */ public int removeUser(String userId) throws SQLException { Connection con = null; PreparedStatement pstmt = null; try { String removeQuery = "DELETE FROM userinfo WHERE userId = ?"; pstmt = getConnection().prepareStatement(removeQuery); pstmt.setString(1, userId); int result = pstmt.executeUpdate(); return result; } finally { cleanUp(con, pstmt); } } }
package net.javajigi.mall.user; import net.javajigi.mall.user.rdb.UserMysqlDAO; public class UserDAOFactory { private static UserDAOFactory _instance = null; public static int DATABASE_MYSQL = 1; private UserDAOFactory() { } public static synchronized UserDAOFactory instance() { if (_instance == null) { _instance = new UserDAOFactory(); } return _instance; } public UserDAO getUserDAO(int dbType) { if ( dbType == DATABASE_MYSQL ) { return (new UserMysqlDAO()); } else { return null; } } }
package net.javajigi.mall.user; public class UserTO { private String id = null; private String password = null; private String name = null; private String email = null; public String getEmail() { return email; } public String getId() { return id; } public String getName() { return name; } public String getPassword() { return password; } public void setEmail(String email) { this.email = email; } public void setId(String id) { this.id = id; } public void setName(String name) { this.name = name; } public void setPassword(String password) { this.password = password; } }
package net.javajigi.mall.user; public class UserVO { private String id = null; private String password = null; private String name = null; private String email = null; public UserVO(String id, String password, String name, String email){ this.id = id; this.password = password; this.name = name; this.email = email; } public String getId() { return id; } public String getPassword() { return password; } public String getEmail() { return email; } public String getName() { return name; } public boolean equals(Object userVO) { return this.equals(userVO); } public String toString() { return getId(); } }
스트러츠의 Action클래스, JSP파일
이번 섹션에서는 사용자 관리를 위한 Action클래스에 대하여 살펴본다. 모든 구조는 앞에서 살펴본 Action클래스와 같다. 단지 달라진 부분은 데이터베이스와의 작업이 가능하도록 수정해 주면 된다. 다음에 살펴볼 클래스들은 앞에서 살펴본 데이터베이스의 접근을 전담하는 클래스들을 이용하는 Action클래스들이다.
또한 각각의 Action클래스 작업이 완료한 다음 뷰를 전담하는 JSP파일에 대하여 살펴보도록 하겠다.
package net.javajigi.mall.user.action; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.javajigi.mall.user.UserDAO; import net.javajigi.mall.user.UserDAOFactory; import org.apache.struts.action.Action; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; public class UserListAction extends Action { public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { UserDAOFactory udf = UserDAOFactory.instance(); UserDAO userDAO = udf.getUserDAO(UserDAOFactory.DATABASE_MYSQL); List users = userDAO.getUsers(); //사용자의 정보를 얻어와 사용자의 정보를 담은 객체를 //request에 저장하여 전달하고 있다. //이것이 가능한 이유는 RequestDispatcher를 이용하기 때문이다. request.setAttribute("listuser", users); return mapping.findForward("userlist"); } }
<%@ page contentType="text/html;charset=EUC-KR"%> <%@ page import="java.util.*"%> <%@ page import="net.javajigi.mall.user.*"%> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %> <html:html locale="true"> <head> <title>사용자 리스트</title> <html:base/> <script language="JavaScript"> function update(userId) { f.userId.value = userId; f.action = "./userUpdateForm.do"; f.submit(); } function del(userId) { f.userId.value = userId; f.action = "./deleteuser.do"; f.submit(); } </script> </head> <body> <form name="f" method="post"> <html:errors /> <br/> <table border="1"> <tr> <td><bean:message key="prompt.id" /></td> <td><bean:message key="prompt.name" /></td> <td><bean:message key="prompt.email" /></td> <td></td> </tr> <% List users = (List)request.getAttribute("listuser"); for (int i=0; i < users.size(); i++) { UserVO userVO = (UserVO)users.get(i); %> <tr> <td><%= userVO.getId() %></td> <td><%= userVO.getName() %></td> <td><%= userVO.getEmail() %></td> <td> <a href="javascript:update('<%= userVO.getId() %>')">수정</a> <a href="javascript:del('<%= userVO.getId() %>')">삭제</a> </td> </tr> <% } %> </table> <input type="hidden" name="userId"/> <html:link page="/useraddform.do"> <bean:message key="user.useradd" /> </html:link> <html:link page="/login.jsp"> <bean:message key="useradd.cancel" /> </html:link> <form> </body> </html:html>
package net.javajigi.mall.user.action; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.javajigi.mall.user.form.UserForm; import net.javajigi.mall.user.UserDAO; import net.javajigi.mall.user.UserDAOFactory; import net.javajigi.mall.user.UserTO; import org.apache.commons.beanutils.PropertyUtils; import org.apache.struts.action.Action; import org.apache.struts.action.ActionError; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; public class UserAddAction extends Action { public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { UserForm useraddForm = (UserForm) form; ActionErrors errors = new ActionErrors(); //요청된 Request의 Token이 유효한지를 처리하는 부분이다. //Token이 유효하지 않을 경우 ActionErrors에 에러를 저장한다. //따라서 같은 요청이 재요청될 경우 에러를 발생시키기 때문에 같은 요청이 //두번 반복해서 처리되지는 않는다. if (!isTokenValid(request)) { errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.transaction.token")); } resetToken(request); // Report any errors we have discovered back to the original form if (!errors.isEmpty()) { saveErrors(request, errors); saveToken(request); return (mapping.getInputForward()); } UserTO userTO = new UserTO(); /* userTO.setId(useraddForm.getId()); userTO.setPassword(useraddForm.getPassword()); userTO.setName(useraddForm.getName()); userTO.setEmail(useraddForm.getEmail()); */ PropertyUtils.copyProperties(userTO, useraddForm); UserDAOFactory udf = UserDAOFactory.instance(); UserDAO userDAO = udf.getUserDAO(UserDAOFactory.DATABASE_MYSQL); userDAO.addUser(userTO); useraddForm.reset(mapping, request); return(mapping.findForward("loginForm")); } }
package net.javajigi.mall.user.action; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import net.javajigi.mall.Constants; import net.javajigi.mall.user.form.UserForm; import net.javajigi.mall.user.UserDAO; import net.javajigi.mall.user.UserDAOFactory; import net.javajigi.mall.user.UserVO; import org.apache.struts.action.Action; import org.apache.struts.action.ActionError; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; public class LoginAction extends Action { public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { UserForm loginForm = (UserForm)form; String inputId = loginForm.getId().trim(); String inputPassword = loginForm.getPassword(); UserDAOFactory udf = UserDAOFactory.instance(); UserDAO userDAO = udf.getUserDAO(UserDAOFactory.DATABASE_MYSQL); UserVO userVO = userDAO.getUser(inputId); ActionErrors errors = new ActionErrors(); if ( userVO == null ) { //UserVO가 null이면 존재하지 않는 아이디로 파악하여 에러 메세지를 출력. errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.id.notexisted")); } else { //사용자 아이디와 비밀번호가 틀릴경우 비밀번호가 틀리다는 에러 메세지 출력 if ( !inputPassword.equals(userVO.getPassword()) ) { errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.password.match")); } } //로그인 과정에서 에러가 있을 경우 Action클래스의 saveErrors에 에러를 저장하고 //로그인 폼페이지로 반환하게된다. //mapping의 getInputForward()에서 반환하게 되는 정보는 struts-config.xml의 action태그의 //input attribute에 정의된 페이지로 이동하게 된다. if (!errors.isEmpty()) { saveErrors(request, errors); return (mapping.getInputForward()); } //로그인 과정이 정상적으로이루어 지면 userVO객체를 세션에 저장. HttpSession session = request.getSession(); session.setAttribute(Constants.USER_KEY, userVO); //로그인 과정이 정상적으로이루어진 다음 메인페이지로 이동한다. //mapping의 findForward에서 사용하는 key값은 struts-config.xml의 forward태그의 //name attribute를 이용한다. return(mapping.findForward("mainpage")); } }
지금까지 스터르츠의 Action클래스와 데이터베이스의 데이터를 가져와 JSP에서 사용하는 방법에 대하여 살펴보았다. 사용자 관리를 각각의 작업들이 모두 위와 같은 과정으로 구현되어 있기 때문에 모든 소스를 보여주지는 않았다. 이 섹션에서 살펴본 예제 이외의 소스는 첨부되는 소스를 참고하여 구현 과정을 살펴보기 바란다.
강좌에 대하여
작성자 : [Javajigi](박재성)
작성일 : 2005년 2월 20일
문서이력 :
- 2005년 2월 20일 박재성 문서 최초 생성
참고 자료
- 강좌에서 사용한 소스 : zigimall_20030511.war
- Struts 프레임워크 사이트 : http://jakarta.apache.org/struts/index.html
- MySQL 데이터베이스 : http://www.mysql.com
출처 : http://wiki.javajigi.net/pages/viewpage.action?pageId=79