JSP 5. 서블릿 비즈니스 로직 처리 (Servlet)

업데이트:
7 분 소요

서블릿 비즈니스 로직 처리

alt

서블릿의 비즈니스 처리 작업

  • 서블릿이 클라이언트로 부터 요청을 받으면 그 요청에 대해 작업을 수행하는 것.
  • 웹 프로그램에서 대부분의 비즈니스 처리 작업은 DB 연동 관련 작업,
    그 외에 다른 서버와 연동해서 데이터를 얻는 작업도 수행.
  • 서블릿의 가장 핵심 기능

1) 예

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

2) 처리 과정

  • 클라이언트로부터 요청 받음
  • DB 연동과 같은 비즈니스 로직 처리
  • 처리 결과를 클라이언트에게 돌려줌.

데이터 엑세스 공통 개념

  • DAO 패턴을 적용하여 비즈니스 로직과 데이터 액세스 로직을 분리하는 것이 원칙임

1) DAO (Data Access Object) 패턴

  • 서비스 계층에 영향을 주지 않고 데이터 액세스 기술 파트 전담
  • 비즈니스 로직을 가진 엔터프라이즈 애플리케이션이라면 데이터 엑세스 계층을
    DAO 패턴으로 분리함.

2) DataSource 이용해 회원 정보 등록하기

  • PreparedStatement를 이용한 insert문 사용하는 방법
  • ?는 id, PWD, NAME, EMAIL에 순서대로 대응한다.
  • 각 ?에 대응하는 값을 지정하기 PreparedStatement의 setter를 이용함
  • setter()메서드의 첫번째 인자는 ‘?’의 순서를 지정함
  • ?은 1부터 시작함
  • insert, delete,update문은 executeUpdate() 메서드를 호출함.

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

  • Servlet, DAO(Data Access Object), VO(Value Object), DB
  • 서블릿으로 회원 정보 테이블의 정보 조회

PreparedStatement

  • Statement 상속하므로 지금까지 사용한 메서드를 그대로 사용함.
  • 성능을 향상시킴
  • SQL문을 ‘?’을 넣을 수 있으므로 ‘?’의 값만 바꾸어 손쉽게 설정 가능하다.

순서

  1. 웹 브라우저로 서블릿에게 회원 정보 요청
  2. MemberServlet은 요청을 받음
  3. 1) MemberDAO 객체를 생성 후 listMembers() 메서드를 호출함
  4. lsitMembers()에서 connDB() 메서드로 데이터베이스 연결후 SQL문 실행함.
  5. 조회된 회원 정보를 MemberVO 속성에 설정한 후 ArrayList에 저장함.
  6. ArrayList의 MemberVO를 차례대로 가져와 회원정보를 HTML 태그 문자열로 만듦.
  7. 만들어진 HTML 태그를 웹 브라우저로 전송함.

1) DB Value 정리

alt

2) DB 테이블 업데이트

alt

3) MemberVO

package kr.co.ezenac.member;
/*
 *  1	ID	id	varchar2	10
	2	비밀번호	pwd	varchar2	10
	3	이름	name	varchar2	50
	4	이메일	email	varchar2	50
	5	가입일자	joinDate	date
 */

import java.sql.Date;

public class MemberVO {
	// t_member 테이블의 컬럼 이름과 동일한 자료형으로 속성들 선언 
	private String id;
	private String pwd;
	private String name;
	private String email;
	private Date joinDate;
	
	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;}	
}

MemberServlet

package kr.co.ezenac.member;

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;

/**
 * Servlet implementation class MemberServlet
 */
@WebServlet("/member")
public class MemberServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setCharacterEncoding("utf-8");
		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>");
		out.print("<body>");
		out.print("<table border=1><tr align='center' bgcolor='lightgreen'>");
		out.print("<td>아이디</td>");
		out.print("<td>비밀번호</td>");
		out.print("<td>이름</td>");
		out.print("<td>이메일</td>");
		out.print("<td>가입일</td></tr>");
		
		for(int i=0;i<list.size();i++) {
			MemberVO memberVO = list.get(i);	
			// 조회한 회원 정보를 for문 <tr> 태그 이용해서 출력함
			String id = memberVO.getId();
			String pw = memberVO.getPwd();
			String name = memberVO.getName();
			String email = memberVO.getEmail();
			Date joindate = memberVO.getJoinDate();
			out.print("<tr><td>"+id+"</td>");
			out.print("<td>"+pw+"</td>");
			out.print("<td>"+name+"</td>");
			out.print("<td>"+email+"</td>");
			out.print("<td>"+joindate+"</td></tr>");
		}
		
		out.print("</table></html>");
		out.print("</body>");
	}

}

