bdfgdfg

서블릿(Servlet) - 2 본문

웹프로그래밍/JSP_Servlet

서블릿(Servlet) - 2

marmelo12 2023. 8. 7. 21:15
반응형

서블릿의 응답과 요청 수행 API

요청 API : javax.servlet.http.HttpServletRequest 클래스

응답 API : javax.servlet.http.HttpServletResponse 클래스

 

클라이언트의 요청이 들어오면 톰캣(WAS)에서 서블릿 객체를 만들고(처음이라면), 해당 요청에 대한 모든 정보를 HttpServerletRequest에 담아서 보내주기에 우리는 그걸 가져다 쓰면 된다.

 -> 비즈니스 로직에만 집중할 수 있음.

 

HTML의 form태그를 이용해 서블릿에 요청을 해보자.

<form name+"frmLogin" method="get" action="login" encType="UTF-8">
 아이디 : <input type="text" name="user_id"><br>
 비밀번호 : <input type="password" name="user_pw"><br>
 <input type="submit" value="로그인"> <input type="reset" value="다시입력?
</form>

form태그의 action 속성은 데이터를 전송할 서블릿이나 JSP의 이름을 지정한다.

method는 HTTP 메소드를 의미.

name태그는 서버에서 데이터의 이름을 식별하기 위한 String.(Parameter) (만약 이름이 중복되면 배열에담김)

 

이제 저 html을 요청을 하고, 실제 서블릿을 호출해보자.

package sec01.ex01;

import java.io.IOException;

import javax.servlet.ServletConfig;
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 LoginServlet
 */
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

    /**
     * Default constructor. 
     */
    public LoginServlet() {
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see Servlet#init(ServletConfig)
	 */
	public void init(ServletConfig config) throws ServletException {
		// TODO Auto-generated method stub
	}

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		request.setCharacterEncoding("utf-8");
		String user_id = request.getParameter("user_id");
		String user_pw = request.getParameter("user_pw");
		
		System.out.println("아이디 : " + user_id);
		System.out.println("비밀번호 : " + user_pw);
	}
}

실제로 아이디,비번입력후 로그인버튼을 클릭하면 위와 같이 request를 잘받아오는걸 알 수 있다.

 

만약 여러개의 값을 보내야한다면 어떻게할까?

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<form name+"frmInput" method="get" action="login" encType="UTF-8">
 	아이디 : <input type="text" name="user_id"><br>
 	비밀번호 : <input type="password" name="user_pw"><br>
 	<input type="checkbox" name="subject" value="java"> 자바
 	<input type="checkbox" name="subject" value="C언어"> C언어
 	<input type="checkbox" name="subject" value="JSP"> JSP
 	<input type="checkbox" name="subject" value="안드로이드"> 안드로이드
 	<br><br>
 	<input type="submit" value="전송">
 	<input type="reset" value="초기화">
	</form>
</body>
</html>

이렇게 뜰것이고.. 아이디 비밀번호는 아무거나 입력후 체크박스에 체크를 하고 전송을 클릭해보자.

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		request.setCharacterEncoding("utf-8");
		String user_id = request.getParameter("user_id");
		String user_pw = request.getParameter("user_pw");
		
		String[] subjects = request.getParameterValues("subject");
		
		System.out.println("아이디 : " + user_id);
		System.out.println("비밀번호 : " + user_pw);
		
		for( String s : subjects )
			System.out.println(s);
	}

잘 나온다. 즉 name태그의 값이 같은것등른 getParameterValues를 통해 배열로 가져오면 편하다.

 

이런식으로도 되지만, 만약 회원가입같은 시스템을 이용하면 해당 회원의 정보마다 모든 것을 getParameter로 처리하기는 힘들다.

