관리 메뉴

거니의 velog

(17) 서블릿의 비즈니스 로직 처리 본문

Java_Servlet

(17) 서블릿의 비즈니스 로직 처리

Unlimited00 2023. 8. 25. 20:22

1. 서블릿의 비즈니스 로직 처리 방법

* 웹 프로그램은 클라이언트의 요청에 대해서 비즈니스 처리 기능을 이용해 데이터 저장소에서 데이터를 조회한 후 서블릿의 응답 기능을 이용해 클라이언트에게 결과를 전송한다.

* 예를 들어 인터넷 교보문고나 예스24 같은 도서 쇼핑몰(온라인 서점)에서 책 제목을 검색창에 입력하고 검색 버튼을 누르면 책 제목이 서블릿으로 전송된다. 그럼 서블릿은 책 제목을 전송 받아 책 제목에 대한 정보를 데이터베이스 연동 기능을 이용해 조회한다. 그리고 조회한 결과를 서블릿 응답 기능을 이용해 클라이언트 브라우저에 전송하여 결과를 보여준다.

* 서블릿 비즈니스 처리 작업이란 서블릿이 클라이언트로부터 요청을 받으면 그 요청에 대해 작업을 수행하는 것을 의미한다. 웹 프로그램에서 대부분의 비즈니스 처리 작업은 데이터베이스 연동 관련 작업이지만 그 외에 다른 서버와 연동해서 데이터를 얻는 작업도 수행한다. 이 기능은 서블릿의 핵심 기능이라 할 수 있을 만큼 중요하다.

* 서블릿의 비즈니스 작업 예로는 여러 가지를 들 수 있지만 대표적인 것들은 다음과 같다.

- 웹 사이트 회원 등록 요청 처리 작업
- 웹 사이트 로그인 요청 처리 작업
- 쇼핑몰 상품 주문 처리 작업


2. 서블릿의 데이터베이스 연동하기

* 이번에는 서블릿에서 데이터베이스와 연동하여 조회한 데이터를 얻은 후 클라이언트의 웹 브라우저로 응답하는 과정을 알아보자.

* 서블릿에서 데이터베이스와 연동하는 과정은 자바의 데이터베이스 연동 과정과 같다. 클라이언트로부터 요청을 받으면 서블릿은 SQL문을 사용해 데이터베이스에 접근하여 작업을 한다. 이 과정에서 DAO와 VO 클래스가 사용된다.


(1) 서블릿으로 회원 정보 테이블의 회원 정보 조회

* 각 클래스가 연동해서 데이터베이스에 접근한다. 그런 다음 서블릿에서 회원 정보를 조회한 후 이 정보를 다시 HTML로 만들어 웹 브라우저로 전송해 출력한다.

1. 웹 브라우저가 서블릿에게 회원 정보를 요청한다.

2. MemberServlet은 요청을 받은 후 MemberDAO 객체를 생성하여 listMembers() 메서드를 호출한다.

3. listMembers()에서 다시 connDB() 메서드를 호출하여 데이터베이스와 연결한 후 SQL 문을 실행해 회원 정보를 조회한다.

4. 조회된 회원 정보를 MemberVO 속성에 설정한 후 다시 ArrayList에 저장한다.

5. ArrayList를 다시 메서드를 호출한 MemberServlet으로 반환한 후 ArrayList의 MemberVO를 차례대로 가져와 회원 정보를 HTML 태그의 문자열로 만든다.

6. 만들어진 HTML 태그를 웹 브라우저로 전송해서 회원 정보를 출력한다.

[테이블 t_member 구성]

NO 속성 이름 컬럼 이름 자료형 크기 유일키 여부 NULL 여부 기본값
1 ID id varchar2 10 Y N 기본키  
2 비밀번호 pwd varchar2 10   N    
3 이름 name varchar2 50   N    
4 이메일 email varchar2 50   N    
5 가입일자 joinDate date     N   sysdate

* 그럼 지금부터 회원 정보 테이블을 생성한 후 회원 정보를 추가하여 웹 브라우저에서 서블릿으로 요청하면 데이터베이스(회원 테이블)와 연동해 회원 정보를 웹 브라우저로 출력하는 작업을 해보자.

1. 먼저 SQL Developer에서 회원 테이블과 회원 정보를 입력하기 위해 SQL Developer를 실행한다.

2. 왼쪽 메뉴의 + 기호를 클릭한 후 새 접속...을 선택한다.

3. 왼쪽 메뉴에 미리 만들어 놓은 접속 이름을 클릭하거나 직접 연결 정보를 입력한 후 접속을 클릭한다.

4. 접속한 후 생성되는 워크시트에 다음과 같은 테이블 생성 SQL문을 입력한다.

-- 회원 테이블 생성
CREATE TABLE t_mamber(
    id varchar2(10) primary key,
    pwd varchar(10),
    name varchar2(50),
    email varchar2(50),
    joinDate date default sysdate -- 명시적으로 추가하지 않으면 현재 시각을 입력한다.
);

