Leeyebin의 블로그

4장 서블릿과 JDBC 본문

공부 기록실/JAVA 웹 개발 워크북 요약정리

4장 서블릿과 JDBC

안되면될때까지 2017. 4. 10. 09:56

부록2 JDBC와 데이터베이스


2.1 데이터베이스가 없던 그 시절

  • 파일 입,출력 API를 사용하여 데이터 입,출력 프로그래밍
  • 데이터가 분리되어 통합 조회 어려움
  • 데이터의 중복 발생
  • 파일 형식이 애플리케이션 및 프로그래밍 언어에 종속됨


데이터 입,출력 프로그래밍이 번거롭다.

애플리케이션의 데이터가 분리되어 통합 조회가 어렵다.

데이터 중복이 발생한다.(저장 공간이 낭비된다., 데이터 무결성이 깨진다.)

파일형식이 애플리케이션이나 프로그래밍 언어에 종속된다.


2.2 데이터베이스의 등장

  • 파일 입,출력 프로그래밍으로부터 탈출
  • 데이터 통합 관리 및 데이터 중복 감소
  • 애플리케이션으로부터 독립

개발자의 파일 입,출력 프로그래밍으로부터 자유롭다.(파일 입,출력 API를 사용하여 데이터를 읽고 쓰는 것은 DBMS에게 맡실 수 있다.)

데이터를 통합하여 관리하므로 관련 데이터의 추출이 쉽고, 중복되지 않는다.

애플리케이션에 종속되지 않는다.


2.3 데이터베이스의 사용

  • DBMS접속 프로토콜의 비공개
  • 벤더 API를 통해서만 접속 가능
  • DBMS종속에 따른 개발비 증가


DBMS 접속 프로토콜이 공개되어 있지 않다.

벤더 API를 사용해야만 DBMS에 접속할 수 있다.

DBMS 전용 애플리케이션의 개발로 비용이 증가한다.


2.4 ODBC로 개발자에게 자유를

ODBC의 사용으로 다양한 DBMS를 손쉽게 지원(ODBC는 특정 DBMS에 종속되지 않고 접근할 수 있도록 마이크로소프트에서 개발한 API이다.)


2.5 JDBC Type1 - ODBC와 연결

  • ODBC드라이버를 사용하여 DBMS에 접속
  • JRE에 포함. 속도가 느림(ODBC드라이버를 거치기 때문에)

2.6 JDBC Type2 - DMS 벤더 API 호출
  • DBMS 벤더 API사용
  • 별도로 JDBC드라이버 내려받기
  • DBMS 클라이언트의 설치 필요