이렇게 전송된 데이터가 많아 일일이 name의 값을 기억하기 힘들 때 getParameterNames() 메소드를 이용!

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		request.setCharacterEncoding("utf-8");
		
		Enumeration<String> e = request.getParameterNames();
		while( e.hasMoreElements() )
		{
			String name = (String)e.nextElement();
			String[] values = request.getParameterValues(name);
			for(String str : values)
			{
				System.out.println("name 태그 : " + name + " value = " + str );
			}
		}
	}

매우 편하다! Enumeration은 Iterator의 구버전임.

 

서블릿의 응답 처리 방법

서블릿이 처리한 결과를 클라이언트에게 응답하는 객체로는 HttpServletResponse 객체를 이용한다.

그전에 응답을 줄 때 어떤 표현 형식으로 줄지를 결정해야하는데 그것이 Content-Type헤더. 그전에 MIME-TYPE을 이해해야한다.

 

MIME-TYPE

톰캣과 같은 컨테이너에서 미리 설정해놓은 데이터 종류들을 MIME-TYPE이라 한다.

 ex) text/html, text/plain, application/xml등등..

 

대충 알았으니, 이제 앞서 배운 로그인 요청에 응답을 넣어본다.

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	// TODO Auto-generated method stub
	request.setCharacterEncoding("utf-8");
	
	Map<String,String[]> mp = new HashMap();
	Enumeration<String> e = request.getParameterNames();
	while( e.hasMoreElements() )
	{
		String name = (String)e.nextElement();
		String[] values = request.getParameterValues(name);
		
		mp.put(name, values);
	}
	
	// 서블릿이 넘겨준 응답객체에 값을 세팅
	
	// html 형식에 인코딩방식은 utf-8
	response.setContentType("text/html;charset=utf-8");
	PrintWriter pw = response.getWriter();
	String data = "<html>";
	data += "<body>";
	
	for( String key : mp.keySet() )
	{
		String[] values = mp.get(key);
		
		for(String value : values)
		{
			data += ( "name :" + key + "value :" + value );
			data += "<br>";
		}
	}
	
	data += "</body>";
	data += "</html>";
	
	pw.print(data);
}

 

 

보면 IO스트림을 생성하고, 해당 스트림에 html String을 넣어주고 있는것을 알 수 잇음.

실제 결과는

 

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

비즈니스 처리작업은 클라이언트로 부터 요청을 보고 작업을 수행하는것을 의미.

 

이제 데이터베이스를 연동해보기전에 테이블을 만들고 미리 데이터를 넣어주자.

create table t_member(
    id varchar2(10) primary key,
    pwd varchar2(10),
    name varchar2(10),
    email varchar2(50),
    joinDate date default sysdate
);

insert into t_member values('hong', '1212', '홍길동', 'hong@gmail.com', sysdate);
insert into t_member values('min', '1234', '밍키', 'min@gmail.com', sysdate);
insert into t_member values('kim', '12345', '밍', 'mink@gmail.com', sysdate);

commit;

 

이제 코드를 작성해보ㅈ.

package sec01.ex01;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.util.*;

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("/members")
public class MemberServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.setContentType("text/html;charset=utf-8");
		
		//응답
		PrintWriter pw = response.getWriter();
		MemberDAO dao = new MemberDAO();
		List<MemberVO> list = null;
		try {
			list = dao.getListMembers();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		pw.print("<html><body>");
		pw.print("<table border=1><tr align='center' bgcolor='lightgreen'>");
		pw.print("<td>아이디</td><td>비밀번호</td><td>이름</td><td>이메일</td><td>가입일</td></tr>");
		
		for( MemberVO member : list)
		{
			pw.print("<tr><td>" + member.id + "</td><td>" + member.pwd + "</td><td>"
					+ member.name + "</td><td>" + member.email + "</td><td>"
					+ member.joinDate + "</td></tr>"
					);
		}
		
		pw.print("</table></body></html>");
	}

}

위 코드는 MemberServlet 클래스이며 서블릿 요청이 들어올 때 처리되는 코드.

 

package sec01.ex01;

import java.sql.*;
import java.util.*;