-- 회원 정보 추가
INSERT INTO t_member
VALUES('hong', '1212', '홍길동', 'hong@gmail.com', sysdate);
INSERT INTO t_member
VALUES('lee', '1212', '이순신', 'lee@test.com', sysdate);
INSERT INTO t_member
VALUES('kim', '1212', '김유신', 'kim@jweb.com', sysdate);
commit; -- SQL Developer에서 테이블에 회원 정보를 추가한 후 반드시 커밋(commit)을 해줘야 영구적으로 반영이 된다.

SELECT * FROM T_MEMBER;

5. 커밋이 완료되었다는 메시지와 함께 select 문으로 조회시 회원 정보가 표시된다.

6. 이클립스에서 만든 프로젝트에서 회원 정보를 조회해 보자.
새 프로젝트 pro07을 생성한 다음 오라클 데이터베이스와 연동하는 데 필요한 드라이버인 ojdbc6.jar를 프로젝트의 /WebContent/WEB-INF/lib 폴더에 복사하여 붙여 넣는다.

7. sec01.ex01 패키지를 만들고 회원 조회와 관련된 자바 클래스 파일인 MemberDao, MemberServlet, MemberVO 클래스를 각각 생성한다.

8. 브라우저의 요청을 받는 MemberServlet 클래스를 다음과 같이 작성한다.

package sec01.ex01;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Date;
import java.util.List;

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("/member")
public class MemberServlet extends HttpServlet {
	
	protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
		
      response.setContentType("text/html;charset=utf-8");
      PrintWriter out = response.getWriter();	
      MemberDAO dao = new MemberDAO(); // SQL문으로 조회할 MemberDAO 객체를 생성한다.
      List<MemberVO> list = dao.listMembers(); // listMembers() 메서드로 회원 정보를 조회한다.
	
      out.print("<html><body>");
      out.print("<table  border=1><tr align='center' bgcolor='lightgreen'>");
      out.print("<td>아이디</td><td>비밀번호</td><td>이름</td><td>이메일</td><td>가입일</td></tr>");
     
     for (int i=0; i<list.size();i++){
		MemberVO memberVO = (MemberVO) list.get(i);
		String id = memberVO.getId();
		String pwd = memberVO.getPwd();
		String name = memberVO.getName();
		String email = memberVO.getEmail();
		Date joinDate = memberVO.getJoinDate();
		out.print("<tr><td>"+id+"</td><td>"+
			                pwd+"</td><td>"+
			                name+"</td><td>"+
			                email+"</td><td>"+
			                joinDate+"</td></tr>");		
      } // 조회한 회원 정보를 for문과 <tr> 태그를 이용해 리스트로 출력한다.
      out.print("</table></body></html>");
   }
	
}

9. MemberDAO 클래스를 다음과 같이 작성한다. 회원 정보 조회 SQL 문을 실행하여 조회한 레코드들의 컬럼 값을 다시 MemberVO 객체의 속성에 설정한 다음 ArrayList에 저장하고 호출한 곳으로 반환한다.

package sec01.ex01;

import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

public class MemberDAO {
	private static final String driver = "oracle.jdbc.driver.OracleDriver";
	private static final String url = "jdbc:oracle:thin:@localhost:1521:XE";
	private static final String user = "pc_09";
	private static final String pwd = "java";
	private Connection con;
	private Statement stmt;