4) MemberDAO - SELECT

package kr.co.ezenac.member;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
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 = "ezen";
	private static final String PWD = "0824";
	private Connection conn;			// 데이터베이스와 연결을 담당함
	private Statement stmt;	// (인파라미터가 없는) 정적 쿼리문 실행할 때 사용함
	private PreparedStatement pstmt;// (인파라미터가 있는) 동적 쿼리문 실행할때 사용됨
	private ResultSet rs;		// select 쿼리문의 결과를 저장할 때 사용됨.
	
	public void connDB() {
		try {
			Class.forName(DRIVER);
			System.out.println("Oracle 드라이버 로딩 성공");
			
			conn = DriverManager.getConnection(URL,USER,PWD);
			System.out.println("Connection 생성 성공");		
		}catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	// 연결 해제(자원 반납)
	public void close() {
		try {
			if(rs != null)rs.close();
			if(stmt !=null)stmt.close();
			if(pstmt !=null)pstmt.close();
			if(conn!=null)conn.close();		
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public List<MemberVO> listMembers(){
		List<MemberVO> list = new ArrayList<>();
		
		connDB();			// 네가지 정보로 DB를 연결함
		
		String query = "SELECT * FROM T_MEMBER";
		System.out.println(query);
		
		try {
			pstmt = conn.prepareStatement(query);	//prepareStatement()메서드에 SQL문을 전달해 객체생성
			
			rs = pstmt.executeQuery();		// 미리 설정한 SQL문 실행
			while(rs.next()) {
				String id = rs.getString("ID");
				String pw = 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(pw);
				vo.setName(name);
				vo.setEmail(email);
				vo.setJoinDate(joinDate);	// 각 컬럼 값을 다시 MemberVO 객체의 속성에 설정함. 
				list.add(vo);// 설정된 MemberVO 객체를 다시 ArrayList에 저장함.
			}
		close();
			
		}catch(SQLException e) {
			e.printStackTrace();
		}
		return list;// 조회한 레코드의 개수만큼 MemberVO 객체를 저장한 ArrayList를 반환함.
	}
}

5) 결과

alt

6) MemberDAO - INSERT

	public void addMember(MemberVO memberVO) {
		
		try {
				conn = dataFactory.getConnection();// DataSource을 이용해 DB 연결
				String id = memberVO.getId();
				String pwd = memberVO.getPwd();
				String name = memberVO.getName();
				String email = memberVO.getEmail();
				
				String query = "INSERT INTO T_member (ID,PWD,NAME,EMAIL) VALUES (?,?,?,?)";
				System.out.println("PrepareStatement : "+query);
				
				pstmt = conn.prepareStatement(query);

				pstmt.setString(1,id);      // insert문의 ?에 순서대로 회원정보 세팅
				pstmt.setString(2,pwd);     
				pstmt.setString(3,name);
				pstmt.setString(4,email);

				pstmt.executeUpdate();    // 회원정보 테이블의 추가됨
				close();
			
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

7) 결과

alt alt

8) MemberDAO - DELETE

	public void delMember(String id) {
		try {
			conn = dataFactory.getConnection();
			String query = "DELETE FROM T_MEMBER WHERE ID =?";//delete 문을 문자열로 만듬
			System.out.println(query);
			
			pstmt = conn.prepareStatement(query);
			pstmt.setString(1, id);// 첫번쨰 '?'에 전달된 ID를 인자로 넣음
			pstmt.executeUpdate(); 
			// delete 문실행 => 테이블에서 해당 id 회원 정보 삭제
			close();
		} catch(SQLException e) {
			e.printStackTrace();
		}
	}

9) 결과

alt alt

커넥션 풀(Connection pool)로 성능 개선

  • DataSource 이용해 DB 연동

1) 요청이 있을 때마다 DB와 새로 연결했다가 해제함

  • Connection 객체를 생성할때마다 네트워크 통신이 이루어지며 사용자인증 같은
    시간이 걸리는 작업 수반됨.
  • 빈번한 연결과 해제는 시스템 성능에 큰 영향을 미침

2) Connection pool

  • Connection 객체를 미리 생성해 풀(pool)에 넣어놓고, 요청이 있을 때 이미
    생성된 Connection 객체를 가져다 사용하는 기법임.
  • 사용이 완료된 객체는 연결을 해제하는 것이 아니라 풀에 반납하여 필요할
    때 재사용 할 수 있도록 함.

JNDI (Java Naming and Directory Interface)

  • 자바 소프트웨어에서 객체나 데이터를 전체 경로를 몰라도 이름만으로 찾아
    쓸수 있는 디렉터리 서비스임.
  • 이름과 실제 객체와의 연결은 외부의 설정 파일에서 관리함