public class MemberDAO {
	private static final String driver = "oracle.jdbc.driver.OracleDriver";
	private static final String url = "jdbc:oracle:thin:@localhost:1521:xe";
	
	private Statement stmt;
	private Connection con;
	
	public List<MemberVO> getListMembers() throws SQLException
	{
		List<MemberVO> list = new ArrayList<MemberVO>(100);
		try
		{
			// 항상 DB에 연결을 해야한다는 점은 좋지않음
			connectDB();
		
			String query = "select * from t_member";
			System.out.println(query);
		
			// 쿼리의 반환값.
			ResultSet rs = stmt.executeQuery(query);
		
			while(rs.next())
			{
				MemberVO vo = new MemberVO();
			
				vo.id = rs.getString("id");
				vo.pwd = rs.getString("pwd");
				vo.name = rs.getString("name");
				vo.email = rs.getString("email");
				vo.joinDate = rs.getDate("joinDate");
			
				list.add(vo);
			}
			
			// 정상처리 되었다면 자원 종료.
			rs.close();
			stmt.close();
			con.close();
		}catch(Exception e)
		{
			e.printStackTrace();
		}
		
		
		return list;
	}
	
	private void connectDB()
	{
		try
		{
			// Driver Class를 로딩하면 객체가 생성되고, DriverManager에 등록.
			Class.forName(driver);
			con = DriverManager.getConnection(url,user,pwd);
			stmt = con.createStatement();
		}catch(Exception e)
		{
			e.printStackTrace();
		}
	}
}

위는 자바에서 DB랑 커넥션을 맺는 코드.

다만 매번 요청이 들어올때마다 DB에다가 연결을하고, 끊는데 이러면 속도가 너무느림. -> 나중에 커넥션풀을 써야한다.

 

package sec01.ex01;

import java.sql.Date;
public class MemberVO {
	
	// t_member 테이블의 속성들
	// 간단히 하기 위해 모두 public으로 설정..
	public String id;
	public String pwd;
	public String name;
	public String email;
	public Date joinDate;
	
	public MemberVO()
	{
		System.out.println("생성자");
	}
	
	
}

그리고 테이블에 해당하는 클래스.

 

실행해보면

 

커넥션 풀(Connection Pool)

매번 요청이 들어올때마다 DB에 연결을하고 끊는건 속도가 매우느리기에 클라이언트 응답도 지연될 수 밖에없다.

그렇기에 미리 db와 커넥션된 객체를 Pool에다 저장하고 꺼내오는 방식이 필요하다.

 

톰캣에 있는 DataSource 설정 및 사용법

1. JDBC 드라이버를 /WEB-INF/lib 폴더에 설치

2. ConnectionPool 기능 관련 jar 파일을 /WEB-INF/lib폴더에 설치

3. CATALINA_HOME/context.xml에 Connection 객체 생성 시 연결할 데이터 베이스 정보를 JNDI로 설정.

4. DAO클래스에서 데이터베이스와 연동 시 미리 설정한 JNDI라는 이름으로 데이터베이스와 연결해 작업

 

JNDI는 간단히 말해 필요한 자원을 key-value형태로 저장하는 것.

 

톰캣에서 제공하는 Connection Pool기능을 사용하기 위해 DBCP 라이브러리를 다운받아오자.

그리고 톰캣의 context설정에 해당 내용을 추가해준다.

    <Resource
		name="jdbc/oracle"
		auth="Container"
		type="javax.sql.DataSource"
		driverClassName="oracle.jdbc.OracleDriver"
		url="jdbc:oracle:thin:@localhost:1521:xe"
		username="?????"
		password="?????"
		maxActive="50"
		maxWait="-1"
	/>

 

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

package sec01.ex01;

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

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

public class MemberDAO {
	private PreparedStatement pstmt;
	private Connection con;
	private DataSource dataFactory;
	
