http://www.daesangit.com/ditboard/board/read.cgi?board=oralib&y_number=47&nnew=2


   ▒   자료실   ▒    

 이름 관리자 
 등록일 2002/9/21(토) 20:34 
 
 서블렛 + JDBC 연동시 코딩 고려사항 ②   
서블렛에서 instance variable의 공유 -DataBase Connection-

 public class TestServlet extends HttpServlet {
   private final static String drv = "oracle.jdbc.driver.OracleDriver";
   private final static String url = "jdbc:orache:thin@210.220.251.96:1521:ORA8i";
   private final static String user = "scott";
   private final static String password = "tiger";

   private ServletContext context;
   private Connection conn = null; <--- !!!
   private Statement stmt = null; <------ !!!
   private ResultSet rs = null; <------ !!!

   public void init(ServletConfig config) throws ServletException {
     super.init(config);
     context = config.getServletContext();
     try {
       Class.forName(drv);
     }
     catch (ClassNotFoundException e) {
      throw new ServletException("Unable to load JDBC driver:"+ e.toString());
     }
   }
   public void doGet(HttpServletRequest req, HttpServletResponse res) 
     throws ServletException, IOException, SQLException
   {
     String id = req.getParameter("id");
     conn = DriverManager.getConnection(url,user,password); ---- (1)
     stmt = conn.createStatement(); ---------- (2)
     rs = stmt.executeQuery("select .... where id = '" + id + "'"); ----- (3)
     while(rs.next()) { ----------- (4)
     ...... --------- (5)
     } 
     rs.close(); -------- (6)
     stmt.close(); ---- (7)
     conn.close(); --- (8)
     .....
   }
}

위에서 잘못된 것 중 하나가 java.sql.Connection과 java.sql.Statment, java.sql.ResultSet을 instance variable로 사용하고 있다는 것이다. 

이 서블렛은 사용자가 혼자일 경우엔 아무 문제가 없다. 하지만 여러사람이 동시에 이 서블렛을 호출하면 이상한 증상이 나타난다. 그 이유는 conn, stmt, rs 등과 같은 reference들을 instance 변수로 선언했기 때문이다. 서블렛은 Thread로 동작하며 위처럼 instance 변수 영역에 선언해 둔 reference들은 doGet(), doPost() 를 수행하면서 각각의 요청들이 동시에 공유하게 된다. 

예를 들어 두 개의 요청이 약간의 시간차를 두고 비슷한 순간에 doGet() 안으로 들어왔다고 가정해 보자. A라는 요청이 순차적으로 (1), (2), (3)까지 수행했을 때, B라는 요청이 곧바로 doGet() 안으로 들어올 수 있다. B 역시 (1), (2), (3)을 수행한다.

이제 요청 A 는 (4) 번과 (5) 번을 수행하려 하는데, 가만히 생각해 보면, 요청B 로 인해 요청A에 의해 할당됐던 conn, stmt, rs의 reference 들은 변경된다. 결국 요청 A는 요청 B의 결과를 가지고 작업을 하게 된다. 반면 요청 B는 요청 A에 의해 rs.next() 를 이미 수행했기 때문에 rs.next() 의 결과가 이미 close됐다는 엉뚱한 결과가 생긴다. 

다른 쉬운 얘기로 설명해 보면, A, B 두사람이 식탁에 앉아서 각자 자신이 준비해 온 사과를 하나씩 깎아 식탁 위의 접시에 올려 놓고 나중에 먹으려 하는 것과 동일하다. A라는 사람이 열심히 사과를 깎아 접시에 담아둘 때, B라는 사람이 들어와서 A가 깎아둔 사과를 버리고 자신이 깎은 사과를 대신 접시에 담아 둔다. 

이제 A라는 사람은 자신이 깎아서 담아 둔 것이라고 생각하는 사과를 접시에서 먹는다. 곧이어 B라는 사람이 자신의 사과를 접시에서 먹어려 하니 이미 A가 먹고 난 후이다. 이는 두 사람이 접시를 공유했기 때문에 발생하는 문제이다. 마찬가지로 서블렛의 각 Thread는 instance variable를 공유하기 때문에 동일한 문제들을 발생하게 된다. 

따라서 위의 내용은 최소한 다음과 같이 수정돼야 한다. 

public class TestServlet extends HttpServlet {
 private final static String drv = "...";
 private final static String url = "....";
 private final static String user = "...";
 private final static String password = "...";

 private ServletContext context;

 public void init(ServletConfig config) throws ServletException {
   super.init(config);
   context = config.getServletContext();
   try {
     Class.forName(drv);
   }
   catch (ClassNotFoundException e) {
    throw new ServletException("Unable to load JDBC driver:"+ e.toString());
   }
 }
 public void doGet(HttpServletRequest req, HttpServletResponse res) 
   throws ServletException, IOException, SQLException
 {
   Connection conn = null; <----- 이곳으로 와야죠..
   Statement stmt = null; <-------
   ResultSet rs = null; <---------

   String id = req.getParameter("id");
   conn = DriverManager.getConnection(url,user,password); 
   stmt = conn.createStatement();
   rs = stmt.executeQuery("select ..... where id = '" + id + "'");
   while(rs.next()) {
     ...... 
   } 
   rs.close();
   stmt.close();
   conn.close();
   .....
   }
}

JSP에서 아래처럼 사용하는 경우가 위의 경우와 동일한 instance 변수를 공유하는 경우이다. 
  
  
  
  
  

마찬가지로 위험천만한 일이며, 여러 Thread에 의해 값이 변할 수 있는 변수들은 를 이용해 선언하면 안된다. 이처럼 instance 변수로 사용할 것은 다음과 같이 그 값이 변하지 않는 값이거나 공유변수에 대한 특별히 관리한 상태에서 해야 한다. 

  

JSP에서의 이와 같은 잘못된 유형도, 앞선 서블렛의 경우처럼 일부 국내 JSP관련 서적에서 발견된다. 해당 서적의 저자는 가능한 빨리 개정판을 통해 시정해야 할 것이다. 해당 책은 출판사, 책의 유형, 글자체로 추정건데 초보자가 쉽게 선택할 법한 책인 만큼 그 파급력과 영향력이 너무 크다. 

이와 같은 부분이 실제 프로젝트에서 존재하면 대부분 시스템 오픈 첫날 쯤에 문제를 인식하게 된다. Connection reference가 엎어쳐지므로 Pool에 반환이 일어나지 않게 되고, 이는 "connection pool"의 가용한 자원이 부하가 얼마 없음에도 불구하고 모자라는 현상으로 나타나며, 때론 사용자의 화면에서는 엉뚱한 다른 사람의 데이타가 나타나거나, SQLException 이 나타난다. @ 