alt

WAS의 JNDI를 통해 커넥션 풀을 사용하는 절차

  1. WAS(톰캣)가 시작할 때 server.xml나 context.xml에 설정한 대로 커넥션 풀을
    생성함.
  2. JSP, Servlet 코드에서 JNDI 서버(WAS가 제공)로 부터 데이터소스 객체를 얻어옴.
  3. 데이터소스로부터 커넥션 객체를 가져옴.
  4. DB 작업 수행 함
  5. 모든 작업이 끝나면 커넥션 객체를 풀로 반환함.

3) 커넷션 풀을 사용하면 WAS가 시작될때마다 미리 생성한 커넥션 객체를 사용

  • 웹 어플리케이션 실행 속도가 빨라짐
  • 클라이언트 동시 요청이 많아지더라도 좀 더 수월하게 응답할 수 있음.
  • 성능 향상 효과 큼
context.xml
      ...
    <!--
      <Manager pathname="" />
    -->
    
    <!--
       name : 생성할 자원(풀) 이름 
       auth : 인증 주체
    	 type : 데이터 소스로 사용할 클래스 명 
    	 driverClassName : JDBC 드라이버의 클래스명
    	 url : 접속할 데이터베이스 주소와 포트 번호 및 SID
       maxActive : 동시에 최대로 DB에 연결할 수 있는 Connection
       maxWait : 새로운 연결이 생길 때까지 기다릴 수 있는 최대 시간
   	-->
    <Resource 
    	name="jdbc/oracle" 
    	auth="Conainer" 
    	type="javax.sql.DataSource" 
    	driverClassName="oracle.jdbc.driver.OracleDriver"
    	url="jdbc:oracle:thin:@localhost:1521:XE"
    	username="ezen"
    	password="0824"
    	maxActive="50"
    	maxWait="-1"
    />
    
</Context>

Connection Pool 사용

  • MemberDAO
package kr.co.ezenac.member;

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

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

public class MemberDAO {
	
	private Connection conn;			
	private Statement stmt;				
	private PreparedStatement pstmt;	
	private ResultSet rs;	
	private DataSource dataFactory;
	
	// 연결 해제(자원 반납)
	public void close() {
		try {
			if(rs != null)rs.close();
			if(stmt !=null)stmt.close();
			if(pstmt !=null)pstmt.close();
			if(conn!=null)conn.close();		
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public MemberDAO() {
		try {
			// 커넥션 풀(DataSource) 얻기
			/* 자바의 네이밍 서비스(JNDI)에서 이름과 실제 객체를 연결해주는 개념이 Context임
			   InitialContext는 네이밍 서비스를 이용하기위한 시작점임. */
			Context ctx= new InitialContext();		// InitialContext 객체 생성
			
			// java:comp/env  <- 현재 웹 어플리케이션의 루트 디렉토리
			// 현재 웹 어플리케이션이 사용할 수 있는 모든 자원은 java:comp/env 아래에 위치함.
			Context envContext = (Context) ctx.lookup("java:comp/env");
			// java:comp/env 아래에 위치한 jdbc/oracle 자원을 얻어옴
			dataFactory = (DataSource) envContext.lookup("jdbc/oracle");
			
		} catch (NamingException e) {
			e.printStackTrace();
		}
	}
	
	public List<MemberVO> listMembers(){
		List<MemberVO> list = new ArrayList<>();
		
		String query = "SELECT * FROM T_MEMBER";
		System.out.println(query);
		
		try {
			// 커넥션 풀을 통해 연결 얻기
			// 데이터소스를 통해 풀에 생성되어 있는 연결 객체를 얻어옴
			conn = dataFactory.getConnection();
			
			pstmt = conn.prepareStatement(query);	//prepareStatement()메서드에 SQL문을 전달해 객체생성
			
			// select 쿼리문을 실행할 떄 사용함.
			// 조회한 레코드들의 집한인 Resultset 객체를 반환함
			rs = pstmt.executeQuery();		// 미리 설정한 SQL문 실행
			while(rs.next()) {
				String id = rs.getString("ID");
				String pw = 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(pw);
				vo.setName(name);
				vo.setEmail(email);
				vo.setJoinDate(joinDate);	// 각 컬럼 값을 다시 MemberVO 객체의 속성에 설정함. 
				list.add(vo);				// 설정된 MemberVO 객체를 다시 ArrayList에 저장함.
			}
		close();
			
		}catch(SQLException e) {
			e.printStackTrace();
		}
		return list;// 조회한 레코드의 개수만큼 MemberVO 객체를 저장한 ArrayList를 반환함.
	}
}

alt

댓글남기기