	public MemberDAO()
	{
		try
		{
			// 미리 연결한 DataSource를 읽어온다
			InitialContext ctx = new InitialContext();
			dataFactory = (DataSource)ctx.lookup("java:/comp/env/jdbc/oracle");
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
	
	public List<MemberVO> getListMembers() throws SQLException
	{
		List<MemberVO> list = new ArrayList<MemberVO>(100);
		try
		{
			con = dataFactory.getConnection();
		
			String query = "select * from t_member";
			System.out.println(query);
		
			// prepareStatement를 이용하면 SQL문을 미리 컴파일해 재사용하다는 장점이 있음.
			pstmt = con.prepareStatement(query);
			ResultSet rs = pstmt.executeQuery();
		
			while(rs.next())
			{
				MemberVO vo = new MemberVO();
			
				vo.id = rs.getString("id");
				vo.pwd = rs.getString("pwd");
				vo.name = rs.getString("name");
				vo.email = rs.getString("email");
				vo.joinDate = rs.getDate("joinDate");
			
				list.add(vo);
			}
			
			// 정상처리 되었다면 자원 종료.
			rs.close();
			pstmt.close();
			con.close();
		}catch(Exception e)
		{
			e.printStackTrace();
		}
		
		
		return list;
	}

}

결과는 동일하게 잘 나온다.

 

DataSource를 이용한 회원정보 등록 및 삭제

DataSource는 기존것 그대로 이용하면 되고, 핵심은 등록(SQL - insert).

@WebServlet("/members3")
public class MemberServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	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 cmd = request.getParameter("command");
		
		if(cmd != null && cmd.equals("addMember"))
		{
			MemberVO vo = new MemberVO();
			vo.id = request.getParameter("id");
			vo.pwd = request.getParameter("pwd");
			vo.name = request.getParameter("name");
			vo.email = request.getParameter("email");
			
			dao.addMember(vo);
		}

	}
}

그리고 DAO클래스에는 밑의 코드가 추가된다.

public void addMember(MemberVO memberVO) {
	try
	{
		con = dataFactory.getConnection();
		
		String query = "insert into t_member";
		query += " (id,pwd,name,email) values(?,?,?,?)";
		
		pstmt = con.prepareStatement(query);
		pstmt.setString(1, memberVO.id);
		pstmt.setString(2, memberVO.pwd);
		pstmt.setString(3, memberVO.name);
		pstmt.setString(4, memberVO.email);
		pstmt.executeUpdate();
		pstmt.close();
	}catch(Exception e)
	{
		e.printStackTrace();
	}
}

잘 추가된다.

 

참고로 HTTP의 GET,POST 둘 다  request.getParameter로 가져올 수 있다.

다만 GET방식은 URI에 유저의 ID,PWD를 노출시키기에 보안에 취약. 가능하다는 점만 알고가자.

 

그다음은 delete는 위만 이해했음 간단하다.

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

 

서블릿의 포워드

서블릿끼리 또는 서블릿과 JSP를 연동해서 작업해야하는 경우가 있다.

하나의 서블릿에서 다른 서블릿이나 JSP와 연동하는 방법을 포워드(forward)라고 한다.

 

포워드의 여러가지 방법

 - redirect : HttpServletResponse 객체의 sendRedirect 메소드를 이용. 클라이언트가 재요청하는 방식

 - Refresh : HttpServletResponse 객체의 addHeader 메소드를 이용. 클라이언트가 재요청하는방식

 - location : 자바스크립트 location 객체의 href속성을 이용. 자바스크립트에서 재요청

 - dispatch : 서블릿이 직접 요청(서버 -> 서버) RequestDispatcher 클래스의 forward메소드를 이용.

  --> redirect방식은 url이 변경되지만, dispatch는 서버에서 서블릿 -> 서블릿의 전달이므로 url의 변경이 없음.

  --> ex) RequestDispatcher dis = request.getRequestDispatcher("서블릿"), dis.forward(request,response);

  --> 서블릿에서 다른 서블릿및 JSP로 데이터를 전달할 때 데이터가 적거나 보안에 덜 민감하다면 GET방식의 쿼리 스트링도 괜찮지만, 대량의 데이터나 민감한 데이터를 전달할 때는 바인딩 기능을 이용한다.

  --> redirect 방식으로는 서블릿에서 바인딩한 데이터를 다른 서블릿으로 전송할 수 없음. (dispatch이용)

 