	public List<MemberVO> listMembers() {
		List<MemberVO> list = new ArrayList<MemberVO>();
		try {
			connDB(); // 네 가지 정보로 데이터베이스를 연결한다.
			String query = "select * from t_member ";
			System.out.println(query);
			ResultSet rs = stmt.executeQuery(query); // SQL 문으로 회원 정보를 조회한다.
			while (rs.next()) {
				String id = rs.getString("id");
				String pwd = rs.getString("pwd");
				String name = rs.getString("name");
				String email = rs.getString("email");
				Date joinDate = rs.getDate("joinDate"); // 조회한 레코드의 각 컬럼 값을 받아온다.
				MemberVO vo = new MemberVO();
				vo.setId(id);
				vo.setPwd(pwd);
				vo.setName(name);
				vo.setEmail(email);
				vo.setJoinDate(joinDate); // 각 컬럼 값을 다시 MemberVO 객체의 속성에 설정한다.
				list.add(vo); // 설정된 MemberVO 객체를 다시 ArrayList에 저장한다.
			}
			rs.close();
			stmt.close();
			con.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return list; // 조회한 레코드의 개수만큼 MemberVO 객체를 저장한 ArrayList를 반환한다.
	}

	private void connDB() {
		try {
			Class.forName(driver);
			System.out.println("Oracle 드라이버 로딩 성공");
			con = DriverManager.getConnection(url, user, pwd);
			System.out.println("Connection 생성 성공");
			stmt = con.createStatement();
			System.out.println("Statement 생성 성공");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

10. MemberVO 클래스를 다음과 같이 작성한다. 이는 값을 전달하는 데 사용되는 VO(Value Object) 클래스이다. 테이블에서 조회한 레코드의 컬럼 값을 속성에 저장해야 하므로 컬럼 이름과 동일한 자료형과 이름으로 속성을 선언하고 getter/setter를 각각 생성한다.

package sec01.ex01;

import java.sql.Date;

public class MemberVO {
	private String id;
	private String pwd;
	private String name;
	private String email;
	private Date joinDate; // t_member 테이블의 컬럼 이름과 동일한 자료형과 이름으로 속성들을 선언한다.
	
	public MemberVO() {
		System.out.println("MemberVO 생성자 호출");
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getPwd() {
		return pwd;
	}

	public void setPwd(String pwd) {
		this.pwd = pwd;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public Date getJoinDate() {
		return joinDate;
	}

	public void setJoinDate(Date joinDate) {
		this.joinDate = joinDate;
	} // getter/setter를 생성한다.
	
}

11. http://localhost:8090/pro07/member로 요청하여 실행 결과를 확인한다. 회원 정보가 웹 브라우저로 출력되는 것을 확인할 수 있다.


(2) PreparedStatement를 이용한 회원 정보 실습

* 앞에서는 회원 정보를 조회하기 위해 MemberDAO에서 Statement 인터페이스를 이용하여 데이터베이스와 연동했다. 그런데 Statement를 이용해서 데이터베이스와 연동할 경우에는 연동할 때마다 DBMS에서 다시 SQL문을 컴파일해야 하므로 속도가 느리다는 단점이 있다.

* 이럴 경우 PreparedStatement 인터페이스를 사용하면 SQL문을 미리 컴파일해서 재사용하므로 Statement 인터페이스보다 훨씬 빠르게 데이터베이스 작업을 수행할 수 있다. 따라서 데이터베이스와 연동할 때 또는 빠른 반복 처리가 필요할 때는 PreparedStatement 인터페이스를 사용해야 한다.

* PreparedStatement 인터페이스의 특징은 다음과 같다.

- PreparedStatement 인터페이스는 Statement 인터페이스를 상속하므로 
  지금까지 사용한 메서드를 그대로 사용한다.
  
- Statement 인터페이스가 DBMS에 전달하는 SQL문은 단순한 문자열이므로
  DBMS는 이 문자열을 DBMS가 이해할 수 있도록 컴파일하고 실행한다.
  반면에 PreparedStatement 인터페이스는 컴파일된 SQL문을 DBMS에 전달하여 성능을 향상시킨다.
  
- PreparedStatement 인터페이스에서 실행하려는 SQL문에 "?"를 넣을 수 있다.
  따라서 "?"의 값만 바꾸어 손쉽게 설정할 수 있어 Statement보다 SQL문 작성하기가 더 간단하다.

그럼 지금부터 PreparedStatement 를 이용해 회원 정보를 조회하는 예제를 실습해 보자.

1. sec01.ex02 패키지를 만든 후 MemberServlet.java와 MemberVO.java는 기존의 것을 복사하여 붙여 넣는다.

2. PreparedStatement 를 이용해 데이터베이스와 연동하는 MemberDAO 클래스를 작성한다.

package sec01.ex02;

import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

public class MemberDAO {
	
	private static final String driver = "oracle.jdbc.driver.OracleDriver";
	private static final String url = "jdbc:oracle:thin:@localhost:1521:XE";
	private static final String user = "pc_09";
	private static final String pwd = "java";
	
	private Connection con;
	private PreparedStatement pstmt;

	public List listMembers() {
		List list = new ArrayList();
		try {
			connDB();
			String query = "select * from t_member ";
			System.out.println("prepareStatememt: " + query);
			pstmt = con.prepareStatement(query); // prepareStatement() 메서드에 SQL문을 전달해서 prepareStatement 객체를 생성한다.
			ResultSet rs = pstmt.executeQuery(); // executeQuery() 메서드를 호출해 미리 설정한 SQL문을 실행한다.
			while (rs.next()) {
				String id = rs.getString("id");
				String pwd = rs.getString("pwd");
				String name = rs.getString("name");
				String email = rs.getString("email");
				Date joinDate = rs.getDate("joinDate");
				MemberVO vo = new MemberVO();
				vo.setId(id);
				vo.setPwd(pwd);
				vo.setName(name);
				vo.setEmail(email);
				vo.setJoinDate(joinDate);
				list.add(vo);
			}
			rs.close();
			pstmt.close();
			con.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return list;
	}

	private void connDB() {
		try {
			Class.forName(driver);
			System.out.println("Oracle 드라이버 로딩 성공");
			con = DriverManager.getConnection(url, user, pwd);
			System.out.println("Connection 생성 성공");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

3. http://localhost:8090/pro07/member 로 요청해서 실행 결과를 확인한다. 눈으로 보면 Statement를 사용했을 때와 결과는 같다. 하지만 데이터베이스와 연동할 경우 수행 속도가 좀 더 빠르다는 차이가 있다.


3. DataSource 이용해 데이터베이스 연동하기

* 앞에서 회원 테이블에서 회원 정보를 조회하는 과정을 보았다. 이러한 데이터베이스 연동 과정은 웹 애플리케이션이 필요할 때마다 데이터베이스에 연결하여 작업하는 방식이다. 그런데 이런 식으로 필요할 때마다 연동해서 작업할 경우 발생하는 문제가 하나 있다. 바로 데이터베이스 연결에 시간이 많이 걸린다는 것이다.

* 특히 온라인 쇼핑몰의 경우 동시에 수십 명, 많게는 수백 명까지 접속해서 상품 조회, 주문하기 등의 기능을 사용하는데 앞의 방법처럼 데이터베이스와 연동해 작업해야 한다면 너무 비효율적이다.

* 이 문제를 해결하기 위해 현재는 웹 애플리케이션이 실행됨과 동시에 연동할 데이터베이스와의 연결을 미리 설정해 둔다. 그리고 필요할 때마다 미리 연결해 놓은 상태를 이용해 빠르게 데이터베이스와 연동하여 작업을 한다. 이렇게 미리 데이터베이스와 연결시킨 상태를 유지하는 기술을 커넥션풀(ConnectionPoll)이라고 부른다.

<커넥션풀 동작 과정>

* 톰캣 컨테이너에서 제공하는 커넥션풀의 동작 과정을 다음과 같이 살펴보자.

1. 톰캣 컨테이너를 실행한 후 응용 프로그램을 실행한다.

2. 톰캣 컨테이너 실행 시 ConnectionPool 객체를 생성한다.

3. 생성된 커넥션 객체는 DBMS와 연결한다.

4. 데이터베이스와의 연동작업이 필요할 경우 응용프로그램은 ConnectionPool에서 제공하는 메서드를 호출하여 연동한다.

톰캣 컨테이너는 자체적으로 ConnectionPool 기능을 제공한다. 톰캣 실행 시 톰캣은 설정 파일에 설정된 데이터베이스 정보를 이용해 미리 데이터베이스와 연결하여 ConnectionPool 객체를 생성한 후 애플리케이션이 데이터베이스와 연동할 일이 생기면 ConnectionPool 객체의 메서드를 호출해 빠르게 연동하여 작업한다.

<JNDI>

* 실제 웹 애플리케이션에서 ConnectionPool 객체를 구현할 떄는 Java SE 에서 제공하는 javax.sql.DataSource 클래스를 이용한다. 그리고 웹 애플리케이션 실행 시 톰캣이 만들어 놓은 ConnectionPool 객체에 접근할 때는 JNDI를 이용한다.

* JNDI(Java Naming and Directory Interface)란 필요한 자원을 키/값(Key/value) 쌍으로 저장한 후 필요할 때 키를 이용해 값을 얻는 방법이다. 즉, 미리 접근할 자원에 키를 지정한 후 애플리케이션이 실행 중일 때 이 키를 이용해 접근해서 작업을 하는 것이다.

* JNDI 사용 예는 다음과 같다.

- 웹 브라우저에서 name/value 쌍으로 전송한 후 서블릿에서 getParameter(name)으로 값을 가져올 때

- 해시맵(HashMap)이나 해시테이블(HashTable)에 키/값으로 저장한 후 키를 이용해 값을 가져올 때

- 웹 브라우저에서 도메인 네임으로 DNS 서버에 요청할 경우 도메인 네임에 대한 IP 주소를 가져올 때

* 톰캣 컨테이너가 ConnectionPool 객체를 생성하면 이 객체에 대한 JNDI 이름(key)을 미리 설정해 놓는다. 그러면 웹 애플리케이션에서 데이터베이스와 연동 작업을 할 때 이 JNDI 이름(key)으로 접근하여 작업한다.

<톰캣의 DataSource 설정 및 사용 방법>

* 톰캣 ConnectionPool 설정 과정

* 실제 톰캣에서 ConnectionPool 기능을 사용하려면 이 기능을 제공하는 DBCP 라이브러리를 따로 내려 받아야 한다. 이 라이브러리 파일은 jsr 압축 파일 형태로 제공되며, 다음 링크에서 tomcat-dbcp-7.0.30.zip 파일을 내려 받은 후 압축을 풀면 된다.

- http://www.java2s.com/Code/Jar/t/Downloadtomcatdbcp7030jar/html 


<이클립스에서 톰캣 DataSource 설정>

* JDBC 드라이버와 ConnectionPool 관련 jar 파일 및 이클립스에서 생성한 톰캣 서버의 설정 파일인 context.xml의 위치를 알 수 있다.

* context.xml 파일을 보면 <Resource> 태그를 이용해 톰캣 실행 시 연결할 데이터베이스를 설정하는 것을 알 수 있다.

* 자바 클래스에서 다음과 같이 name 속성의 jdbc/oracle로 DataSource에 접근한다.

<?xml version="1.0" encoding="UTF-8"?>
<!--
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
--><!-- The contents of this file will be loaded for each web application --><Context>

    <!-- Default set of monitored resources. If one of these changes, the    -->
    <!-- web application will be reloaded.                                   -->
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <WatchedResource>WEB-INF/tomcat-web.xml</WatchedResource>
    <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>

    <!-- Uncomment this to disable session persistence across Tomcat restarts -->
    <!--
    <Manager pathname="" />
    -->
    
    <!-- 
    	name의 jdbc/oracle로 Datasource에 접근한다.
    	데이터베이스를 연결하는 데 필요한 네 가지 값(driverClassName, url, username, password)을 설정한다.
    -->
    <Resource 
    	name="jdbc/oracle" 
    	auth="Container" 
    	type="javax.sql.DataSource" 
    	driverClassName="oracle.jdbc.OracleDriver" 
    	url="jdbc:oracle:thin:@localhost:1521:XE" 
    	username="pc_09" 
    	password="java" 
    	maxActive="50" 
    	maxWait="-1" />
    
</Context>

* 오라클 데이터베이스(정확히는 오라클 DBMS)를 연결할 때 다른 속성들은 고정적으로 사용하며, 프로그래머가 주로 설정하는 정보는 driverClassName, url, username, password만 변경해서 설정한다.

* ConnectionPool로 연결할 데이터베이스 속성

속성 설명
name DataSource에 대한 JNDI 이름
auth 인증 주체
driverClassName 연결할 데이터베이스 종류에 따른 드라이버 클래스 이름
factory 연결할 데이터베이스 종류에 따른 ConnectionPool 생성 클래스 이름
maxActive 동시에 최대로 데이터베이스에 연결할 수 있는 Connection 수
maxIdle 동시에 idle 상태로 대기할 수 있는 최대 수
maxWait 새로운 연결이 생길 때까지 기다릴 수 있는 최대 시간
user 데이터베이스 접속 ID
password 데이터베이스 접속 비밀번호
type 데이터베이스 종류별 DataSource
url 접속할 데이터베이스 주소와 포트 번호 및 SID

<톰캣의 DataSource로 연동해 회원 정보 조회 실습>

* 이제 설정을 마쳤으니 클래스를 만들어 연동해 보자.

1. sec02.ex01 패키지를 만들고 앞에서 사용한 MemberDAO, MemberServlet, MemberVO 클래스를 복사하여 붙여 넣는다.

2. 복사한 MemberServlet 클래스의 서블릿 매핑 이름을 /member2로 변경한다.

3. DataSource를 이용해 데이터베이스와 연동하는 MemberDAO 클래스를 다음과 같이 수정한다. 먼저 앞에서 데이터베이스와 연동할 때 사용한 connDB() 메서드는 주석 처리한다. 그리고 생성자에서 톰캣 실행 시 톰캣에서 미리 생성한 DataSource를 name값인 jdbc/oracle을 이용해 미리 받아온다. 마지막으로 서블릿에서 listMember() 메서드를 호출하면 getConnection() 메서드를 호출하여 DataSource에 접근한 후 데이터베이스와의 연동 작업을 수행한다.

package sec02.ex01;

import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

public class MemberDAO {
	/*
	private static final String driver = "oracle.jdbc.driver.OracleDriver";
	private static final String url = "jdbc:oracle:thin:@localhost:1521:XE";
	private static final String user = "pc_21";
	private static final String pwd = "java";
	*/ // 더 이상 사용되지 않으므로 주석처리 한다.
	
	private Connection con;
	private PreparedStatement pstmt;
	private DataSource dataFactory;
	
	public MemberDAO() {
		try {
			Context ctx = new InitialContext(); 
			Context envContext = (Context) ctx.lookup("java:/comp/env"); // JNDI에 접근하기 위해 기본 경로(java:/comp/env)를 지정한다.
			dataFactory = (DataSource) envContext.lookup("jdbc/oracle"); // 톰캣 context.xml에 설정한 name 값인 jdbc/oracle을 이용해 톰캣이 미리 연결한 DataSource를 받아 온다.
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public List listMembers() {
		List list = new ArrayList();
		try {
			//connDB();
			con=dataFactory.getConnection(); // DataSource를 이용해 데이터베이스에 연결한다.
			String query = "select * from t_member ";
			System.out.println("prepareStatememt: " + query);
			pstmt = con.prepareStatement(query);
			ResultSet rs = pstmt.executeQuery();
			while (rs.next()) {
				String id = rs.getString("id");
				String pwd = rs.getString("pwd");
				String name = rs.getString("name");
				String email = rs.getString("email");
				Date joinDate = rs.getDate("joinDate");
				MemberVO vo = new MemberVO();
				vo.setId(id);
				vo.setPwd(pwd);
				vo.setName(name);
				vo.setEmail(email);
				vo.setJoinDate(joinDate);
				list.add(vo);
			}
			rs.close();
			pstmt.close();
			con.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return list;
	}

	/*// DAO에서 직접 연결하는 기능은 주석 처리한다.
	private void connDB() {
		try {
			Class.forName(driver);
			System.out.println("Oracle 드라이버 로딩 성공");
			con = DriverManager.getConnection(url, user, pwd);
			System.out.println("Connection 생성 성공");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	*/
	
}

4. http://localhost:8090/pro07/member2 로 요청한다. 결과는 앞에서 실습한 것과 같지만 이번에는 커넥션풀을 이용해서 데이터베이스와 연동했다는 점에서 차이가 있다.


4. DataSource 이용해 회원 정보 등록하기

* 이번에는 커넥션을 이용해 새 회원을 등록해 보자.

1. sec02.ex02 패키지를 만들고 MemberVO.java를 복사하여 붙여 넣는다.

2. 회원 가입창을 작성하기 위해 다음과 같이 memberForm.html을 작성한다. <hidden> 태그를 이용해 회원 가입창에서 새 회원 등록 요청을 서블릿에 전달한다.

<!DOCTYPE html>
<html lang="ko">
    <head>
        <meta charset="UTF-8" />
        <title>회원 가입창</title>
        <script type="text/javascript">
            function fn_sendMember() {
                var frmMember = document.frmMember;
                var id = frmMember.id.value;
                var pwd = frmMember.pwd.value;
                var name = frmMember.name.value;
                var email = frmMember.email.value; // 자바스크립트에서 <form> 태그의 name으로 접근해 입력한 값들을 얻는다.
                if (id.length == 0 || id == "") {
                    alert("아이디는 필수입니다.");
                } else if (pwd.length == 0 || pwd == "") {
                    alert("비밀번호는 필수입니다.");
                } else if (name.length == 0 || name == "") {
                    alert("이름은 필수입니다.");
                } else if (email.length == 0 || email == "") {
                    alert("이메일은 필수입니다.");
                } else {
                    frmMember.method = "post"; // 전송 방법을 post로 지정한다.
                    frmMember.action = "member3"; // 서블릿 매핑 이름을 member3로 지정한다.
                    frmMember.submit(); // 서블릿으로 전송한다.
                }
            }
        </script>
    </head>

    <body>
        <form name="frmMember">
            <table>
                <th>회원 가입창</th>
                <tr>
                    <td>아이디</td>
                    <td><input type="text" name="id"></td> <!-- 입력한 ID를 서블릿으로 전송한다. -->
                </tr>
                <tr>
                    <td>비밀번호</td>
                    <td><input type="password" name="pwd"></td> <!-- 입력한 비밀번호를 서블릿으로 전송한다. -->
                </tr>
                <tr>
                    <td>이름</td>
                    <td><input type="text" name="name"></td> <!-- 입력한 이름을 서블릿으로 전송한다. -->
                </tr>
                <tr>
                    <td>이메일</td>
                    <td><input type="text" name="email"></td> <!-- 입력한 이메일을 서블릿으로 전송한다. -->
                </tr>
            </table>
            <input type="button" value="가입하기" onclick="fn_sendMember()">
            <input type="reset" value="다시입력">
            <input type="hidden" name="command" value="addMember" /> <!-- <hidden> 태그를 이용해 서블릿에게 회원 등록임을 알린다. -->
        </form>
    </body>
</html>

3. MemberServlet 클래스를 다음과 같이 작성한다. command 값을 먼저 받아와 addMember이면 같이 전송된 회원 정보를 받아 온다. 회원 정보를 MemberVO 객체에 설정한 후 MemberDAO의 메서드로 전달해 SQL문을 이용하여 테이블에 추가한다.

package sec02.ex02;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Date;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;

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("/member3")
public class MemberServlet extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doHandle(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doHandle(request, response);
	}

	private void doHandle(HttpServletRequest request,HttpServletResponse response)  throws ServletException, IOException {
      request.setCharacterEncoding("utf-8");
      response.setContentType("text/html;charset=utf-8");
      MemberDAO dao=new MemberDAO();
      PrintWriter out=response.getWriter();
      String command=request.getParameter("command"); // command 값을 받아온다.
      
      if(command!= null && command.equals("addMember")){ // 회원 가입창에서 전송된 command가 addMember이면 전송된 값들을 받아온다.
		 String _id=request.getParameter("id");
		 String _pwd=request.getParameter("pwd");
		 String _name=request.getParameter("name");
		 String _email=request.getParameter("email");
		 
		 MemberVO vo=new MemberVO();
		 vo.setId(_id);
		 vo.setPwd(_pwd);
		 vo.setName(_name);
		 vo.setEmail(_email);
	     dao.addMember(vo); // 회원 가입창에서 전송된 값들을 얻어와 MemberVO 객체에 저장한 후 SQL문을 이용해 전달한다.
      }else if(command!= null && command.equals("delMember")) {
    	  String id = request.getParameter("id");
    	  dao.delMember(id);
      }
       List list=dao.listMembers();
     out.print("<html><body>");
     out.print("<table border=1><tr align='center' bgcolor='lightgreen'>");
     out.print("<td>아이디</td><td>비밀번호</td><td>이름</td><td>이메일</td><td>가입일</td><td >삭제</td></tr>");
    
     for (int i=0; i<list.size();i++){
 		MemberVO memberVO=(MemberVO) list.get(i);
 		String id=memberVO.getId();
 		String pwd = memberVO.getPwd();
 		String name = memberVO.getName();
 		String email =memberVO.getEmail();
 		Date joinDate = memberVO.getJoinDate();
 		out.print("<tr><td>"+id+"</td><td>"
 			                +pwd+"</td><td>"
 			                +name+"</td><td>"
 			                +email+"</td><td>"
 			                +joinDate+"</td><td>"
 		                    +"<a href='/pro07/member3?command=delMember&id="+id+"'>삭제 </a></td></tr>");

 	 }
 	 out.print("</table></body></html>");
     out.print("<a href='/pro07/memberForm.html'>새 회원 등록하기</a"); // 클릭하면 다시 회원가입 창으로 이동한다.
   }
}
* PrepareStatement에서 insert문 사용하는 방법

1. PrepareStatement의 insert문은 회원 정보를 저장하기 위해 ?(물음표)를 사용한다.

2. ?는 id, pwd, name, age에 순서대로 대응한다.

3. 각 ?에 대응하는 값을 지정하기 위해 PrepareStatement의 setter를 이용한다.

4. setter() 메서드의 첫 번째 인자는 '?'의 순서를 지정한다.

5. ?은 1부터 시작한다(오라클의 index는 1부터 시작).

6. insert, delete, update 문은 executeUpdate() 메서드를 호출한다.

4. MemberDAO 클래스를 다음과 같이 수정한다.

package sec02.ex02;

import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

public class MemberDAO {
	private Connection con;
	private PreparedStatement pstmt;
	private DataSource dataFactory;

	public MemberDAO() {
		try {
			Context ctx = new InitialContext();
			Context envContext = (Context) ctx.lookup("java:/comp/env");
			dataFactory = (DataSource) envContext.lookup("jdbc/oracle");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public List<MemberVO> listMembers() {
		List<MemberVO> list = new ArrayList<MemberVO>();
		try {
			con = dataFactory.getConnection();
			String query = "select * from t_member ";
			System.out.println("prepareStatememt: " + query);
			pstmt = con.prepareStatement(query);
			ResultSet rs = pstmt.executeQuery();
			while (rs.next()) {
				String id = rs.getString("id");
				String pwd = rs.getString("pwd");
				String name = rs.getString("name");
				String email = rs.getString("email");
				Date joinDate = rs.getDate("joinDate");
				MemberVO vo = new MemberVO();
				vo.setId(id);
				vo.setPwd(pwd);
				vo.setName(name);
				vo.setEmail(email);
				vo.setJoinDate(joinDate);
				list.add(vo);
			}
			rs.close();
			pstmt.close();
			con.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return list;
	}

	public void addMember(MemberVO memberVO) {
		try {
			con = dataFactory.getConnection(); // DataSource를 이용해 데이터베이스와 연결한다.
			String id = memberVO.getId();
			String pwd = memberVO.getPwd();
			String name = memberVO.getName();
			String email = memberVO.getEmail(); // 테이블에 저장할 회원 정보를 받아온다.
			String query = "insert into t_member";
			query += " (id,pwd,name,email)";
			query += " values(?,?,?,?)"; // insert문을 문자열로 만든다.
			System.out.println("prepareStatememt: " + query);
			pstmt = con.prepareStatement(query);
			pstmt.setString(1, id);
			pstmt.setString(2, pwd);
			pstmt.setString(3, name);
			pstmt.setString(4, email); // insert문의 각 "?"에 순서대로 회원 정보를 세팅한다.
			pstmt.executeUpdate(); // 회원 정보를 테이블에 추가한다.
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void delMember(String id) {
		try {
			con = dataFactory.getConnection();
			String query = "delete from t_member" + " where id=?";
			System.out.println("prepareStatememt:" + query);
			pstmt = con.prepareStatement(query);
			pstmt.setString(1, id);
			pstmt.executeUpdate();
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

5. http://localhost:8090/pro07/memberForm.html 로 요청하여 회원 정보를 입력한 후 가입하기를 클릭한다.

6. 다음과 같이 회원 정보가 출력된다.

- http://localhost:8090/pro07/member3


5. 회원 정보 삭제하기

* 이번에는 회원 정보를 삭제하는 기능을 구현해 보자.

1. MemberServlet 클래스를 다음과 같이 수정한다. <a> 태그를 이용해 회원 정보를 삭제할 수 있는 링크를 추가한다. 브라우저에서 삭제 요청도 전송하므로 if문에 else if문을 추가하여 삭제 기능을 수행한다.

package sec02.ex02;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Date;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;

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("/member3")
public class MemberServlet extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doHandle(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doHandle(request, response);
	}

	private void doHandle(HttpServletRequest request,HttpServletResponse response)  throws ServletException, IOException {
      request.setCharacterEncoding("utf-8");
      response.setContentType("text/html;charset=utf-8");
      MemberDAO dao=new MemberDAO();
      PrintWriter out=response.getWriter();
      String command=request.getParameter("command"); 
      
      if(command!= null && command.equals("addMember")){ 
		 String _id=request.getParameter("id");
		 String _pwd=request.getParameter("pwd");
		 String _name=request.getParameter("name");
		 String _email=request.getParameter("email");
		 
		 MemberVO vo=new MemberVO();
		 vo.setId(_id);
		 vo.setPwd(_pwd);
		 vo.setName(_name);
		 vo.setEmail(_email);
	     dao.addMember(vo); 
      }else if(command!= null && command.equals("delMember")) {
    	  String id = request.getParameter("id");
    	  dao.delMember(id); // command 값이 delMember인 경우 ID를 가져와 SQL 문으로 전달해서 삭제한다.
      }
       List list=dao.listMembers();
     out.print("<html><body>");
     out.print("<table border=1><tr align='center' bgcolor='lightgreen'>");
     out.print("<td>아이디</td><td>비밀번호</td><td>이름</td><td>이메일</td><td>가입일</td><td >삭제</td></tr>");
    
     for (int i=0; i<list.size();i++){
 		MemberVO memberVO=(MemberVO) list.get(i);
 		String id=memberVO.getId();
 		String pwd = memberVO.getPwd();
 		String name = memberVO.getName();
 		String email =memberVO.getEmail();
 		Date joinDate = memberVO.getJoinDate();
 		out.print("<tr><td>"+id+"</td><td>"
 			                +pwd+"</td><td>"
 			                +name+"</td><td>"
 			                +email+"</td><td>"
 			                +joinDate+"</td><td>"
 		                    +"<a href='/pro07/member3?command=delMember&id="+id+"'>삭제 </a></td></tr>"); // 삭제를 클릭하면 command 값과 회원 ID를 서블릿으로 전송한다.

 	 }
 	 out.print("</table></body></html>");
     out.print("<a href='/pro07/memberForm.html'>새 회원 등록하기</a");
   }
}

2. MemberDAO 클래스를 다음과 같이 수정한다. delete 문의 첫 번째 '?'에 전달된 ID를 인자로 executeUpdate() 메서드를 호출합니다.

package sec02.ex02;

import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

public class MemberDAO {
	private Connection con;
	private PreparedStatement pstmt;
	private DataSource dataFactory;

	public MemberDAO() {
		try {
			Context ctx = new InitialContext();
			Context envContext = (Context) ctx.lookup("java:/comp/env");
			dataFactory = (DataSource) envContext.lookup("jdbc/oracle");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public List<MemberVO> listMembers() {
		List<MemberVO> list = new ArrayList<MemberVO>();
		try {
			con = dataFactory.getConnection();
			String query = "select * from t_member ";
			System.out.println("prepareStatememt: " + query);
			pstmt = con.prepareStatement(query);
			ResultSet rs = pstmt.executeQuery();
			while (rs.next()) {
				String id = rs.getString("id");
				String pwd = rs.getString("pwd");
				String name = rs.getString("name");
				String email = rs.getString("email");
				Date joinDate = rs.getDate("joinDate");
				MemberVO vo = new MemberVO();
				vo.setId(id);
				vo.setPwd(pwd);
				vo.setName(name);
				vo.setEmail(email);
				vo.setJoinDate(joinDate);
				list.add(vo);
			}
			rs.close();
			pstmt.close();
			con.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return list;
	}

	public void addMember(MemberVO memberVO) {
		try {
			con = dataFactory.getConnection(); // DataSource를 이용해 데이터베이스와 연결한다.
			String id = memberVO.getId();
			String pwd = memberVO.getPwd();
			String name = memberVO.getName();
			String email = memberVO.getEmail(); // 테이블에 저장할 회원 정보를 받아온다.
			String query = "insert into t_member";
			query += " (id,pwd,name,email)";
			query += " values(?,?,?,?)"; // insert문을 문자열로 만든다.
			System.out.println("prepareStatememt: " + query);
			pstmt = con.prepareStatement(query);
			pstmt.setString(1, id);
			pstmt.setString(2, pwd);
			pstmt.setString(3, name);
			pstmt.setString(4, email); // insert문의 각 "?"에 순서대로 회원 정보를 세팅한다.
			pstmt.executeUpdate(); // 회원 정보를 테이블에 추가한다.
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void delMember(String id) {
		try {
			con = dataFactory.getConnection();
			String query = "delete from t_member" + " where id=?"; // delete문을 문자열로 만든다.
			System.out.println("prepareStatememt:" + query);
			pstmt = con.prepareStatement(query);
			pstmt.setString(1, id); // 첫 번째 '?'에 전달된 ID를 인자로 넣는다.
			pstmt.executeUpdate(); // delete문을 실행해 테이블에서 해당 ID의 회원 정보를 삭제한다.
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

3. http://localhost:8090/pro07/member3 로 요청한 후 삭제를 클릭한다.

4. 회원 정보를 삭제한 후 남은 회원 정보가 다시 출력된다.

* 지금까지 서블릿의 세 가지 기능 중 마지막 기능인 비즈니스 로직 처리 작업을 알아보았다. 일반적으로 서블릿에서 데이터베이스와 연동하는 작업은 크게 CRUD(Create, Read, Update, Delete) 작업을 나눌 수 있다. 회원 기능에서 CRUD 중 update 작업은 뒤에서 JSP를 배운 후 적용해 보자.