2.7 JDBC Type3 - 미들웨어 서버를 경유
  • 미들웨어 서버를 경유하여 DBMS에 접속
  • 미들웨어에서 제공하는 JDBC 드라이버 사용
  • Pure Java(ODBC나 벤더 API처럼 C나 C++로 만든 API를 호출하지 않기 때문에 Pure Java라한다.

2.8 JDBC Type4 - DBMS 프로토콜로 직접 연결
  • DBMS프로토콜을 사용하여 직접통신
  • 별도로 JDBC드라이버 내려받기
  • Pure Java

4.1 데이터베이스에서 데이터 가져오기
데이터베이스를 사용하려면 두 가지가 필요함
  • 데이터베이스에 요청을 전달하고 결과를 받을 때 사용할 도구(JAVA에서는 JDBC 기술을 제공한다.)
  • 데이터베이스에 명령을 내릴 때 사용할 언어(SQL)



실습
study계정 만들면서 비번 세팅
mysql> create user study@localhost identified by 'study';


스키마 만들면서 문자열셋도 세팅(utf8)
mysql> create database studydb default character set utf8 collate utf8_general_ci;
show databases;


study에 권한주기
mysql> grant all privileges on studydb.* to study@localhost identified by 'study';


study로 로그인
mysql -ustudy -pstudy
mysql> use studydb


회원목록 조회 구현(P163 )
--회원기본정보 테이블 MEMBERS 생성
mysql> CREATE TABLE MEMBERS(
    ->  MNO INTEGER NOT NULL COMMENT '회원일련번호',
    -> EMAIL VARCHAR(40) NOT NULL COMMENT '이메일',
    -> PWD VARCHAR(100) NOT NULL COMMENT '암호',
    -> MNAME VARCHAR(50) NOT NULL COMMENT '이름',
    -> CRE_DATE DATETIME NOT NULL COMMENT '가입일',
    -> MOD_DATE DATETIME NOT NULL COMMENT '마지막암호변경일'
    -> )
    -> COMMENT '회원기본정보';

--MEMBERS 테이블의 기본 키 정의
mysql> ALTER TABLE MEMBERS
    -> ADD CONSTRAINT PK_MEMBERS
    -> PRIMARY KEY(
    -> MNO
    -> );

--MEMBERS의 EMAIL 칼럼을 유니크로 지정
mysql> CREATE UNIQUE INDEX UIX_MAMBERS
    ->  ON MEMBERS(
    ->          EMAIL ASC
    -> );

--MEMBERS의 PK자동증가
mysql> ALTER TABLE MEMBERS
    ->  MODIFY COLUMN MNO INTEGER NOT NULL AUTO_INCREMENT
    -> COMMENT '회원일련번호';


--테스트데이터 준비
mysql> INSERT INTO MEMBERS(EMAIL, PWD, MNAME, CRE_DATE, MOD_DATE)
    -> VALUE('s1@test.com', '1111', '홍길동', NOW(), NOW());

테스트 데이터 입력
mysql> INSERT INTO MEMBERS(EMAIL, PWD, MNAME, CRE_DATE, MOD_DATE)
    -> VALUE('s1@test.com', '1111', '홍길동', NOW(), NOW());
Query OK, 1 row affected (0.01 sec)

mysql> INSERT INTO MEMBERS(EMAIL, PWD, MNAME, CRE_DATE, MOD_DATE) VALUE('s2@test
.com', '1111', '임꺽정', NOW(), NOW());
Query OK, 1 row affected (0.01 sec)

mysql> INSERT INTO MEMBERS(EMAIL, PWD, MNAME, CRE_DATE, MOD_DATE) VALUE('s3@test
.com', '1111', '일지매', NOW(), NOW());
Query OK, 1 row affected (0.01 sec)

mysql> INSERT INTO MEMBERS(EMAIL, PWD, MNAME, CRE_DATE, MOD_DATE) VALUE('s4@test
.com', '1111', '이몽룡', NOW(), NOW());
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO MEMBERS(EMAIL, PWD, MNAME, CRE_DATE, MOD_DATE) VALUE('s5@test
.com', '1111', '성춘향', NOW(), NOW());
Query OK, 1 row affected (0.01 sec)

JDBC 드라이버 준비(ex: mysql-connector-java-5.1.26-bin.jar // 프로젝트의 WEB-INF/lib안에)


package spms.servlets;
package spms.servlets;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;

@WebServlet("/member/list")
public class MemberListServlet extends GenericServlet { //GenericServlet을 상속받음
	private static final long serialVersionUID = 1L;

	@Override
	public void service(ServletRequest request, ServletResponse response)
			throws ServletException, IOException {
		//데이터베이스 관련 객체의 참조 변수 선언
		Connection conn = null;
		Statement stmt = null;
		ResultSet rs = null;

		try { //JDBC API를 사용할 때 예외가 발생할 수 잇으므로 try~catch~ 블록을 준비한다.
			DriverManager.registerDriver(new com.mysql.jdbc.Driver()); //DriverManager를 이용하여 java.sql.Driver 인터페이스 구현체를 등록하는 일이다. MySQL의 경우에는 com.mysql.jdbc.Driver클래스
			conn = DriverManager.getConnection( //MySQL 서버에 연결
					"jdbc:mysql://localhost/studydb", //JDBC URL (사용할 JDBC드라이버 + 서버주소와 포트번호  + DB서비스 아이디)
					"study",	// DBMS 사용자 아이디
					"study");	// DBMS 사용자 암호
			stmt = conn.createStatement(); //Connection 구현체를 이용하여 SQL문을 실행할 객체를 준비한다.
			rs = stmt.executeQuery( //java.sql.Statement 인터페이스의 구현체 => executeQuery(), executeUpdate(), execute(), executeBatch()
					"SELECT MNO,MNAME,EMAIL,CRE_DATE" + 
					" FROM MEMBERS" +
					" ORDER BY MNO ASC");
			
			
			response.setContentType("text/html; charset=UTF-8");//데이터 형식과 문자 집합 세팅
			PrintWriter out = response.getWriter();
			out.println("<html><head><title>회원목록</title></head>");
			out.println("<body><h1>회원목록</h1>");
			while(rs.next()) {//rs(ResultSet 인터페이스의 구현체 => first(), last(), previous(), next(), getXXX(), updateXXX(), deleteRow())
				out.println(//getInt(1) OR getInt("MNO")로 할 수 있다. 주의할 점은 칼럼 인덱스는 1부터 시작한다.
					rs.getInt("MNO") + "," +
					rs.getString("MNAME") + "," +
					rs.getString("EMAIL") + "," + 
					rs.getDate("CRE_DATE") + "<br>"
				);
			}
			out.println("</body></html>");
		} catch (Exception e) {
			throw new ServletException(e);
			
		} finally {
			//자원을 해제할 때는 역순으로 처리 ResultSet -> Statement -> Connection 순서대로 close()
			try {if (rs != null) rs.close();} catch(Exception e) {}
			try {if (stmt != null) stmt.close();} catch(Exception e) {}
			try {if (conn != null) conn.close();} catch(Exception e) {}
		}

	}
}



4.2 HttpServlet으로 GET 요청 다루기


심플하게 URL이 '/'으로 시작하면 절대 경로, '/'으로 시작하지 않으면 상대 경로


절대경로 URL - 웹 서버 루트를 기준으로 계산한다.

절대 경로를 작성할 때 현재 웹 애플리케이션의 경로를 빠트려서는 안된다. 절대 경로로  URL을 계산할 때 현재 컨텍스트 루트의 경로가 자동 계산되지 않기 때문이다. 컨텍스트 루트의 이름을 바꾸면 절대 경로를 사용한 모든 웹 페이지의 링크를 바꾸어야 하기때문에 실무에서는 절대경로를 사용하지 않고 상대 경로를 사용한다.

EX)
서버루트 - http://localhost:9999
최종경로 - http://localhost:9999/web04/member/add
절대경로 - /web04/member/add






상대경로 URL -  현재 경로를 기준으로 계산한다.
EX)
현재경로 - http://localhost:9999/web04/member/list
최종경로 - http://localhost:9999/web04/member/add
상대경로 - add




HttpServlet 클래스

참고 : https://tomcat.apache.org/tomcat-5.5-doc/servletapi/javax/servlet/http/HttpServlet.html


HttpServlet 클래스는 GenericServlet 클래스의 하위 클래스이다. 따라서 HttpServlet을 상속받으면 GenericServlet클래스를 상송받는 것과 마찬가지로 javax.servlet.Servlet 인터페이스를 구현한 것이 된다.


Servlet과 GenericServlet을 상속받으면 service()를 오버라이딩해야하는데 HttpServlet을 받으면 service를 오버라이딩하지 않는 이유에 대해서 인터넷을 찾다가 못찾겠어서 디컴파일을 해봤다. 부들부들


//Servlet.class
package javax.servlet;

import java.io.IOException;

public abstract interface Servlet
{
  public abstract void init(ServletConfig paramServletConfig)
    throws ServletException;
  
  public abstract ServletConfig getServletConfig();
  
  public abstract void service(ServletRequest paramServletRequest, ServletResponse paramServletResponse)
    throws ServletException, IOException;
  
  public abstract String getServletInfo();
  
  public abstract void destroy();
}


//GenericServlet.class
package javax.servlet;

import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;

public abstract class GenericServlet
  implements Servlet, ServletConfig, Serializable
{
  private static final long serialVersionUID = 1L;
  private transient ServletConfig config;
  
  public void destroy() {}
  
  public String getInitParameter(String name)
  {
    return getServletConfig().getInitParameter(name);
  }
  
  public Enumeration getInitParameterNames()
  {
    return getServletConfig().getInitParameterNames();
  }
  
  public ServletConfig getServletConfig()
  {
    return this.config;
  }
  
  public ServletContext getServletContext()
  {
    return getServletConfig().getServletContext();
  }
  
  public String getServletInfo()
  {
    return "";
  }
  
  public void init(ServletConfig config)
    throws ServletException
  {
    this.config = config;
    init();
  }
  
  public void init()
    throws ServletException
  {}
  
  public void log(String msg)
  {
    getServletContext().log(getServletName() + ": " + msg);
  }
  
  public void log(String message, Throwable t)
  {
    getServletContext().log(getServletName() + ": " + message, t);
  }
  
  public abstract void service(ServletRequest paramServletRequest, ServletResponse paramServletResponse)
    throws ServletException, IOException;
  
  public String getServletName()
  {
    return this.config.getServletName();
  }
}


//HttpServlet.class
package javax.servlet.http;

import java.io.IOException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.ResourceBundle;
import javax.servlet.DispatcherType;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public abstract class HttpServlet
  extends GenericServlet
{
  private static final long serialVersionUID = 1L;
  private static final String METHOD_DELETE = "DELETE";
  private static final String METHOD_HEAD = "HEAD";
  private static final String METHOD_GET = "GET";
  private static final String METHOD_OPTIONS = "OPTIONS";
  private static final String METHOD_POST = "POST";
  private static final String METHOD_PUT = "PUT";
  private static final String METHOD_TRACE = "TRACE";
  private static final String HEADER_IFMODSINCE = "If-Modified-Since";
  private static final String HEADER_LASTMOD = "Last-Modified";
  private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
  private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings");
  
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException
  {
    String protocol = req.getProtocol();
    String msg = lStrings.getString("http.method_get_not_supported");
    if (protocol.endsWith("1.1")) {
      resp.sendError(405, msg);
    } else {
      resp.sendError(400, msg);
    }
  }
  
  protected long getLastModified(HttpServletRequest req)
  {
    return -1L;
  }
  
  protected void doHead(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException
  {
    if (DispatcherType.INCLUDE.equals(req.getDispatcherType()))
    {
      doGet(req, resp);
    }
    else
    {
      NoBodyResponse response = new NoBodyResponse(resp);
      doGet(req, response);
      response.setContentLength();
    }
  }
  
  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException
  {
    String protocol = req.getProtocol();
    String msg = lStrings.getString("http.method_post_not_supported");
    if (protocol.endsWith("1.1")) {
      resp.sendError(405, msg);
    } else {
      resp.sendError(400, msg);
    }
  }
  
  protected void doPut(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException
  {
    String protocol = req.getProtocol();
    String msg = lStrings.getString("http.method_put_not_supported");
    if (protocol.endsWith("1.1")) {
      resp.sendError(405, msg);
    } else {
      resp.sendError(400, msg);
    }
  }
  
  protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException
  {
    String protocol = req.getProtocol();
    String msg = lStrings.getString("http.method_delete_not_supported");
    if (protocol.endsWith("1.1")) {
      resp.sendError(405, msg);
    } else {
      resp.sendError(400, msg);
    }
  }
  
  private static Method[] getAllDeclaredMethods(Class c)
  {
    if (c.equals(HttpServlet.class)) {
      return null;
    }
    Method[] parentMethods = getAllDeclaredMethods(c.getSuperclass());
    Method[] thisMethods = c.getDeclaredMethods();
    if ((parentMethods != null) && (parentMethods.length > 0))
    {
      Method[] allMethods = new Method[parentMethods.length + thisMethods.length];
      
      System.arraycopy(parentMethods, 0, allMethods, 0, parentMethods.length);
      
      System.arraycopy(thisMethods, 0, allMethods, parentMethods.length, thisMethods.length);
      
      thisMethods = allMethods;
    }
    return thisMethods;
  }
  
  protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException
  {
    Method[] methods = getAllDeclaredMethods(getClass());
    
    boolean ALLOW_GET = false;
    boolean ALLOW_HEAD = false;
    boolean ALLOW_POST = false;
    boolean ALLOW_PUT = false;
    boolean ALLOW_DELETE = false;
    boolean ALLOW_TRACE = true;
    boolean ALLOW_OPTIONS = true;
    for (int i = 0; i < methods.length; i++)
    {
      Method m = methods[i];
      if (m.getName().equals("doGet"))
      {
        ALLOW_GET = true;
        ALLOW_HEAD = true;
      }
      if (m.getName().equals("doPost")) {
        ALLOW_POST = true;
      }
      if (m.getName().equals("doPut")) {
        ALLOW_PUT = true;
      }
      if (m.getName().equals("doDelete")) {
        ALLOW_DELETE = true;
      }
    }
    String allow = null;
    if (ALLOW_GET) {
      allow = "GET";
    }
    if (ALLOW_HEAD) {
      if (allow == null) {
        allow = "HEAD";
      } else {
        allow = allow + ", HEAD";
      }
    }
    if (ALLOW_POST) {
      if (allow == null) {
        allow = "POST";
      } else {
        allow = allow + ", POST";
      }
    }
    if (ALLOW_PUT) {
      if (allow == null) {
        allow = "PUT";
      } else {
        allow = allow + ", PUT";
      }
    }
    if (ALLOW_DELETE) {
      if (allow == null) {
        allow = "DELETE";
      } else {
        allow = allow + ", DELETE";
      }
    }
    if (ALLOW_TRACE) {
      if (allow == null) {
        allow = "TRACE";
      } else {
        allow = allow + ", TRACE";
      }
    }
    if (ALLOW_OPTIONS) {
      if (allow == null) {
        allow = "OPTIONS";
      } else {
        allow = allow + ", OPTIONS";
      }
    }
    resp.setHeader("Allow", allow);
  }
  
  protected void doTrace(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException
  {
    String CRLF = "\r\n";
    StringBuilder buffer = new StringBuilder("TRACE ").append(req.getRequestURI()).append(" ").append(req.getProtocol());
    
    Enumeration reqHeaderEnum = req.getHeaderNames();
    while (reqHeaderEnum.hasMoreElements())
    {
      String headerName = (String)reqHeaderEnum.nextElement();
      buffer.append(CRLF).append(headerName).append(": ").append(req.getHeader(headerName));
    }
    buffer.append(CRLF);
    
    int responseLength = buffer.length();
    
    resp.setContentType("message/http");
    resp.setContentLength(responseLength);
    ServletOutputStream out = resp.getOutputStream();
    out.print(buffer.toString());
    out.close();
  }
  
  protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException
  {
    String method = req.getMethod();
    if (method.equals("GET"))
    {
      long lastModified = getLastModified(req);
      if (lastModified == -1L)
      {
        doGet(req, resp);
      }
      else
      {
        long ifModifiedSince;
        try
        {
          ifModifiedSince = req.getDateHeader("If-Modified-Since");
        }
        catch (IllegalArgumentException iae)
        {
          ifModifiedSince = -1L;
        }
        if (ifModifiedSince < lastModified / 1000L * 1000L)
        {
          maybeSetLastModified(resp, lastModified);
          doGet(req, resp);
        }
        else
        {
          resp.setStatus(304);
        }
      }
    }
    else if (method.equals("HEAD"))
    {
      long lastModified = getLastModified(req);
      maybeSetLastModified(resp, lastModified);
      doHead(req, resp);
    }
    else if (method.equals("POST"))
    {
      doPost(req, resp);
    }
    else if (method.equals("PUT"))
    {
      doPut(req, resp);
    }
    else if (method.equals("DELETE"))
    {
      doDelete(req, resp);
    }
    else if (method.equals("OPTIONS"))
    {
      doOptions(req, resp);
    }
    else if (method.equals("TRACE"))
    {
      doTrace(req, resp);
    }
    else
    {
      String errMsg = lStrings.getString("http.method_not_implemented");
      Object[] errArgs = new Object[1];
      errArgs[0] = method;
      errMsg = MessageFormat.format(errMsg, errArgs);
      
      resp.sendError(501, errMsg);
    }
  }
  
  private void maybeSetLastModified(HttpServletResponse resp, long lastModified)
  {
    if (resp.containsHeader("Last-Modified")) {
      return;
    }
    if (lastModified >= 0L) {
      resp.setDateHeader("Last-Modified", lastModified);
    }
  }
  
  public void service(ServletRequest req, ServletResponse res)
    throws ServletException, IOException
  {
    HttpServletRequest request;
    HttpServletResponse response;
    try
    {
      request = (HttpServletRequest)req;
      response = (HttpServletResponse)res;
    }
    catch (ClassCastException e)
    {
      throw new ServletException("non-HTTP request or response");
    }
    service(request, response);
  }
}


클라이언트 요청이 들어오면, 첫째로 상속받은 HttpServlet의 service() 메서드가 호출되고(HttpServlet.class의 222Line), service()는 클라이언트 요청방식에 따라 doGet(), doPost(), doPut() 등의 메서드를 호출 한다.


참고사이트 : http://www.silverwolf.co.kr/java/9906


4.3 HttpServlet으로 POST 요청 다루기

package spms.servlets; import java.io.IOException; import java.io.PrintWriter; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.Statement; 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/add") public class MemberAddServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doGet( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html; charset=UTF-8"); PrintWriter out = response.getWriter(); out.println("<html><head><title>회원 등록</title></head>"); out.println("<body><h1>회원 등록</h1>"); out.println("<form action='add' method='post'>"); out.println("이름: <input type='text' name='name'><br>"); out.println("이메일: <input type='text' name='email'><br>"); out.println("암호: <input type='password' name='password'><br>"); out.println("<input type='submit' value='추가'>"); out.println("<input type='reset' value='취소'>"); out.println("</form>"); out.println("</body></html>"); } @Override protected void doPost( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); Connection conn = null; //Statement stmt = null; PreparedStatement stmt = null; try { DriverManager.registerDriver(new com.mysql.jdbc.Driver()); conn = DriverManager.getConnection( "jdbc:mysql://localhost/studydb", //JDBC URL "study", // DBMS 사용자 아이디 "study"); // DBMS 사용자 암호 stmt = conn.prepareStatement( "INSERT INTO MEMBERS(EMAIL,PWD,MNAME,CRE_DATE,MOD_DATE)" + " VALUES (?,?,?,NOW(),NOW())"); stmt.setString(1, request.getParameter("email"));//입력 매개변수에 할당할 데이터의 타입에 따라, 입력 매개변수의 번호는 1부터 시작함. stmt.setString(2, request.getParameter("password")); stmt.setString(3, request.getParameter("name")); stmt.executeUpdate(); // 리다이렉트를 이용한 리프래시 response.sendRedirect("list"); } catch (Exception e) { throw new ServletException(e); } finally { try {if (stmt != null) stmt.close();} catch(Exception e) {} try {if (conn != null) conn.close();} catch(Exception e) {} } } }


46, 47Line

PreparedStatement는 반복적인 질의를 하거나, 입력 매개변수가 많은 경우에 유용하다. 특히 이미지와 같은 바이너리 데이터를 저장하거나 변경할 때는 PreparedStatement만이 가능하다.


이미지 출처 : http://javaconceptoftheday.com/statement-vs-preparedstatement-vs-callablestatement-in-java/


62Line

SELECT문을 실행할 때는 executeQuery()를 호출하고, INSERT처럼 DDL이나 DML종류의 SQL문을 실행할 때는 executeUpdate()를 호출한다.


4.4 요청 매개변수의 한글 깨짐 처리

4.3 맨 아래에 있는 소스대로 입력을 한다면 한글이 깨져서 나올 것이다. 그 이유는 웹 브라우저가 UTF-8로 인코딩되어 서버에 전달하더라도, 서블릿에서 인코딩 세팅 없이 getParameter()를 호출하게 되면 기본적으로 매개변수의 값을 iso-8859-1로 인코딩되었다고 가정하고 각 바이트를 유니코드로 바꾸고 나서 반환하기 때문이다.(쉽게말해 영어라고 간주하고 유니코드로 바꾼다.)


한글이 깨지는 것에 대한 왠만한 모든 해결방법이 있다.

참고: http://prioriq.tistory.com/5


한글 깨짐 해결책

getParameter()를 호출하기 전에 클라이언트가 보낸 매개변수의 값이 어떤 인코딩으로 되어있는지 세팅 해야한다.

request.setCharacterEncoding("UTF-8"); 
//단, 이 메서드가 호출되기 전에 getParameter()가 먼저 호출된다면 아무 소용이 없다.
//통캣(5.x)의 경우 클라이언트에서 GET 요청으로 데이터를 보내면 여전히 한글이 깨질 수 있는데, 매개변수의 값을 URL에 포함하여 보내는 쿼드스트링의 경우에는 setCharacterEncoding()은 적용되지 않는다.
//이런 경우 톰캣 서버의 server.xml에서 URIEncoding="UTF-8"을 추가하도록 하자


4.5리프레시

작업 결과를 출력한 후 다른 페이지로 이동할 때 사용함.

-응답 헤더를 이요한 리프레시

-HTML의 meta 태그를 이요한 리프래시(<meta>태그는 반드시 <head> 태그 안에 선언해야 한다.)


4.6 리다이렉트

작업결과를 출력하지 않고 다른 페이지로 이동할 때 사용함.

-response.sendRedirect("list");


4.7 서블릿 초기화 매개변수

서블릿을 생성하고 초기화할 때, init()을 호출할 대 서블릿 컨테이너가 전달하는 데이터이다. 보통 정적인 데이터를 서블릿에 전달할 때 사용한다.

설정 방법 두 가지

-DD파일(web.xml)의 서블릿 배치 정보에 설정

서블릿 초기화 매개변수들은 오직 그 매개변수를 선언한 서블릿에서만 사용할 수 있다.

소스 파일 밖에 DB 정보를 두면 나중에 DB정보가 바뀌더라도 web.xml만 편집하면 되기 때문에 유지보수가 쉬워진다. 이처럼 변경되기 쉬운 값들은 XML 파일(.xml)이나 프로퍼티 파일(.properties)에 두어 관리한다.

--서블릿 초기화 매개변수를 설정하는 엘리먼트
<init-param>
    <param-name>매개변수 이름</param-name>
    <param-value>매개변수 값</param-value>
</init-param>


-애노테이션을 사용하여 서블릿 소스 코드에 설정
@WebServlet 애노테이션의 initParams는 서블릿 초기화 매개변수를 설정하는 속성이다. 이 속성의 값은 @WebInitParam의 배열이다.

initParams={@WebInitParam(), @WebInitParam(), ...} //@WebInitParam은 두 개의 필수 속성과 한 개의 선택 송성이 올 수 있다. @WebInitParam( name="매개변수 이름", /* (필수) 초기화 매개변수의 이름을 설정 */ value="매개변수 값", /* (필수) value 속성값 설정 */ description="매개변수에 대한 설명" /* (선택) */ ) //Example @WebServlet( urlPatterns={"/member/update"}, initParams={ @WebInitParam(name="driver",value="com.mysql.jdbc.Driver"), @WebInitParam(name="url",value="jdbc:mysql://localhost/studydb"), @WebInitParam(name="username",value="study"), @WebInitParam(name="password",value="study") } )


4.8 컨텍스트 초기화 매개변수


같은 웹 애플리케이션에 소속된 서블릿들이 공유하는 매개변수이다.(여러 서블릿이 사용하는 공통된 정보일 때 컨텍스트 초기화 매개변수를 사용한다.)


<!-- 컨텍스트 매개변수(web.xml) -->
	<context-param>
		<param-name>driver</param-name>
		<param-value>com.mysql.jdbc.Driver</param-value>
	</context-param>
	<context-param>
		<param-name>url</param-name>
		<param-value>jdbc:mysql://localhost/studydb</param-value>
	</context-param>
	<context-param>
		<param-name>username</param-name>
		<param-value>study</param-value>
	</context-param>
	<context-param>
		<param-name>password</param-name>
		<param-value>study</param-value>
	</context-param>


try {
			Class.forName(this.getInitParameter("driver"));
			conn = DriverManager.getConnection(
						this.getInitParameter("url"),
						this.getInitParameter("username"),
						this.getInitParameter("password")); 
			stmt = conn.prepareStatement(
					"UPDATE MEMBERS SET EMAIL=?,MNAME=?,MOD_DATE=now()"
					+ " WHERE MNO=?");
			stmt.setString(1, request.getParameter("email"));
			stmt.setString(2, request.getParameter("name"));
			stmt.setInt(3, Integer.parseInt(request.getParameter("no")));
			stmt.executeUpdate();
			
			response.sendRedirect("list");
			
		}
        
//
// 변경
//

try {
			ServletContext sc = this.getServletContext();
			//Class.forName(this.getInitParameter("driver"));
			conn = DriverManager.getConnection(
						sc.getInitParameter("url"),
						sc.getInitParameter("username"),
						sc.getInitParameter("password")); 
			stmt = conn.prepareStatement(
					"UPDATE MEMBERS SET EMAIL=?,MNAME=?,MOD_DATE=now()"
					+ " WHERE MNO=?");
			stmt.setString(1, request.getParameter("email"));
			stmt.setString(2, request.getParameter("name"));
			stmt.setInt(3, Integer.parseInt(request.getParameter("no")));
			stmt.executeUpdate();
			
			response.sendRedirect("list");
			
		}



4.9 필터 사용하기

서블릿 실해 전후에 어떤 작업을 하고자 할 때 사용하는 기술이다. 예)클라이언트가 보낸 데이터의 암호를 해제, 필요한 자원 미리 준비, 서블릿 실행될 때마다 로그를 남기기 등


필터의 라이프 사이클(오른쪽)


이미지 출처 : http://otndnld.oracle.co.jp/document/products/as10g/101300/B25221_03/web.1013/b14426/filters.htm


Method Summary
 voiddestroy() 
          Called by the web container to indicate to a filter that it is being taken out of service.
 voiddoFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
          The doFilter method of the Filter is called by the container each time a request/response pair is passed through the chain due to a client request for a resource at the end of the chain.
 voidinit(FilterConfig filterConfig) 
          Called by the web container to indicate to a filter that it is being placed into service.

 표출처 : https://tomcat.apache.org/tomcat-5.5-doc/servletapi/javax/servlet/Filter.html



init()

필터 객체가 생성되고 나서 준비 작업을 위해 딱 한 번 호출 된다. Servlet 인터페이스의 init()와 같은 용도이다. 매개변수는 FilterConfig 객체이다.


doFilter()

필터와 연결된 URL에 대해 요청이 들어오면 doFilter()가 항상 호출된다. 이 메서드에 필터가 할 일을 작성하면 된다.


destroy()

웹 애플리케이션을 종료하기 전에 필터들에 대해 마무리 작업을 한다.


필터의 설정 방법 두 가지

-DD파일에 필터 배치 정보 설정(web.xml)

<!-- 예제 -->
<!-- 필터 선언 -->

	<filter>
		<filter-name>CharacterEncodingFilter</filter-name>
		<filter-class>spms.filters.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
	</filter>	
	<!-- 필터 URL 매핑 -->

	<filter-mapping>
		<filter-name>CharacterEncodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>


-애노테이션을 이용한 필터 배치


@WebFilter(
	urlPatterns="/*",
	initParams={
		@WebInitParam(name="encoding",value="UTF-8")
	})
public class CharacterEncodingFilter implements Filter{
	FilterConfig config;
	
	@Override
	public void init(FilterConfig config) throws ServletException {
		this.config = config;
	}
	
	@Override
	public void doFilter(
			ServletRequest request, ServletResponse response,
			FilterChain nextFilter) throws IOException, ServletException {
		request.setCharacterEncoding(config.getInitParameter("encoding"));
		nextFilter.doFilter(request, response);
	}

	@Override
	public void destroy() {}
}
Comments