간단한 예제.

@WebServlet("/members3")
public class MemberServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		try {
			doHandle(request,response);
		} catch (ServletException | IOException | SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		try {
			doHandle(request,response);
		} catch (ServletException | IOException | SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private void doHandle(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException, SQLException {
		
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		MemberDAO dao = new MemberDAO();
		PrintWriter out = response.getWriter();
		
		List<MemberVO> LIST = dao.getListMembers();
		
		request.setAttribute("memberList", LIST);
		RequestDispatcher dis = request.getRequestDispatcher("memeberforward");
		dis.forward(request, response);
	}
}
@WebServlet("/memeberforward")
public class MemberServlet2 extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public MemberServlet2() {
        super();
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		
		
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		
		PrintWriter out = response.getWriter();
		
		List<MemberVO> list = (List)request.getAttribute("memberList");
		
		out.println("<html><body>");
		for(MemberVO vo : list)
		{
			out.println( "id : " + vo.id + "pwd" + vo.pwd + "name" + vo.name + "email" + vo.email);
		}
		out.println("</html></body>");
		
	}
}

ServletContext / ServletConfig

앞서 forward가 하나의 서블릿에서 다른 서블릿 혹은 jsp로 데이터를 전달하는 개념이라면

ServletContext는 해당 객체에 데이터를 바인딩하고, 모든 서블릿에서 접근이 가능하다.

 -> ServletContext는 웹 애플리케이션 마다 한 개의 객체를 생성하고, 톰캣이 종료될 때 소멸된다.

// 하나의 서블릿에서 ServletContext에 데이터 바인딩
ServletContext context = getServletContext();
context.setAttribute("member", memberList);

// 다른 서블릿에서 ServletContext에 바인딩 된 데이터를 get
ServletContext context = getServletContext();		
List<MemberVO> list = (ArrayList)context.getAttribute("member");

 

이렇게 서블릿에서 직접 ServletContext에 직접 바인딩 할 수도있지만, web.xml에 설정해녹호 프로그림 시작 시 초기화 할 때 가져와서 사용하면 편리하다.

<conext-param>
	<param-name>menu_order</param-name>
	<param-value>주문조회 주문등록 주문수정 주문취소</param-value>
</context-param>

 

ServletConfig는 각 Servlet 객체에 대해 생성된다.

또한 다른 서블릿간에 공유는 불가능하고 자기자신의 서블릿에서만 접근이 가능하다.

 -> 서블릿 생성시 같이 ServletConfig객체가 생성되고 서블릿 소멸 시 같이 ServletConfig객체가 소멸된다.

 

load-on-startup

서블릿객체는 브라우저에서 최초 요청시 객체를 생성하고 init메소드를 실행한다.

 -> 최초 요청에 대한 응답이 늦음. 

이러한 단점을 보완한게 load-on-startup.

 -> 톰캣 컨테이너가 실해오디면서 미리 서블릿을 실행

 -> 지정한 숫자가 0보다 크면 톰캣 컨테이너가 실행되면서 서블릿이 초기화

 -> 지정한 숫자는 우선순위를 의미하며 작은 숫자부터 초기화 된다.

 

 

반응형

'웹프로그래밍 > JSP_Servlet' 카테고리의 다른 글

JSP - 2  (0) 2023.08.11
JSP  (0) 2023.08.11
서블릿에서의 쿠키와 세션  (0) 2023.08.10
서블릿(Servlet) 기초  (0) 2023.08.07
웹 어플리케이션의 기본구조 및 학습  (0) 2023.08.07
Comments