달력

72025  이전 다음

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

최초작성일자: 2000/09/05 16:19:47 
최근 수정일 : 2001.01.27
최근 수정일 : 2001.03.12(nested sql query issue)
최근 수정일 : 2001.03.13(transaction)
최근 수정일 : 2001.03.20(instance variables in JSP)
최근 수정일 : 2001.04.03(문맥수정)
최근 수정일 : 2002.02.06("close 할 땐 제대로..." 추가사항첨가)
최근 수정일 : 2002.02.25("transaction관련 추가")
최근 수정일 : 2002.06.11(PreparedStatement에 의한 ResultSet close 이슈)
최근 수정일 : 2002.06.18(PreparedStatement관련 추가)
최근 수정일 : 2002.12.30(Instance Variable 공유 1.2 추가)


다들 아실법한 단순한 얘깁니다만, 아직 많은 분들이 모르시는 것 같아 다시한번 
정리합니다. 아래의 각각의 예제는 잘못 사용하고 계시는 전형적인 예들입니다.

1. 서블렛에서 instance variable 의 공유

1.1 서블렛에서 instance variable 의 공유 - PrintWriter - 

  다음과 같은 코드를 생각해 보겠습니다.

 import java.io.*;
 import javax.servlet.*;
 import javax.servlet.http.*;

 public class CountServlet extends HttpServlet {
     private PrintWriter out = null; // <-------------- (1)
 
     public void doGet(HttpServletRequest req, HttpServletResponse res)  
         throws ServletException, IOException
     {
         res.setContentType("text/html");
         out = res.getWriter();
         for(int i=0;i<20;i++){
             out.println("count= " + (i+1) + "<br>");  // <---- (2)
             out.flush();
             try{Thread.sleep(1000);}catch(Exception e){}
         }
    }
  }

 위의 CountServlet.java 를 컴파일하여 돌려 보면, 1초간격으로 일련의 숫자가 올라가는
 것이 보일 겁니다.(서블렛엔진의 구현방식에 따라 Buffering 이 되어 20초가 모두 지난
 후에서 퍽 나올 수도 있습니다.)
 혼자서 단일 Request 를 날려 보면, 아무런 문제가 없겠지만, 이제 브라우져 창을 두개 이상
 띄우시고 10초의 시간 차를 두시면서 동시에 호출해 보세요... 이상한 증상이 나타날
 겁니다. 먼저 호출한 창에는 10 까지 정도만 나타나고, 10초 뒤에 호출한 창에서는 먼저
 호출한 창에서 나타나야할 내용들까지 덤으로 나타나는 것을 목격할 수 있을 겁니다.

 이는 서블렛의 각 호출은 Thread 로 동작하여, 따라서, 각 호출은 위의 (1) 에서 선언한
 instance variable 들을 공유하기 때문에 나타나는 문제입니다.

 위 부분은 다음과 같이 고쳐져야 합니다.

  public class CountServlet extends HttpServlet {
     //private PrintWriter out = null;
 
     public void doGet(HttpServletRequest req, HttpServletResponse res)  
         throws ServletException, IOException
     {
         PrintWriter out = null; // <--- 이 쪽으로 와야죠 !!!
         res.setContentType("text/html");
         out = res.getWriter();
         for(int i=0;i<20;i++){
             out.println("count= " + (i+1) + "<br>");  // <---- (2)
             out.flush();
             try{Thread.sleep(1000);}catch(Exception e){}
         }
    }
  }

 국내 몇몇 Servlet 관련 서적의 일부 예제들이 위와 같은 잘못된 형태로 설명한 
 소스코드들이 눈에 띕니다. 빠른 시일에 바로 잡아야 할 것입니다.

 실제 프로젝트 환경에서 개발된 실무시스템에서도, 그러한 책을 통해 공부하신듯, 동일한
 잘못된 코딩을 하고 있는 개발자들이 있습니다. 결과적으로 테스트 환경에서는 나타나지
 않더니만, 막상 시스템을 오픈하고나니 고객으로 부터 다음과 같은 소리를 듣습니다. 
 "내 데이타가 아닌데 남의 데이타가 내 화면에 간혹 나타나요. refresh 를 누르면 또, 
 제대로 되구요" .....
 

1.2 서블렛에서 instance variable 의 공유

 앞서의 경우와 의미를 같이하는데, 다음과 같이 하면 안된다는 얘기지요.

  public class BadServlet extends HttpServlet {
     private String userid = null;
     private String username = null;
     private int hitcount = 0;
 
     public void doGet(HttpServletRequest req, HttpServletResponse res)  
         throws ServletException, IOException
     {
         res.setContentType("text/html");
         PrintWriter out = res.getWriter();
         userid = request.getParameter("userid");
         username = request.getParameter("username");
         hitcount = hitcount + 1;
         ....
    }
  }

 새로운 매 HTTP 요청마다 userid/username변수는 새롭게 할당됩니다. 문제는 그것이 특정
 사용자에 한하여 그러한 것이 아니라, BadServlet의 인스턴스(instance)는 해당
 웹컨테이너(Web Container)에 상에서 (예외경우가 있지만) 단 하나만 존재하고, 서로 다른
 모든 사용자들의 서로 다른 모든 요청들에 대해서 동일한 userid/username 및 count 변수를
 접근하게 됩니다. 따라서, 다음과 같이 메소드 안으로 끌어들여 사용하여야 함을 강조합니다.

  public class BadServlet extends HttpServlet {
     //private String userid = null; // <---- !!
     //private String username = null; // <---- !!
     private int hitcount = 0;
 
     public void doGet(HttpServletRequest req, HttpServletResponse res)  
         throws ServletException, IOException
     {
         res.setContentType("text/html");
         PrintWriter out = res.getWriter();
         String userid = request.getParameter("userid"); // <---- !!
         String username = request.getParameter("username"); // <---- !!

         //또한, instance 변수에 대한 접근은 적어도 아래처럼 동기화를 고려해야...
         synchronized(this){ hitcount = hitcount + 1; } 
         ....
    }
  }


1.3 서블렛에서 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();
         .....
     }
  }


1.4 JSP에서 Instance Variable 공유

 JSP에서 아래처럼 사용하는 경우가 위의 경우와 동일한 instance 변수를 공유하는 경우가
 됩니다.
   ---------------------------------------------------------
   <%@ page session=.... import=.... contentType=........ %>
   <%! 
       Connection conn = null;
       Statement stmt = null;
       ResultSet rs = null;
       String userid = null;
   %>
   <html><head></head><body>
   <%
       ........
       conn = ...
       stmt = .....

       uesrid = ......
   %>
   </body></html>
   ---------------------------------------------------------

   마찬가지로 위험천만한 일이며, 여러 Thread 에 의해 그 값이 변할 수 있는 변수들은
   <%! ... %> 를 이용하여 선언하시면 안됩니다. 이처럼 instance 변수로 사용할 것은
   다음 처럼, 그 값이 변하지 않는 값이거나, 혹은 공유변수에 대한 특별한 관리를
   하신 상태에서 하셔야 합니다.

   <%!  private static final String USERID = "scott";
        private static final String PASSWORD = "tiger";
   %>

   JSP에서의 이와 같은 잘못된 유형도, 앞선 서블렛의 경우처럼 일부 국내 JSP관련 
   서적에서 발견됩니다.  해당 서적의 저자는 가능한 빨리 개정판을 내셔서 시정하셔야
   할 것입니다. 해당 책은 출판사나 책의 유형, 그리고 글자체로 추정건데, 초보자가
   쉽게 선택할 법한 책인 만큼 그 파급력과 영향력이 너무 큰 듯 합니다.
 
   이와 같은 부분이 실 프로젝트에서 존재할 경우, 대부분 시스템 오픈 첫날 쯤에
   문제를 인식하게 됩니다. Connection reference 가 엎어쳐지므로 Pool 에 반환이
   일어나지 않게 되고, 이는 "connection pool"의 가용한 자원이 부하가 얼마 없음에도
   불구하고 모자라는 현상으로 나타나며, 때론 사용자의 화면에서는 엉뚱한 다른
   사람의 데이타가 나타나거나, SQLException 이 나타납니다.

   NOTE: 어떻게하란 말입니까? 각 호출간에 공유되어서는 안될 변수들은 <%! ...%> 가
   아니라, <% ... %> 내에서 선언하여 사용하란 얘깁니다. JSP가 Pre-compile되어
   Servlet형태로 변환될 때, <%! ... %>는 서블렛의 instance variable로 구성되는 반면,
   <% ... %>는 _jspService() 메소드 내의 method variable로 구성됩니다.



2. 하나의 Connection을 init()에서 미리 연결해 두고 사용하는 경우.

 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;  <--- !!!
 
     public void init(ServletConfig config) throws ServletException {
         super.init(config);
         context = config.getServletContext();
         try {
             Class.forName(drv);
             conn = DriverManager.getConnection(url,user,password);
         }
         catch (ClassNotFoundException e) {
             throw new ServletException("Unable to load JDBC driver:"+ e.toString());
         }
         catch (SQLException e) {
             throw new ServletException("Unable to connect to database:"+ e.toString());
         }
     }
     public void doGet(HttpServletRequest req, HttpServletResponse res)  
         throws ServletException, IOException, SQLException
     {
         Statement stmt = null;
         ResultSet rs = null;
         String id = req.getParameter("id");
         stmt = conn.createStatement();
         rs = stmt.executeQuery("select ..... where id = '" + id + "'");
         while(rs.next()) {
            ......  
         }  
         rs.close(); 
         stmt.close();
         .....
     }
     public void destroy() {
         if ( conn != null ) try {conn.close();}catch(Exception e){}
     }     
  }

 위는 뭐가 잘못되었을 까요?  서블렛당 하나씩 java.sql.Connection 을 init()에서 미리
 맺어 두고 사용하는 구조 입니다.

 얼핏 생각하면 아무런 문제가 없을 듯도 합니다. doGet() 내에서 별도의 Statement와 
 ResultSet 을 사용하고 있으니, 각 Thread는 자신만의 Reference를 갖고 사용하게 되니까요.

 이 구조는 크게 세가지의 문제를 안고 있습니다. 하나는 DB 연결자원의 낭비를 가져오며,
 두번째로 수많은 동시사용자에 대한 처리한계를 가져오고, 또 마지막으로 insert, update,
 delete 와 같이 하나 이상의 SQL문장을 수행하면서 단일의 Transaction 처리를 보장받을
 수 없다는 것입니다.

 1) DB 자원의 낭비

   위의 구조는 서블렛당 하나씩 java.sql.Connection 을 점유하고 있습니다. 실 프로젝트에서
   보통 서블렛이 몇개나 될까요? 최소한 100 개에서 400개가 넘어 갈 때도 있겠죠?
   그럼 java.sql.Connection에 할당 되어야 할 "DB연결갯수"도 서블렛 갯수 많큼 필요하게
   됩니다. DB 연결 자원은 DB 에서 설정하기 나름이지만, 통상 maximum 을 셋팅하기
   마련입니다. 그러나 아무런 요청이 없을 때도 400 여개의 DB연결이 연결되어 있어야 한다는
   것은 자원의 낭비입니다.
    
 2) 대량의 동시 사용자 처리 불가.

   또한, 같은 서블렛에 대해 동시에 100 혹은 그 이상의 요청이 들어온다고 가정해 보겠습
   니다. 그럼 같은 java.sql.Connection 에 대해서 각각의 요청이 conn.createStatement() 를
   호출하게 됩니다.
   문제는 하나의 Connection 에 대해 동시에 Open 할 수 있는 Statement 갯수는 ( 이 역시
   DB 에서 셋팅하기 나름이지만 ) maximum 제한이 있습니다. Oracle 의 경우 Default는 50
   입니다. 만약 이 수치 이상을 동시에 Open 하려고 하면 "maximum open cursor exceed !"
   혹은 "Limit on number of statements exceeded"라는 SQLExceptoin 을 발생하게 됩니다.

   예를 들어 다음과 같은 프로그램을 실행시켜 보세요.

   public class DbTest {
     public static void main(String[] args) throws Exception {
        Class.forName("jdbc driver...");
        Connection conn = DriverManager.getConnection("url...","id","password");
        int i=0;
        while(true) {
           Statement stmt = conn.createStatement();
           System.out.println( (++i) + "- stmt created");
        }
     }
   }

   과연 몇개 까지 conn.createStement() 가 수행될 수 있을까요? 이는 DB에서 설정하기 나름
   입니다. 중요한 것은 그 한계가 있다는 것입니다.
   또한 conn.createStatement() 통해 만들어진 stmt 는 java.sql.Connection 의 자원이기
   때문에 위처럼 stmt 의 reference 가 없어졌다고 해도 GC(Garbage Collection)이 되지
   않습니다.


  3) Transaction 중복현상 발생

  예를 들어 다음과 같은 서비스가 있다고 가정해 보겠습니다.

  public void doGet(HttpServletRequest req, HttpServletResponse res)  
      throws ServletException, IOException, SQLException
  {
      Statement stmt = null;
      String id = req.getParameter("id");
      try {
          conn.setAutoCommit(false);
          stmt = conn.createStatement();
          stmt.executeUpdate("update into XXXX..... where id = " + id + "'");
          stmt.executeUpdate("delete from XXXX..... where id = " + id + "'");
          stmt.executeUpdate(".... where id = " + id + "'");
          stmt.executeUpdate(".... where id = " + id + "'");
          conn.commit();
      }
      catch(Exception e){
          try{conn.rollback();}catch(Exception e){}
      }
      finally {
         if ( stmt != null ) try{stmt.close();}catch(Exception e){}
         conn.setAutoCommit(true);
      }
      .....
  }

  아무런 문제가 없을 듯도 합니다. 그러나 위의 서비스를 동시에 요청하게 되면, 같은 
  java.sql.Connection 을 갖고 작업을 하고 있으니 Transaction 이 중첩되게 됩니다.
  왜냐면, conn.commit(), conn.rollback() 과 같이 conn 이라는 Connection 에 대해서
  Transaction 이 관리되기 때문입니다. 요청 A 가 총 4개의 SQL문장 중 3개를 정상적으로
  수행하고 마지막 4번째의 SQL문장을 수행하려 합니다. 이 때 요청 B가 뒤따라 들어와서
  2개의 SQL 문장들을 열심히 수행했습니다.  근데, 요청 A에 의한 마지막 SQL 문장
  수행중에 SQLException 이 발생했습니다. 그렇담 요청 A 는 catch(Exception e) 절로
  분기가 일어나고 conn.rollback() 을 수행하여 이미 수행한 3개의 SQL 수행들을 모두
  rollback 시킵니다. 근데,,, 문제는 요청 B 에 의해 수행된 2개의 SQL문장들도 같이
  싸잡아서 rollback() 되어 버립니다. 왜냐면 같은 conn 객체니까요. 결국, 요청B 는
  영문도 모르고 마지막 2개의 SQL문장만 수행한 결과를 낳고 맙니다.

  따라서 정리하면, Connection, Statement, ResultSet 는 doGet() , doPost() 내에서
  선언되고 사용되어져야 합니다.

  public void doGet(HttpServletRequest req, HttpServletResponse res)  
      throws ServletException, IOException, SQLException 
  {
      Connection conn = null;  <----- 이곳으로 와야죠..
      Statement stmt = null; <-------
      ResultSet rs = null; <---------
      .....
  }




 3. Exception 이 발생했을 때도 Connection 은 닫혀야 한다 !!

  public void doGet(HttpServletRequest req, HttpServletResponse res)  
       throws ServletException, IOException, SQLException
  {
      String id = req.getParameter("id");

      Connection conn = DriverManager.getConnection("url...","id","password");
      Statement stmt = conn.createStatement();
      ResultSet rs = stmt.executeQuery("ssselect * from XXX where id = '" + id + "'");
      while(rs.next()) {
         ......  
      }  
      rs.close();
      stmt.close();
      conn.close();
      .....
  }

  위에선 뭐가 잘못되었을까요? 네, 사실 특별히 잘못된 것 없습니다. 단지 SQL문장에 오타가
  있다는 것을 제외하곤.... 근데, 과연 그럴까요?
  SQLException 이라는 것은 Runtime 시에 발생합니다. DB 의 조건이 맞지 않는다거나
  개발기간 중에 개발자의 실수로 SQL문장에 위처럼 오타를 적을 수도 있죠.
  문제는 Exception 이 발생하면 마지막 라인들 즉, rs.close(), stmt.close(), conn.close()
  가 수행되지 않는다는 것입니다.
  java.sql.Connection 은 reference 를 잃더라도 JVM(Java Virtual Machine)의 GC(Garbage 
  Collection) 대상이 아닙니다. 가뜩이나 모자라는 "DB연결자원"을 특정한 어플리케이션이
  점유하고 놓아 주지 않기 때문에 얼마안가 DB Connection 을 더이상 연결하지 못하는
  사태가 발생합니다.

  따라서 다음처럼 Exception 이 발생하든 발생하지 않든 반드시 java.sql.Connection 을 
  close() 하는 로직이 꼭(!) 들어가야 합니다.

  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");
      try {
        conn = DriverManager.getConnection("url...","id","password");
        stmt = conn.createStatement();
        rs = stmt.executeQuery("sselect * from XXX where id = '" + id + "'");
        while(rs.next()) {
         ......  
        }
        rs.close();
        stmt.close();
      }
      finally {
         if ( conn != null ) try {conn.close();}catch(Exception e){}
      }
      .....
  }

  참고로, 실프로젝트의 진단 및 튜닝을 나가보면 처음에는 적절한 응답속도가 나오다가
  일정한 횟수 이상을 호출하고 난 뒤부터 엄청 응답시간이 느려진다면, 십중팔구는 위처럼
  java.sql.Connection 을 닫지 않아서 생기는 문제입니다.
  가용한 모든 Connection 을 연결하여 더이상 연결시킬 Connection 자원을 할당할 수 없을
  때, 대부분 timewait 이 걸리기 때문입니다. 일단 DB관련한 작업이 들어오는 족족
  timewait에 빠질 경우, "어플리케이션서버"에서 동시에 처리할 수 있는 최대 갯수만큼
  호출이 차곡차곡 쌓이는 건 불과 몇분 걸리지 않습니다. 그 뒤부터는 애궂은 dummy.jsp
  조차 호출이 되지 않게 되고,   누군가는 "시스템 또 죽었네요"라며 묘한 웃음을 짓곤
  하겠죠....



4. Connection 뿐만 아니라 Statement, ResultSet 도 반드시 닫혀야 한다 !!

4.1  3번의 예제에서 Connection 의 close() 만 고려하였지 Statement 나 ResultSet 에 대한
  close는 전혀 고려 하지 않고 있습니다. 무슨 문제가 있을까요? Statement 를 닫지 않아도
  Connection 을 닫았으니 Statement 나 ResultSet 은 자동으로 따라서 닫히는 것 아니냐구요?
  천만의 말씀, 만만의 콩깎지입니다.

  만약, DB Connection Pooling 을 사용하지 않고 직접 JDBC Driver 를 이용하여 매번 DB
  연결을 하였다가 끊는 구조라면 문제가 없습니다.
  그러나 DB Connection Pooling 은 이젠 보편화되어 누구나 DB Connection Pooling 을 사용
  해야한다는 것을 알고 있습니다. 그것이 어플리케이션 서버가 제공해 주든, 혹은 작은
  서블렛엔진에서 운영하고 있다면 직접 만들거나, 인터넷으로 돌아다니는 남의 소스를 가져다
  사용하고 있을 겁니다.

  이처럼 DB Connection Pooling 을 사용하고 있을 경우는 Conneciton 이 실제 close()되는
  것이 아니라 Pool에 반환되어 지게 되는데, 결국 reference가 사리지지 않기 때문에 GC시점에
  자동 close되지 않게 됩니다.
  특정 Connection 에서 열어둔 Statement 를  close() 하지 않은채 그냥 반환시켜 놓게 되면,
  언젠가는 그 Connection 은 다음과 같은   SQLException 을 야기할 가능성을 내포하게 됩니다.

  Oracle :
    java.sql.SQLException : ORA-01000: maximum open cursor exceeded !!
                            (최대열기 커서수를 초과했습니다)
  UDB DB2 :
    COM.ibm.db2.jdbc.DB2Exception: [IBM][JDBC 드라이버] CLI0601E  유효하지 않은
      명령문 핸들 또는 명령문이 닫혔습니다. SQLSTATE=S1000
    COM.ibm.db2.jdbc.DB2Exception: [IBM][CLI Driver] CLI0129E  핸들(handle)이 
      더이상 없습니다. SQLSTATE=HY014        
    COM.ibm.db2.jdbc.DB2Exception: [IBM][CLI Driver][DB2/NT] SQL0954C  응용프로그램
      힙(heap)에 명령문을 처리하기 위해 사용 가능한 저장영역이 충분하지 않습니다.
      SQLSTATE=57011

  이유는 앞 2)번글에서 이미 언급드렸습니다. 보다 자세한 기술적 내용은 아래의 글을
  참고하세요.
  Connection/Statement 최대 동시 Open 수
  http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=jdbc&c=r_p&n=972287002

  따라서 또다시 3번의 소스는 다음과 같은 유형으로 고쳐져야 합니다.

  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");
      try {
        conn = ...<getConnection()>...; // (편의상 생략합니다.)
        stmt = conn.createStatement();
        rs = stmt.executeQuery("sselect * from XXX where id = '" + id + "'");
        while(rs.next()) {
         ......  
        }
        // rs.close();
        // stmt.close();
      }
      finally {
        if ( rs != null ) try {rs.close();}catch(Exception e){}
        if ( stmt != null ) try {stmt.close();}catch(Exception e){} // <-- !!!!
        if ( conn != null ) ...<releaseConnection()>...; // (편의상 생략)

      }
      .....
  }

 4.2 사실 위와 같은 구조에서, java.sql.Statement의 쿼리에 의한 ResultSet의 close()에
  대한 것은 그리 중요한 것은 아닙니다. ResultSet은 Statement 가 close() 될 때 함께
  자원이 해제됩니다. 따라서 다음과 같이 하셔도 됩니다.

  public void doGet(HttpServletRequest req, HttpServletResponse res)  
       throws ServletException, IOException, SQLException
  {
      Connection conn = null;
      Statement stmt = null;
      String id = req.getParameter("id");
      try {
        conn = ...<getConnection()>...;
        stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("sselect * from XXX where id = '" + id + "'");
        while(rs.next()) {
         ......  
        }
        rs.close(); //<--- !!!
      }
      finally {
        // if ( rs != null ) try {rs.close();}catch(Exception e){}
        if ( stmt != null ) try {stmt.close();}catch(Exception e){}
        if ( conn != null ) ...<releaseConnection()>...;
      }
      .....
  }


 4.3  가장 대표적으로 잘못 프로그래밍하고 있는 예를 들라면 다음과 같은 유형입니다.

  Connection conn = null;
  try {
      conn = ...<getConnection()>....;
      Statement stmt = conn.createStatement();
      stmt.executeUpdate("....");  <--- 여기서 SQLException 이 일어나면...
      .....
      .....
      .....  <-- 만약,이쯤에서 NullPointerException 이 일어나면 ?
      .....
      stmt.close(); <-- 이것을 타지 않음 !!!
  }
  finally{
     if ( conn != null ) ...<releaseConnection()>...;
  }


 4.4  Statement 가 close() 되지 않는 또 하나의 경우가 다음과 같은 경우입니다.

  Connection conn = null;
  Statement stmt = null;
  try {
    conn = .....
    stmt = conn.createStatement(); // ....(1)
    rs = stmt.executeQuery("select a from ...");
    .....
    rs.close();

    stmt = conn.createStatement(); // ....(2)
    rs = stmt.executeQuery("select b from ...");
    ....
  }
  finally{
    if ( rs != null ) try {rs.close();}catch(Exception e){}
    if ( stmt != null ) try{stmt.close();}catch(Exception e){}
    if ( conn != null ) ....
  }

  즉, 두번 stmt = conn.createStatement() 를 수행함으로써, 먼저 생성된 stmt 는 
  (2)번에 의해 그 reference 가 엎어쳐버리게 되어 영원히 close() 되지 않은채 
  남아 있게 됩니다.
  이 경우, (2)번 문장을 주석처리하여야 겠지요. 한번 생성된 Statement 로 여러번
  Query 를 수행하면 됩니다.

  Connection conn = null;
  Statement stmt = null;
  try {
    conn = .....
    stmt = conn.createStatement(); // ....(1)
    rs = stmt.executeQuery("select a from ...");
    .....
    rs.close();

    // stmt = conn.createStatement(); // <--- (2) !!!
    rs = stmt.executeQuery("select b from ...");
    ....
  }
  finally{
    if ( rs != null ) try {rs.close();}catch(Exception e){}
    if ( stmt != null ) try{stmt.close();}catch(Exception e){}
    if ( conn != null ) ....
  }


 4.5  Statement 뿐만 아니라 PreparedStatement 를 사용할 때로 마찬가지 입니다.
  ....
  PreparedStatement pstmt = conn.prepareStatement("select ....");
  ....
  이렇게 만들어진  pstmt 도 반드시 pstmt.close() 되어야 합니다.

  예를 들면, 다음과 같은 코드를 생각할 수 있습니다.

  Connection conn = null;
  try {
    conn = ...<getConnection()>...;
    PreparedStatement pstmt = conn.prepareStatement("select .... ?...?");
    pstmt.setString(1,"xxxx");
    pstmt.setString(2,"yyyy");
    ResultSet rs = pstmt.executeQuery(); <--- 여기서 SQLException 이 일어나면
    while(rs.next()){
      ....
    }
    rs.close();
    pstmt.close();  <-- 이것을 타지 않음 !!!
  }
  finally{
     if ( conn != null ) ...<releaseConnection()>...;
  }

  따라서 같은 맥락으로 다음과 같이 고쳐져야 합니다.
  
  Connection conn = null;
  PreparedStatement pstmt = null; // <-------- !!
  ResultSet rs = null;
  try {
    conn = ...<getConnection()>...;
    pstmt = conn.prepareStatement("select .... ?...?");
    pstmt.setString(1,"xxxx");
    pstmt.setString(2,"yyyy");
    rs = pstmt.executeQuery(); <--- 여기서 SQLException 이 일어나더라도...
    while(rs.next()){
      ....
    }
    //rs.close();
    //pstmt.close(); 
  }
  finally{
    if ( rs != null ) try {rs.close();}catch(Exception e){}
    if ( pstmt != null ) try {pstmt.close();}catch(Exception e){} // <-- !!!!
    if ( conn != null ) ...<releaseConnection()>...;
  }


 4.6  PreparedStatement 에 관련해서 다음과 같은 경우도 생각할 수 있습니다.

 4.6.1 앞서의 4.4에서 Statement를 createStatement() 연거푸 두번 생성하는 것과
  동일하게 PreparedStatement에서도 주의하셔야 합니다. 예를 들면 다음과 같습니다.

  Connection conn = null;
  PreparedStatement pstmt = null;
  ResultSet rs = null;
  try {
    conn = .....
    pstmt = conn.prepareStatement("select a from ..."); // ....(1)
    rs = pstmt.executeQuery();
    .....
    rs.close();

    pstmt = conn.prepareStatement("select b from ..."); // <--- (2) !!!
    rs = pstmt.executeQuery();
    ....
  }
  finally{
    if ( rs != null ) try {rs.close();}catch(Exception e){}
    if ( pstmt != null ) try{pstmt.close();}catch(Exception e){} // <--- (3)
    if ( conn != null ) ...<releaseConnection()>...;
  }

  Statement의 인스턴스는 conn.createStatement() 시에 할당되어 지는 반면, 
  PreparedStatement는 conn.prepareStatement("..."); 시에 할당되어 집니다. 위의 경우에서
  설령 마지막 finally 절에서 pstmt.close() 를 하고 있기는 하지만, (1)번에서 할당되어진
  pstmt 는 (2)에서 엎어쳤기 때문에 영원히 close() 되지 않게 됩니다. 따라서, 다음과
  같이 코딩되어야 한다는 것은 자명합니다.

  Connection conn = null;
  PreparedStatement pstmt = null;
  ResultSet rs = null;
  try {
    conn = .....
    pstmt = conn.prepareStatement("select a from ..."); // ....(1)
    rs = pstmt.executeQuery();
    .....
    rs.close();
    pstmt.close(); // <------- !!!!! 이처럼 여기서 먼저 close() 해야지요.

    pstmt = conn.prepareStatement("select b from ..."); // <--- (2)
    rs = pstmt.executeQuery();
    ....
  }
  finally{
    if ( rs != null ) try {rs.close();}catch(Exception e){}
    if ( pstmt != null ) try{pstmt.close();}catch(Exception e){} // <--- (3)
    if ( conn != null ) ...<releaseConnection()>...;
  }

  혹은 다음과 같이 서로 다른 두개의 PreparedStatement를 이용할 수도 있습니다.
  
  Connection conn = null;
  PreparedStatement pstmt1 = null;
  ResultSet rs1 = null;
  PreparedStatement pstmt2 = null;
  ResultSet rs2 = null;
  try {
    conn = .....
    pstmt1 = conn.prepareStatement("select a from ..."); // ....(1)
    rs1 = pstmt1.executeQuery();
    .....
    pstmt2 = conn.prepareStatement("select b from ..."); // <--- (2)
    rs2 = pstmt2.executeQuery();
    ....
  }
  finally{
    if ( rs1 != null ) try {rs1.close();}catch(Exception e){}
    if ( pstmt1 != null ) try{pstmt1.close();}catch(Exception e){} // <--- (3)
    if ( rs2 != null ) try {rs2.close();}catch(Exception e){}
    if ( pstmt2 != null ) try{pstmt2.close();}catch(Exception e){} // <--- (4)
    if ( conn != null ) ...<releaseConnection()>...;
  }


 4.6.2 아래는 앞서의 4.6.1과 같은 맥락인데, for loop 안에서 사용된 경우입니다.

  Connection conn = null;
  PreparedStatement pstmt = null;
  try {
    conn = ...<getConnection()>...;
    for(int i=0;i<10;i++){
      pstmt = conn.prepareStatement("update .... ?... where id = ?"); //... (1)
      pstmt.setString(1,"xxxx");
      pstmt.setString(2,"id"+(i+1) );
      int affected = pstmt.executeUpdate();
      if ( affected == 0 ) throw new Exception("NoAffected");
      else if ( affedted > 1 ) throw new Exception("TooManyAffected");
    }
  }
  finally{
     if ( pstmt != null ) try {pstmt.close();}catch(Exception e){} // ...(2)
     if ( conn != null ) ...<releaseConnection()>...;
  }

  이 경우가 실제 프로젝트 performace 튜닝을 가 보면 종종 발견되는 잘못된
  유형 중의 하나입니다. 핵심은 pstmt.prepareStatement("update..."); 문장을
  수행할 때 마다 내부적으로 pstmt 가 새로 할당된다는 것에 있습니다.
  결국, (1)번 문장이 for loop 을 돌면서 9개는 pstmt.close()가 되지 않고, 마지막
  하나의 pstmt 만  finally 절의 (2)번에서 close 되지요...
  따라서 다음처럼 고쳐져야 합니다.

  Connection conn = null;
  PreparedStatement pstmt = null;
  try {
    conn = ...<getConnection()>...;
    pstmt = conn.prepareStatement("update .... ?... where id = ?");
    for(int i=0;i<10;i++){
      pstmt.clearParameters();      
      pstmt.setString(1,"xxxx");
      pstmt.setString(2,"id"+(i+1) );
      int affected = pstmt.executeUpdate();
      if ( affected == 0 ) throw new Exception("NoAffected");
      else if ( affedted > 1 ) throw new Exception("TooManyAffected");
    }
  }
  finally{
     if ( pstmt != null ) try {pstmt.close();}catch(Exception e){}
     if ( conn != null ) ...<releaseConnection()>...;
  }

  PreparedStatement 라는 것이, 한번 파싱하여 동일한 SQL문장을 곧바로 Execution할 수
  있는 장점이 있는 것이고, 궁극적으로 위와 같은 경우에 효과를 극대화 할 수 있는
  것이지요.

  어느 개발자의 소스에서는 위의 경우를 다음과 같이 for loop 안에서 매번
  conn.prepareStatement(...)를 하는 경우를 보았습니다.

  Connection conn = null;
  
  try {
    conn = ...<getConnection()>...;
    for(int i=0;i<10;i++) {
        PreparedStatement pstmt = null;
        try{
            pstmt = conn.prepareStatement("update .... ?... where id = ?"); 
            pstmt.clearParameters();      
            pstmt.setString(1,"xxxx");
            pstmt.setString(2,"id"+(i+1) );
            int affected = pstmt.executeUpdate();
            if ( affected == 0 ) throw new Exception("NoAffected");
            else if ( affedted > 1 ) throw new Exception("TooManyAffected");
        }
        finally{
            if ( pstmt != null ) try {pstmt.close();}catch(Exception e){}
        }
    }
  }
  finally{
     if ( conn != null ) ...<releaseConnection()>...;
  }

  위 경우는 장애관점에서 보면 사실 별 문제는 없습니다. 적어도 닫을 건 모두 잘 닫고
  있으니까요. 단지 효율성의 문제가 대두될 수 있을 뿐입니다.

 4.7 생각해 보면, Statement 나 PreparedStatement 가 close() 되지 않는 유형은
  여러가지가 있습니다. 그러나 열려진 Statement는 반드시 close()되어야 한다라는
  단순한 사실에 조금만 신경쓰시면 어떻게 프로그래밍이 되어야 하는지 쉽게
  감이 오실 겁니다. 단지 신경을 안쓰시니 문제가 되는 것이지요...

  Statement 를 닫지 않는 실수를 한 코드가 400-1000여개의 전 어플리케이션을 통털어
  한두군데에 숨어 있는 경우가 제일 찾아내기 어렵습니다. 한두번 Statement 를
  close 하지 않는다고 하여 곧바로 문제로 나타나지 않기 때문입니다.
  하루나 이틀, 혹은 며칠지나서야, Oracle 의 경우, "maximum open cursor exceed"
  에러를 내게 됩니다. oracle 의 경우, 위와 같은 에러를 conn.createStatement()시에
  발생하므로 (경우에 따라) 큰 문제로 이어지지는 않습니다. 찾아서 고치면 되니까요.

  반면, DB2 의 경우는 메모리가 허용하는 한도까지 지속적인 메모리 증가를 야기합니다.
  급기야 "핸들(handle)이 더이상 없습니다", "응용프로그램 힙(heap)에 명령문을 
  처리하기 위해 사용 가능한 저장영역이 충분하지 않습니다", "유효하지 않은 명령문
  핸들 또는 명령문이 닫혔습니다" 등과 같은 에러를 내지만, 여기서 그치는 것이 아니라
  새로운 connection 을 맺는 시점에 SIGSEGV 를 내면 crashing 이 일어납니다.
  java.lang.OutOfMemoryError 가 발생하기 때문이지요.

  Oracle 의 경우도 사실 상황에 따라 심각할 수 있습니다. 예를 들어, 어떤 개발자가
  "maximum open cursor exceed"라는 Oracle SQL 에러 메세지를 만나고는 자신이
  코딩을 잘못한 것은 염두에 두지 않고, 무조건 DBA에게 oraXXX.ini 파일에서
  OPEN_CURSORS 값을 올려달라고 요청하고, DBA는 그 조언(?)을 충실히 받아들여 
  Default 50 에서 이를 3000 으로 조정합니다. 여기서 문제는 깊숙히(?) 숨겨집니다.
  close() 안된 Statement 가 3000 회에 도달하지 전까진 아무도 문제를 인식하지
  못하기 때문이죠. 그러나, Statement가 하나씩 close()되지 않은 갯수에 비례하여
  Oracle 엔진의 메모리 사용은 자꾸만 증가하고, 전체적인 성능저하를 야기하지만, 
  이를 인식하기란 막상 시스템을 오픈하고 나서 3-4일이 지난 후에 "DB성능이 이상하네,
  응답속도가 느리네" 하면서 또 한번의 "maximum open cursor exceed" 메세지를
  확인하고 난 뒤의 일이 되곤 합니다.

  에러가 없는 정상적인 로직 flow 에서는 대부분 Statement가 잘 닫힐 겁니다. 그러나,
  어떤 이유에서건 아주 드물게 Runtime Exception 이 발생하여 exception throwing으로
  인해 "stmt.close()"를 타지 않는 경우가 제일 무섭지요. 정말 무섭지요...

  Statement, PreparedStatement, CallableStatement 모두 마찬가지 입니다.



5. close() 를 할 땐 제대로 해야 한다!!

 5.1 다음과 같은 프로그램 형식을 생각할 수 있습니다.

  Connection conn = null;
  Statement stmt = null;
  ResultSet rs = null;
  try{
     conn = ...<getConnection()>...; //.......(1)
     stmt = conn.createStatement();    //.............(2)
     rs = stmt.executeQuery("select .....");  // .....(3)
     while(rs.next()){
       ......
     }
  }
  finally {
     try {
        rs.close(); //........(4)
        stmt.close(); //......(5)
        ...<releaseConneciton()>...; //......(6)
     }catch(Exception e){}
  }

  위에선 뭐가 잘못되었을까요? 다 제대로 한듯 한데....
  finally 절에서 rs, stmt, conn 을 null check 없이, 그리고 동일한 try{}catch 절로
  실행하고 있습니다.
  예를 들어, (1), (2) 번을 거치면서 conn 과 stmt 객체까지는 제대로 수행되었으나
  (3)번 Query문장을 수행하는 도중에 SQLException 이 발생할 수 있습니다. 그러면, 
  finally  절에서 (4) 번 rs.close()를 수행하려 합니다. 그러나, executeQuery()가
  실패 했기 때문에 rs 의 reference 는 null 이므로 rs.close() 시에 
  NullPointerException 이 발생합니다.  결국 정작 반드시 수행되어야할 (5)번, (6)번이
  실행되지 않습니다. 따라서 반드시(!) 다음과 같은 형식의 코딩이 되어야 합니다.

  Connection conn = null;
  Statement stmt = null;
  ResultSet rs = null;
  try{
     conn = ...<getConnection()>...;
     stmt = conn.createStatement();
     rs = stmt.executeQuery("select .....");
     while(rs.next()){
       ......
     }
  }
  catch(Exception e){
     ....
  }
  finally {
     if ( rs != null ) try{rs.close();}catch(Exception e){}
     if ( stmt != null ) try{stmt.close();}catch(Exception e){}
     if ( conn != null ) ...<releaseConnection()>...;
  }

 같은 맥락으로 PreparedStatement 를 사용할 경우도, 다음과 같은 코딩은 잘못되었습니다.

  Connection conn = null;
  PreparedStatement pstmt = null;
  try{
     conn = ...<getConnection()>...; //.......(1)
     pstmt = conn.prepareStatement("ddelete from EMP where empno=7942"); //...(2)
     int k = pstmt.executeUpdate();  // .....(3)
     ......
  }
  finally {
     try {
        pstmt.close(); //......(4)
        ...<releaseConneciton()>...; //......(5)
     }catch(Exception e){}
  }
  왜냐면, SQL문장에 오타가 있었고, 이는 prepareStatement("ddelete..")시점에 에러가
  발생하여 pstmt 가 여전히 null 인 상태로 finally 절로 분기되기 때문인 것이죠.

 5.2.0 앞서 4.2에서도 언급했지만, java.sql.Statement의 executeQuery()에 의한 ResultSet은
  Statement 가 close 될때 자원이 같이 해제되므로 다음과 같이 하여도 그리 문제가 되진
  않습니다. 
     
  Connection conn = null;
  Statement stmt = null;
  //ResultSet rs = null;
  try{
     conn = ...<getConnection()>...;
     stmt = conn.createStatement();
     ResultSet rs = stmt.executeQuery("select .....");
     while(rs.next()){
       ......
     }
     rs.close(); // <---- !!!
  }
  catch(Exception e){
     ....
  }
  finally {
     //if ( rs != null ) try{rs.close();}catch(Exception e){}
     if ( stmt != null ) try{stmt.close();}catch(Exception e){}
     if ( conn != null ) ...<releaseConnection()>...;
  }


5.2.1  그러나 PreparedStatement에 의한 ResultSet close()는 얘기가 다를 수 있습니다.
  (2002.06.11 추가 사항)
  
  많은 분들이, "아니 설령 ResultSet를 close하지 않았더라도 Statement/PreparedStatement를
  close하면 함께 ResultSet로 close되는 것 아니냐, JDBC 가이드에서도 그렇다고
  나와 있다, 무슨 개뿔같은 소리냐?" 라구요.

  그러나, 한가지 더 이해하셔야 할 부분은, 웹스피어, 웹로직과 같은 상용 웹어플리케이션
  서버들은 성능향상을 위해 PreparedStatement 및 심지어 Statement에 대한 캐싱기능을
  제공하고  있습니다. pstmt.close() 를 하였다고 하여 정말 해당 PreparedStatement가
  close되는 것이 아니라, 해당 PreparedeStatement가 생겨난 java.sql.Connection당 지정된
  개수까지 웹어플리케이션서버 내부에서 reference가 사라지지 않고 캐시로 남아 있게 됩니다.
  결국, 연관된 ResultSet 역시 close()가 되지 않은 채 남아있게 되는 것이지요. 명시적인
  rs.close()를 타지 않으면, 웹어플리케이션서버의 JVM내부 힙영역 뿐만아니라, 쿼리의 결과로
  데이타베이스에서 임시로 만들어진 메모리데이타가 사라지지 않는 결과를 낳게 됩니다. 
  특히 ResultSet이 닫히지 않으면 DB에서의 CURSOR가 사라지지 않습니다. 
  또한, CURSOR를 자동으로 닫거나 닫지 않는 등의 옵션은 WAS마다 WAS의 DataSource설정에서
  CORSOR 핸들링에 대한 별도의 옵션을 제공하게 되며 특히 아래 [항목6]에서 다시 언급할
  nested sql query 처리시에 민감한 반응을 하게 됩니다.

  따라서, 앞서의 코딩 방법을 반드시 다음처럼 ResultSet도 close하시기를 다시금 정정하여
  권장 드립니다.

  Connection conn = null;
  PreparedStatement pstmt = null;
  ResultSet rs = null; // <---- !!!
  try{
     conn = ...<getConnection()>...;
     pstmt = conn.prepareStatement("select .....");
     rs = pstmt.executeQuery(); // <----- !!!
     while(rs.next()){
       ......
     }
     //rs.close(); // <---- !!!
  }
  catch(Exception e){
     ....
  }
  finally {
     if ( rs != null ) try{rs.close();}catch(Exception e){} // <---- !!!
     if ( pstmt != null ) try{pstmt.close();}catch(Exception e){}
     if ( conn != null ) ...<releaseConnection()>...;
  }

  PS: 웹어플리케이션서버에서의 PreparedStatement 캐싱기능에 관한 부분은 아래의 글들을
  참조하세요.
  Connection Pool & PreparedStatement Cache Size
  http://www.javaservice.net/~java/bbs/read.cgi?m=appserver&b=was&c=r_p&n=995572195
  WebLogic에서의 PreparedStatement Cache -서정희-
  http://www.javaservice.net/~java/bbs/read.cgi?m=dbms&b=jdbc&c=r_p&n=1023286823



 5.3 간혹, 다음과 같은 코딩은 문제를 야기하지 않을 것이라고 생각하는 분들이 많습니다.

 finally{
    try{
      if ( stmt != null ) stmt.close();
      if ( conn != null ) conn.close();
    }catch(Exception e){}
 }
 저명한 많은 책에서도 위처럼 코딩되어 있는 경우를 종종봅니다. 맞습니다. 특별히 문제성이
 있어보이지는 않습니다. 그러나, 간혹 웹어플리케이션서버의 Connection Pool과 연계하여
 사용할 땐 때론 문제가 될 때도 있습니다. 즉, 만약, stmt.close()시에 Exception이 throw
 되면 어떻게 되겠습니까? 
 아니 무슨 null 체크까지 했는데, 무슨 Exception이 발생하느냐고 반문할 수도 있지만,
 오랜 시간이 걸리는 SQL JOB에 빠져 있다가 Connection Pool의 "Orphan Timeout"이 지나
 자동으로 해당 Connection을 Pool에 돌려보내거나 혹은 특별한 marking처리를 해 둘 수
 있습니다. 이 경우라면 stmt.close()시에 해당 웹어플리케이션서버에 특화된 Exception이
 발생하게 됩니다. 그렇게 되면 conn.close()를 타지 못하게 되는 사태가 벌어집니다.
 따라서, 앞서 하라고 권장한 형식으로 코딩하세요.


6. Nested (Statemet) SQL Query Issue !!

 6.1 아래와 같은 코딩을 생각해 보겠습니다.

  Connection conn = null;
  Statement stmt = null;
  try{
     conn = ...<getConnection()>...;
     stmt = conn.createStatement();
     ResultSet rs = stmt.executeQuery("select deptno ...where id ='"+ id +"'");
     while(rs.next()){
       String deptno = rs.getString("deptno");
       stmt.executeUpdate(
           "update set dept_name = ... where deptno = '"+ deptno +"'"
       );
       ......
     }
     rs.close();
  }
  catch(Exception e){
     ....
  }
  finally {
     if ( stmt != null ) try{stmt.close();}catch(Exception e){}
     if ( conn != null ) ...<releaseConnection()>...;
  }

  위 코드는 사실 실행하자 말자 다음과 같은 에러를 만나게 됩니다.

  DB2 : -99999: [IBM][CLI Driver] CLI0115E 커서 상태가 유효하지 않습니다. 
        SQLSTATE=24000 
  Oracle : 

  에러는 두번째 while(rs.next()) 시에 발생합니다. 왜냐면, Statement가 nested하게
  실행된 executeUpdate() 에 의해 엎어쳐 버렸기 때문입니다. ResultSet 은 
  Statement와 밀접하게 관련이 있습니다. ResultSet은 자료를 저장하고 있는 객체가
  아니라, 데이타베이스와 step by step으로 상호 통신하는 Interface 일 뿐이기 때문입니다.
  따라서 위 코드는 다음처럼 바뀌어져야 합니다.

  Connection conn = null;
  Statement stmt1 = null;
  Statement stmt2 = null;
  try{
     conn = ...<getConnection()>...;
     stmt1 = conn.createStatement();
     stmt2 = conn.createStatement();
     ResultSet rs = stmt1.executeQuery("select deptno ...where id ='"+ id +"'");
     while(rs.next()){
       String deptno = rs.getString("deptno");
       stmt2.executeUpdate(
           "update set dept_name = ... where deptno = '"+ deptno +"'"
       );
       ......
     }
     rs.close();
  }
  catch(Exception e){
     ....
  }
  finally {
     if ( stmt1 != null ) try{stmt1.close();}catch(Exception e){}
     if ( stmt2 != null ) try{stmt2.close();}catch(Exception e){}
     if ( conn != null ) ...<releaseConnection()>...;
  }

  즉, ResultSet에 대한 fetch 작업이 아직 남아 있는 상태에서 관련된 Statement를
  또다시 executeQuery()/executeUpdate() 를 하면 안된다는 것입니다.

  PS: IBM WebSphere 환경일 경우, 아래의 글들을 추가로 확인하시기 바랍니다.
  349   Re: Function sequence error  (Version 3.x)
  http://www.javaservice.net/~java/bbs/read.cgi?m=appserver&b=was&c=r_p&n=991154615
  486   WAS4.0x: Function sequence error 해결  
  http://www.javaservice.net/~java/bbs/read.cgi?m=appserver&b=was&c=r_p&n=1015345459



7. executeUpdate() 의 결과를 비즈니스 로직에 맞게 적절히 활용하라.

 7.1 아래와 같은 코딩을 생각해 보겠습니다.

  public void someMethod(String empno) throws Exception {
      Connection conn = null;
      Statement stmt = null;
      try{
         conn = ...<getConnection()>...;
         stmt = conn.createStatement();
         stmt.executeUpdate(
             "UPDATE emp SET name='이원영' WHERE empno = '" + empno + "'"
         );
      }
      finally {
         if ( stmt != null ) try{stmt.close();}catch(Exception e){}
         if ( conn != null ) ...<releaseConnection()>...;
      }
  }

  사실 흔히들 이렇게 하십니다. 별로 잘못된 것도 없어보입니다. 근데, 만약 
  DB TABLE에 해당 empno 값이 없으면 어떻게 될까요? SQLException 이 발생
  하나요? 그렇지 않죠? 아무런 에러없이 그냥 흘러 내려 옵니다. 그러면, Update를
  하러 들어 왔는데, DB에 Update할 것이 없었다면 어떻게 해야 합니까? 그냥 무시하면
  되나요? 안되죠..  따라서, 다음 처럼, 이러한 상황을 이 메소드를 부른 곳으로
  알려 줘야 합니다.

  public void someMethod(String empno) throws Exception {
      Connection conn = null;
      Statement stmt = null;
      try{
         conn = ...<getConnection()>...;
         stmt = conn.createStatement();
         int affected = stmt.executeUpdate(
             "UPDATE emp SET name='이원영' WHERE empno = '" + empno + "'"
         );
         if ( affected == 0 ) throw new Exception("NoAffectedException");
         else if ( affected > 1 ) throw new Exception("TooManyAffectedException");
      }
      finally {
         if ( stmt != null ) try{stmt.close();}catch(Exception e){}
         if ( conn != null ) ...<releaseConnection()>...;
      }
  }

  물론 이러한 부분들은 해당 비즈니스로직이 뭐냐에 따라서 다릅니다. 그것을 무시케
  하는 것이 비즈니스 로직이었다면 그냥 무시하시면 되지만, MIS 성 어플리케이션의
  대부분은 이처럼 update 나 delete 쿼리의 결과에 따라 적절한 처리를 해 주어야
  할 것입니다.



8. Transaction 처리를 할 땐 세심하게 해야 한다.

 단, 아래는 웹어플리케이션서버의 JTA(Java Transaction API)기반의 트렌젝션처리가 아닌,
 java.sql.Connection에서의 명시적인 conn.setAutoCommit(false) 모드를 통한 트렌젝션처리에
 대해서 언급 하고 있습니다.

 8.1 Non-XA JDBC Transaction(명시적인 java.sql.Connection/commit/rollback)

  Connection conn = null;
  Statement stmt = null;
  try{
     conn = ...<getConnection()>...;
     stmt = conn.createStatement();
     stmt.executeUpdate("UPDATE ...."); // -------- (1)
     stmt.executeUpdate("DELETE ...."); // -------- (2)
     stmt.executeUpdate("INSERT ...."); // -------- (3)
  }
  finally {
     if ( stmt != null ) try{stmt.close();}catch(Exception e){}
     if ( conn != null ) ...<releaseConnection()>...;
  }

  위와 같은 코딩은 아무리 비즈니스적 요구가 간소하더라도, 실 프로젝트에서는
  있을 수가 없는 코딩입니다.(JTS/JTA가 아닌 명시적인 Transaction 처리시에..)

  다들 아시겠지만, (1), (2) 번까지는 정상적으로 잘 수행되었는데, (3)번문장을
  수행하면서 SQLException 이 발생하면 어떻게 되나요? (1),(2)은 이미 DB에 반영된
  채로 남아 있게 됩니다. 대부분의 비즈니스로직은 그렇지 않았을 겁니다.
  
  "(1),(2),(3)번이 모두 정상적으로 수행되거나, 하나라도 잘못되면(?) 모두 취소
  되어야 한다" 
  
  가 일반적인 비즈니스적 요구사항이었을 겁니다. 따라서, 다음처럼 코딩 되어야
  합니다.

  Connection conn = null;
  Statement stmt = null;
  try{
     conn = ...<getConnection()>...;
     conn.setAutoCommit(false);
     stmt = conn.createStatement();
     stmt.executeUpdate("UPDATE ...."); // -------- (1)
     stmt.executeUpdate("DELETE ...."); // -------- (2)
     stmt.executeUpdate("INSERT ...."); // -------- (3)

     conn.commit(); // <-- 반드시 try{} 블럭의 마지막에 와야 합니다.
  }
  catch(Exception e){
     if ( conn != null ) try{conn.rollback();}catch(Exception ee){}
     // error handling if you want
     
     throw e;  // <--- 필요한 경우, 호출한 곳으로 Exception상황을 알려줄
               //      수도 있습니다
  }
  finally {
     if ( stmt != null ) try{stmt.close();}catch(Exception e){}
     // in some connection pool, you have to reset commit mode to "true"
     if ( conn != null ) ...<releaseConnection()>...;
  }


 8.2 auto commit mode 를 "false"로 셋팅하여 명시적인 Transaction 관리를 할 때,
  정말 조심해야 할 부분이 있습니다. 예를 들면 다음과 같은 경우입니다.

  Connection conn = null;
  Statement stmt = null;
  try{
     conn = ...<getConnection()>...;
     conn.setAutoCommit(false);
     stmt = conn.createStatement();
     stmt.executeUpdate("UPDATE ...."); // ----------------------- (1)
     ResultSet rs = stmt.executeQuery("SELECT ename ..."); // ---- (2)
     if ( rs.next() ) {
        conn.commit(); // ------------------- (3)
     }
     else {
        conn.rollback(); // ----------------- (4)
     }
  }
  finally {
     if ( rs != null ) try{rs.close();}catch(Exception e){}
     if ( stmt != null ) try{stmt.close();}catch(Exception e){}
     // in some connection pool, you have to reset commit mode to "true"
     if ( conn != null ) ...<releaseConnection()>...;
  }

  코드가 왜 위처럼 됐는지는 저도 모르겠습니다.  단지 비즈니스가 그러했나보죠..

  문제는 만약, (2)번 executeQuery()문장을 수행하면서 SQLException 이나 기타의
  RuntimeException 이 발생할 때 입니다.
  commit() 이나 rollback()을 타지 않고, finally 절로 건너 뛰어 Statement를
  닫고, connection 은 반환됩니다. 이때, commit() 이나 rollback()이 되지 않은채
  (1)번 UPDATE 문이 수행된채로 남아 있게 됩니다.  이는 DB LOCK을 점유하게
  되고, 경우에 따라 다르겠지만, 다음번 요청시에 DB LOCK으로 인한 hang현상을
  초래할 수도 있습니다.
  일단 한두개의 어플리케이션이 어떠한 이유였든, DB Lock 을 발생시키면, 해당
  DB에 관련된 어플리케이션들이 전부 응답이 없게 됩니다. 이 상황이 조금만
  지속되면, 해당 waiting 을 유발하는 요청들이 어플리케이션서버의 최대 동시
  접속처리수치에 도달하게 되고, 이는 전체 시스템의 hang현상으로 이어지게
  되는 것이죠..


  따라서, 비즈니스 로직이 어떠했든, 반드시 지켜져야할 사항은 다음과 같습니다.

  "conn.setAutoComm(false); 상태에서 한번 실행된 Update성 SQL Query는 이유를
   막론하고 어떠한 경우에도 commit() 이나 rollback() 되어야 한다."

  위의 경우라면 다음처럼 catch 절에서 rollback 시켜주는 부분이 첨가되면 되겠네요.

  Connection conn = null;
  Statement stmt = null;
  try{
     conn = ...<getConnection()>...;
     conn.setAutoCommit(false);
     stmt = conn.createStatement();
     stmt.executeUpdate("UPDATE ...."); 
     ResultSet rs = stmt.executeQuery("SELECT ename ...");
     if ( rs.next() ) {
        conn.commit();
     }
     else {
        conn.rollback();
     }
  }
  catch(Exception e){  // <---- !!!!!
     if ( conn != null ) try{conn.rollback();}catch(Exception ee){}
     throw e;
  }
  finally {
     if ( rs != null ) try{rs.close();}catch(Exception e){}
     if ( stmt != null ) try{stmt.close();}catch(Exception e){}
     // in some connection pool, you have to reset commit mode to "true"
     if ( conn != null ) ...<releaseConnection()>...;
  }


 8.3 모든 경우의 수를 생각하라.

  다음과 같은 경우를 생각해 보겠습니다.

  Connection conn = null;
  Statement stmt = null;
  try{
     conn = ...<getConnection()>...;
     conn.setAutoCommit(false);
     stmt = conn.createStatement();
     stmt.executeUpdate("UPDATE ...."); 
     String idx = name.substring(3);
     ResultSet rs = stmt.executeQuery("SELECT ename ... where idx=" + idx);
     if ( rs.next() ) {
        .....
     }
     rs.close(); rs = null;

     stmt.executeUpdate("UPDATE ...."); 
     conn.commit();
  }
  catch(SQLException e){ 
     if ( conn != null ) try{conn.rollback();}catch(Exception ee){}
     throw e;
  }
  finally {
     if ( rs != null ) try{rs.close();}catch(Exception e){}
     if ( stmt != null ) try{stmt.close();}catch(Exception e){}
     // in some connection pool, you have to reset commit mode to "true"
     if ( conn != null ) ...<releaseConnection()>...;
  }

  잘 찾아 보세요. 어디가 잘못되었습니까? 잘 안보이시죠?

  Connection conn = null;
  Statement stmt = null;
  try{
     conn = ...<getConnection()>...;
     conn.setAutoCommit(false);
     stmt = conn.createStatement();
     stmt.executeUpdate("UPDATE ...."); //---- (1)
     String idx = name.substring(3); //<------ (2) NullPointerExceptoin ?
     ResultSet rs = stmt.executeQuery("SELECT ename ... where idx=" + idx);
     if ( rs.next() ) {
        .....
     }
     rs.close(); rs = null;

     stmt.executeUpdate("UPDATE ...."); 
     conn.commit();
  }
  catch(SQLException e){ //<------ (3) 이 부분을 탈까?
     if ( conn != null ) try{conn.rollback();}catch(Exception ee){}
     throw e;
  }
  finally {
     if ( rs != null ) try{rs.close();}catch(Exception e){}
     if ( stmt != null ) try{stmt.close();}catch(Exception e){}
     // in some connection pool, you have to reset commit mode to "true"
     if ( conn != null ) ...<releaseConnection()>...;
  }

  위 코드를 보듯, 만약 (1)을 수행 후 (2)번 에서 NullPointerException 이나 
  ArrayIndexOutOfBoundException이라도 나면 어떻게 되죠? catch(SQLException ...)에는
  걸리지 않고 곧바로 finally 절로 건너띄어 버리네요. 그럼 (1)에서 update 된 것은
  commit()이나 rollback() 없이 connection 이 반환되네요... ;-)
  어떻게 해야 합니까? SQLException만 잡아서 되는 것이 아니라, catch(Exception ...)과
  같이 모든 Exception 을 catch해 주어야 합니다.


 8.4 위 주석문에서도 언급해 두었지만, Hans Bergsteins 의 DBConnectionManager.java
  와 같은 Connection Pool 을 사용할 경우에, 개발자의 코드에서 transaction auto
  commit mode 를 명시적으로 "false"로 한 후, 이를 그냥 pool 에 반환하시면,
  그 다음 사용자가 pool 에서 그 connection 을 사용할 경우, 여전히 transaction
  mode 가 "false"가 된다는 것은 주지하셔야 합니다. 따라서, DBConnectionManger의
  release method를 수정하시든지, 혹은 개발자가 명시적으로 초기화한 후 pool 에
  반환하셔야 합니다. 그렇지 않을 경우, DB Lock 이 발생할 수 있습니다.
  반면, IBM WebSphere 나 BEA WebLogic 과 같인 JDBC 2.0 스펙에 준하는 Connection
  Pool을 사용할 경우는 반환할 당시의 transaction mode 가 무엇이었든 간에,
  pool 에서 꺼내오는 connection 의 transaction mode는 항상 일정합니다.
  (default 값은 엔진의 설정에 따라 달라집니다.)
  그렇다고 commit 시키지 않은 실행된 쿼리가 자동으로 commit/rollback 되는 것은
  아닙니다. 단지 auto commit 모드만 자동으로 초기화 될 뿐입니다.

  PS:WAS의 JTS/JTA 트렌젝션 기반으로 운영될 경우는 명시적으로 commit/rollback되지
   않은 트렌젝션은 자동으로 rollback되거나 commit됩니다. default는 WAS 설정에 따라
   다를 수 있습니다.

  ---------------
  NOTE: 자바서비스컨설팅의 WAS성능관리/모니터링 툴인 제니퍼(Jennifer 2.0)를 적용하면,
  어플리케이션에서 명시적으로 commit/rollback시키지 않고 그대로 반환시킨 어플리케이션의
  소스 위치를 실시간으로 감지하여 알려줍니다. 이를 만약 수작업으로 한다면, 수많은 코드
  중 어디에서 DB lock을 유발 시키는 코드가 숨어있는지를 찾기가 경우에 따라 만만치 않은
  경우가 많습니다.

8.5 XA JDBC Driver, J2EE JTS/JTA
  JDBC 2.0, 표준 javax.sql.DataSource를 통한 JDBC Connection을 사용할 경우에,
  대부분의 상용WAS제품들은 J2EE의 표준 JTS(Java Transaction Service)/JTA(Java Transaction
  API)를 구현하고 있습니다. 특별히, 하나 이상의 데이타베이스에서 2 phase-commit과
  같은 XA Protocol를 지원하고 있지요(사실 WAS에서 2PC가 지원되기 시작한 것은 몇년
  되지 않습니다. 2PC를 사용하려면 반드시 XA-JDBC Driver가 WAS에 설치되어야 합니다)

  샘플 예제는 다음과 같습니다.

    ...
    javax.transaction.UserTransaction tx = null;
    java.sql.Connection conn1 = null;
    java.sql.Statement stmt1 = null;

    java.sql.Connection conn2 = null;
    java.sql.Statement stmt2 = null;
    java.sql.CallableStatement cstmt2 = null;
    
    try {
        javax.naming.InitialContext ctx = new javax.naming.InitialContext();
        tx = (javax.transaction.UserTransaction) ctx.lookup("java:comp/UserTransaction");
        // 트렌젝션 시작
        tx.begin();

        // -------------------------------------------------------------------------
        // A. UDB DB2 7.2 2PC(XA) Test
        // -------------------------------------------------------------------------
        javax.sql.DataSource ds1 = 
            (javax.sql.DataSource)ctx.lookup("java:comp/env/jdbc/DB2");
        conn1 = ds1.getConnection();

        stmt1 = conn1.createStatement();
        stmt1.executeUpdate(
            "insert into emp(empno,ename) values(" + empno + ",'Name" + empno + "')"
        );
        stmt1.executeUpdate(
            "update emp set ename = 'LWY" + count + "' where empno = 7934"
        );
        java.sql.ResultSet rs1 = stmt1.executeQuery("select empno,ename from emp");
        while(rs1.next()){
            ...
        }
        rs1.close();
        
        // -------------------------------------------------------------------------
        // B. Oracle 8.1.7 2PC(XA) Test
        // -------------------------------------------------------------------------
        javax.sql.DataSource ds2 =
            (javax.sql.DataSource)ctx.lookup("java:comp/env/jdbc/ORA8i");
        conn2 = ds2.getConnection();
        
        stmt2 = conn2.createStatement();
        stmt2.executeUpdate(
            "update emp set ename = 'LWY" + count + "' where empno = 7934"
        );
        java.sql.ResultSet rs2 = stmt2.executeQuery("select empno,ename from emp");
        while(rs2.next()){
            ...
        }
        rs2.close();


        // -------------------------------------------------------------------------
        // 트렌젝션 commit
        tx.commit();
    }
    catch(Exception e){
        // 트렌젝션 rollback
        if ( tx != null ) try{tx.rollback();}catch(Exception ee){}
        ...
    }
    finally {
        if ( stmt1 != null ) try { stmt1.close();}catch(Exception e){}
        if ( conn1 != null ) try { conn1.close();}catch(Exception e){}

        if ( stmt2 != null ) try { stmt2.close();}catch(Exception e){}
        if ( conn2 != null ) try { conn2.close();}catch(Exception e){}
    }

  

NOTE: 위에서 설명한 하나하나가 제 입장에서 보면 너무나 가슴깊이 다가오는 
  문제들입니다. 개발하시는 분의 입장에서 보면, 위의 가이드에 조금 어긋났다고
  뭐그리 문제겠느냐고 반문하실 수 있지만, 수백본의 소스코드 중에 위와 같은 규칙을
  준수하지 않은 코드가  단 하나라도 있다면, 잘 운영되던 시스템이 며칠에 한번씩
  에러를 야기하거나 응답이 느려지고 급기야 hang 현상을 초래하는 결과를 가져 옵니다.
  정말(!) 그렇습니다.

NOTE: 위에서 사용한 코딩 샘플들은 JDBC Connection Pooling 은 전혀 고려치 않고
  설명드렸습니다. 그러했기 때문에 <getConnection()>, <releaseConnection()> 이란
  Pseudo 코드로 설명했던 것입니다.
  반드시 "서블렛 + JDBC 연동시 코딩 고려사항 -제2탄-" 을 읽어 보세요.
  http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=servlet&c=r_p&n=968522077


-------------------------------------------------------  
  본 문서는 자유롭게 배포/복사 할 수 있으나 반드시
  이 문서의 저자에 대한 언급을 삭제하시면 안됩니다
================================================
  자바서비스넷 이원영
  E-mail: javaservice@hanmail.net
  PCS:010-6239-6498
================================================

'java' 카테고리의 다른 글

JSON-PRC-java 적용하기  (0) 2006.02.25
서블렛 + JDBC 연동시 코딩 고려사항 -제2탄-  (0) 2006.02.21
리소스 누수현상  (0) 2006.02.21
java.sql.SQLException: IO 예외 상황: Broken pipe - thread  (0) 2006.01.31
Jakarta POI - 쓰기  (0) 2005.11.30
Posted by 알 수 없는 사용자
|

리소스 누수현상

java 2006. 2. 21. 10:07

http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=consult&c=r_p&n=1106886851

제목 : 긴급! 리소스 누수 현상 ㅜ.ㅜ
글쓴이: 김영근(naucika) 2005/01/28 13:34:11 조회수:7258 줄수:41
안녕하세요. 
이번에 사내 SFA 서비스가 말썽이 생겨서 도움을 좀 부탁드립니다.
프로젝트가 끝난지는 2년이 다 되가는데요.
이번에 자주 웹서버가 다운되는 바람에 애를 먹고 있습니다.
도대체 원인을 모르겠고, 뭘 더 어떻게 해야할지 모르겠네요.

환경을 말씀드리면,
JBOSS 3.2.5 구요. 서버는 제온 프로세서 2, 메모리 1G 정도입니다. 윈도2003 서버구요.
두개의 서비스가 구동중인데요.
하나는 SFA(영업관리 시스템), 또 하나는 채용시스템입니다.

SFA는 초기모델이라 자체 프레임웍에 서블릿 JSP로 구성되어 있고, JBOSS에서 제공하고 
있는 DB POOL을 사용합니다.
채용시스템은 하이버네이트+스트럿츠+서블릿+JSP+스프링 프레임웍등으로 
구성되어 있구요 하이버네이트에 플러그인인 proxool 을 설정해서 쓰고 있습니다.
둘다 최소 10개 최대 30개 정도의 커넥션 풀로 구성되게 해놓았구요.

사용자층은 SFA는 사내 영업직원 약 300명 가량 동시접속자 2~30명정도구요.
채용은 몰릴때 일시적으로 몰리는데요 하루 400명정도 가량 접속을 합니다.

일단 로그를 확인해보면, 
어쩔땐 OutOfMemory 로 완전히 엔진이 맛이 간적도 있구요.
connection time out으로 pool에서 커넥션을 제대로 못가져오는 경우도 있구요.
getOutputStream() has already been called for this response -> 이에러도 많이 발생합니다.
정말 궁금한거 위에러는 열린거 또 열려고 할때 발생하는것 같은데,
이건 도대체 감을 잡을수가 없습니다. 

소스도 디테일하게 체크해봤지만, 에러가 발생한다고 해서 커넥션 반납에 문제가 있을
만한 내용은 없었습니다. finally 혹은 외부에서 exception을 처리해서 최대한 신경을
썻는데요.. 스트레스 테스트는 고사하고, 모니터링이라도 제대로 해보고 싶은데
튜닝쪽 정보나 실력이 영 모자랍니다.

일단, 전문가가 계시면 비용문제를 떠나서 좀 와서 봐주셨으면 좋겠구요.
아니면 이런 튜닝쪽을 의뢰할 방법이나 연락처라도 알려주시면 좋겠구요.
그것도 아니면, 힌트라도.. ㅜ.ㅜ 위에서 난리가 나서, 정말 갑갑합니다.
도와주세요~



온세상을 자바로~ -_-!!
http://cafe.naver.com/javalove
제목 : Re: 메모리 누수현상 원인 분석 방법
글쓴이: 이원영(javaservice) 2005/01/29 14:44:23 조회수:3042 줄수:53
언급하신 증상은 조금더 면밀한 조사가 필요하나 대부분 다음과 같은 원인에 의해 발생할
수 있습니다.

1) Statement/PreparedStatement/ResultSet을 정상적으로 닫지 않을 경우, native memory
 leak의 주원인이 되어, (JVM Heap메모리와 무관하게), OutOfMemoryError를 유발하게
 됩니다. 이 경우, 아래의 문서에 기술된 바와 같이 JDBC 코딩 가이드라인을 준수하지
 않은 것이 원인이라, 해당 어플리케이션을 수정하셔야만 해결이 됩니다.

 서블렛 + JDBC 연동시 코딩 고려사항 -제1탄- 
 http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=servlet&c=r_p&n=968185187

 문제는 어떤 어플리케이션에서 그러한 문제를 유발하고 있느냐를 찾기가 운영단계에서는
 쉽지가 않은데, 아래와 같은 실시간 모니터링 툴을 설치하시면 매우 쉽게 찾아집니다.


2) native memory가 아닌 Heap memory 가 정말 부족하게 설정되어 있을 수 있습니다.
 heap memory는 -Xms -Xmx로 설정가능하며, heap memory는 Runtime의 freeMemory()
 totalMemory()를 이용하여 추출가능합니다. 이 또한 위의 모니터링 툴을 통해 보다
 직관적인 실시간 그래프로 모니터링이 가능합니다.

 이 경우는, 극히 정상적으로 어플리케이션이 그렇게 많은 메모리를 사용하고 있는
 구조일 수 있고, 이 경우라면 -Xms -Xmx를 통해 시스템 전체 메모리를 고려하여 충분히
 잡아주는 것과, 혹은 그러한 어플리케이션을 보다 적게 메모리를 사용하는 구조로
 변경하는 것과 같은 노력이 필요합니다.

 혹은 ObjectOutputStream의 재사용과정에서 reset()를 주지 않았다거나,

 TCP/IP Socket ObjectOutputStream.reset() 
 http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=javatip&c=r_p&n=1058887057

 혹은 WAS의 bug에 기인한 heap memory leak이라거나, 혹은 어플리케이션에서 global
 변수인 vector나 hashtable에 지속적으로 저장하는 구조이거나, 어플리케이션 레벨에서의
 cache를 구현하는 과정에서 지속적으로 메모리를 점유하는 등의 원인이 있을 수 있습니다.

 heap memory leak의 추적은 크게 두가지 방법으로 나뉩니다.
 하나는 전용 JVM Profiling Tool을 통해 JVM Heap 메모리 누수를 찾아낸다거나,
 (HP-UX의 Enterprise Edtion의 OS전용분석기, Borland OptimizeIt 등),
 두번째는 OS가 만약 IBM의 AIX이거나 Windows,linux라면 IBM JDK의 자체기능인 heapdump를
 이용하여 이를 HeapRoot라는 툴로 분석하여 매우 쉽게 어떤 객체 어느 곳에서
 memory leak이 일어난지를 어렵지 않은 방법으로 찾아낼 수 있습니다.

 IBM HeapDump & HeapRoots
 http://www.javaservice.net/~java/bbs/read.cgi?m=dbms&b=jdbc&c=r_p&n=1083294657

결론적으로, 우선 Jennifer 전체적인 상황을 보다 면밀히 판단하고, memory leak의
형태에 따라 적절히 그 원인을 추적해 들어가시면 되겠습니다.


자바서비스컨설팅 이원영
e-mail : lwy@javaservice.com
MSN : javaservice@hanmail.net
Phone: 010-6239-6498
제목 : Re: 기본적인 것 부터 확인해 보세요.
글쓴이: 박철휘(guest) 2005/01/30 17:39:34 조회수:1452 줄수:58
이원영씨께서 답해주신 것이 옳은 방법이나 일단 문제가 생긴 시점에서 profilling 기법도
모르는데 그러한 정보로 해결을 한다는 것은 매우 어려운 일인 것 같습니다.

말씀하신대로 전문가의 도움을 받으시는 것이 비용대비 효율상 가장 좋을 것 같습니다.

다만 여의치 않으시다면 일반적인 요령 몇가지가 있습니다.
제 경험으론 이런게 사실 문제의 원인인 경우가 대부분이더군요.
그리고 그래도 안된다면 해결하는데 시간 많이 들어갑니다.

1. 웹서버 다운
서버자체가 먹통이 된다면 서버를 바꾸는 것이 좋을 것입니다.
OS와 하드웨어의 궁합으로 인한 문제가 간혹 있습니다. 
이것은 자바만의 문제가 아닌 모든 application에 해당되며 재수가 없는 것입니다.

application이 다운되는데 OOM(OutOfMemory)라면 십중팔구는 heap memory가 full 이 난
것입니다. 메모리 누수요인을 체크하는 것이 좋은데 이원영씨께서 말씀하신 부분이 원인인
경우가 대부분입니다. 그것이 요인이 아니라면 찾는 것이 어렵지는 않으나 시간이 꽤 걸립니다.
무엇을 실행시켰는데 heap에 메모리를 반환하지 않는지 일일히 확인해봐야 합니다.
Optimizeit으로 찾으려면 수작업이 조금 들어가구요. ServerTrace를 사용하면 매우 직관적이고
편리하게 이 문제를 확인할 수 있습니다.

application이 다운된 것은 아닌데 하얀화면만 나오는 이른바 hang 현상이라면 dead lock인
경우가 많고 간혹 WAS와 JVM의 bug로 인한 것이 있습니다.
이런건 지속적인 dump나 hang이 걸리는 시점에서 몇번의 dump 만으로도 문제의 원인을 찾을
수 있습니다. OOM보다 오히려 문제의 원인을 찾는데 시간이 적게 들어가는 경우가 많습니다.

2. POOL에서의 Connection Time Out
WAS가 허용하는 Connection 갯수 대비 connection pool 갯수가 현저히 적을 경우 발생하는
경우가 많습니다. connection pool 갯수를 늘리든지 WAS의 connection 갯수를 줄이든지 해서
경과를 지켜보시는게 좋을겁니다. 그렇지 않다면 db에서 허용하는 max connection num을
확인해 보세요.

3. getOutputStream()
외부 프레임워크를 쓰는데 이런 일이 발생하는 경우라면 ServletResponse를 직접 제어하는데
제대로 못써서 그런 경우이거나 프레임워크 자체를 잘 못 써서 그런 경우가 많습니다.
보통 파일 다운로드나 redirect를 해놓고 처리를 종료하는 것이 아니라 프레임워크에게 계속
처리가 넘어가는 경우 많이 생깁니다.

4. 하이버네이트+스트럿츠+서블릿+JSP+스프링 프레임웍
요즘 외국에서 인기리에 사용되는 방식인데 이것을 모두 도입하여 사용하실 정도인데 위의
문제가 발생하는 것이 조금 의문이네요.
하이버네이트 : 다 좋은데 가르치는데 시간이 많이 들더군요. 교육비용이 매우 많이 드는 것
같습니다. 이해의 부족으로 인한 결함 요인이 자꾸 생겨서 그때마다 확인해주는 것도 보통
일이 아니더군요. 그냥 포기하고 native sql을 사용하는 DAO로 전환시켰습니다.
스프링 : JBOSS를 사용하신다면 EJB를 사용하시는 것이 아닌가요. 그런데도 spring이 필요한
점이 의외네요.
스트러츠 : 이런거 쓸 때는 reponse를 직접 제어하는거 주의하시는게 좋습니다. 파일다운로드는
별도의 서블릿으로 사용하시는게 속편할 겁니다.
제 개인적인 견해로는 Hibernate를 쓰면서 spring과 결합이라면 transaction에 대한 부분이
매력이지만 critical한 것도 아닌 것이라면 없어도 무리는 없을 것 같은데요. 일반적으로
ThreadLocal을 사용하여 request당 Session 하나를 두는 것을 권장합니다. hibernate에서도
그것을 권장하는 편이구요.

이런 문제를 해결하면서 많은 것을 배우게 됩니다.
하지만 이것 때문에 내 목이 위태로운 상태라면 문제는 보통 심각한게 아니겠지요.
전문가에게 맡기는 방법이라면 프리랜서를 고용하는 방법이나 비싼 WAS 구매하면서 이 부분에
대한 분석을 요청하시면 될 겁니다. (이것도 운이 좀 필요하더군요. 이 문제를 곧바로
해결해주는 엔지니어를 만나신다면 행운입니다.)
제목 : Re: 좋은의견 주셔서 감사합니다.
글쓴이: 김영근(naucika) 2005/01/31 09:15:02 조회수:1133 줄수:28
저번주 금요일부터 나타나는 오류들을 하나하나씩 잡고, 주말에 추이를 좀 지켜보았는데요.
역시나, outofmemory가 떨어졌습니다.

로그확인결과, 
[net.sf.hibernate.collection.PersistentCollection] Failed to lazily initialize a collection
이란 하이버네이트 오류가 발생했구요. 
-> 공지사항 검색 로직인데, lazy 방식으로 로드합니다. 공지사항이 없을리는 만무하고,
특정구간에서 발생하는 오류인듯 싶습니다.

[org.jboss.web.localhost.Engine] StandardWrapperValve[jsp]: 
Servlet.service() for servlet jsp threw exception
java.lang.OutOfMemoryError
그외에 이렇게 oom 이 자주 일어나고, 종국엔 새벽경에 서버가 다운됬습니다.
한두시간정도 이런 oom이 계속 발생한거 같습니다.

철휘님이 말씀하신것중에 hibernate와 jboss 사용이유는 원래 ejb를 사용했던것인데요.
이번에 o/r mapping 으로 리뉴어링 했습니다. 굳이 서버를 변경할 필요까진 없어서
그대로 사용을 했던거구요, 이서버에 sfa(영업관리시스템)을 통합운영하게 된거죠.

그외에 퍼포먼스 가이드를 계속 살펴보고 있습니다.
로그분석도 이번주에 시작해 볼 예정입니다만, 솔직히 자신이 없네요. 
발견되는 내용은 족족 게시판에 업로드 하겠습니다..

수고하세요.


온세상을 자바로~ -_-!!
http://cafe.naver.com/javalove
제목 : Re: 하이버네이트
글쓴이: 손님(guest) 2005/02/01 14:07:32 조회수:1491 줄수:51
기억이 가물가물한데....항상 시간이 지나면 잊어버리니..쩝..
하이버네이트요...세션클로즈방식이 잘못쓰면 저런에러가 나왔던기억이...나네요..
종국에는 이원영님의 1번코멘트가 정답일겁니다.

제 가물가물한 기억으로는...1.1버전썼던것같은데 벌써3.0버전이되버렸네여..

Session session = _sessions.openSession();
        Transaction tx = null;
        List result = null;
        try {
            tx = session.beginTransaction();
            Query q = session.createQuery(
                "select blog.id, blog.name, count(blogItem) " +
                "from Blog as blog " +
                "left outer join blog.items as blogItem " +
                "group by blog.name, blog.id " +
                "order by max(blogItem.datetime)"
            );
            q.setMaxResults(max);
            result = q.list();
            tx.commit();
        }
        catch (HibernateException he) {
            if (tx!=null) tx.rollback();
            throw he;
        }
        finally {
            session.close();
        }
        return result;
    }

이와같이 준수하여 사용하셨는지 함 확인해보시기 바랍니다.

위의 메모리릭은 

1) Statement/PreparedStatement/ResultSet을 정상적으로 닫지 않을 경우, native memory
 leak의 주원인이 되어, (JVM Heap메모리와 무관하게), OutOfMemoryError를 유발하게
 됩니다. 이 경우, 아래의 문서에 기술된 바와 같이 JDBC 코딩 가이드라인을 준수하지
 않은 것이 원인이라, 해당 어플리케이션을 수정하셔야만 해결이 됩니다

요게 정답이라고 사료됩니다. 단 하이버네이트의 경우 세션이라는 개념을 사용합니다..
예전에..XX사는넘님이 이거 담당이였는데...ㅎㅎ
XX사는넘님 글보시면 코멘트부탁드립니다...케케
근데 HQL(Hibernate Query Language) 이거 제대로 쓸려니 머리좀 아프더군여..^^;





제목 : Re: 링크걸어드리져.
글쓴이: 손님(guest) 2005/02/01 14:23:53 조회수:1092 줄수:1
http://forum.hibernate.org/viewtopic.php?t=938011&highlight=failed+lazily+initialize
제목 : Re: 하이버네이트 관련글.
글쓴이: 김영근(naucika) 2005/02/01 18:16:34 조회수:1260 줄수:51
일단, 제경우 하이버네이트를 스프링 프레임웍에 붙였습니다.
코딩은 다음과 같죠.

public Object findMajorByCode(final String code, final String sc_code) throws MasterException,
			HibernateException {
	return getHibernateTemplate().execute(new HibernateCallback() {
  	      public Object doInHibernate(Session session) throws HibernateException, SQLException {
				Criteria crit = getSession().createCriteria(HA020TB.class);
				crit.add(Expression.eq("HA02_SCHOOLCAREER_CD", sc_code));
				crit.add(Expression.eq("HA02_MAJOR_CD", code));
				List list = crit.list();
				if (list.size() > 0) {
					HA020TB data = (HA020TB) list.get(0);
					return data.getHA02_MAJOR_NM();
				} else {
					return "";
				}

			}
		});
	}

위소스처럼 되어있는데요.
세션을 직접 얻어서 쓰는경우가 거의 없습니다.
혹은 아래처럼, 

BA010TB obj = (BA010TB) getHibernateTemplate().get(BA010TB.class, zipid);

식으로, 스프링에서 제공해주는 템플릿을 애용(?)합니다.
이런, 소스내용도 잘못된걸까요?

어쨋든, 한 이틀째 모니터링을 하고 있는데요,
(하이버네이트쪽은 검사하지 않고 있습니다.) 당혹스럽게도..
stmt, rs가 안닫히는 부문이 많습니다. --;;;;
게다가, 불필요한 커넥션을 많이 가져가는 부분도 있구요. (가급적 join으로 처리할려구요)
일단, 소스는 수정하지 않은상태에서 스트레스 테스트를 해볼려구요.
과연 이렇게 안닫혀지는 stmt들이 얼마나 악영향을 끼칠지 궁금합니다.

메모리 덤프로 떠볼려구요. (서비스넷 쥔장님이 그렇게 해보는게 가장 확실하다고 해서용)
어쨋든 일단 소스는 잘못된게 확실하네요.. 
자체 프레임웍을 썻는데, 미흡했나 봅니다.

모니터링 결과는 이번주내에 나올듯 싶습니다.






온세상을 자바로~ -_-!!
http://cafe.naver.com/javalove
제목 : Re: 메모리 리익
글쓴이: 손님(dhseong)(guest) 2005/02/01 19:24:14 조회수:1063 줄수:20
1) Statement/PreparedStatement/ResultSet을 정상적으로 닫지 않을 경우, native memory
 leak의 주원인이 되어....

위와 같은 경우라면 웬만큼 커넥션을 많이 열어서 작업하지 않고서는 OutOfMemory가 
발생하기 전에 (1) DB에서 inactive size의 증가나 cursor의 증가가 먼저 확인되리라
생각됩니다.

메모리 릭 자체에 집중하시기 전에 Connection Pool관련하여 Timeout도 발생한다고
하니 DB쪽에서 어떤 증상으로 나타나는지 점검하시는게 좋을듯 합니다.

Timeout관련해서 커넥션 (2) Pool min/max를 조절하시어 증상확인도 하셔야 할듯 합니다.

그리고, (3) Heap Size를 설정하실때는 OS마다 New/Old 영역의 비율이 중요한데
Windows 계열에서는 1:11~13로 설정하는 것이 좋습니다. 
예를 들어, -XX:NewSize=64m -Xms768 -Xmx768m 으로 설정할 수 있겠지요.

Heap Size도 변경하셔서 증상 확인 다시 하실 필요가 있습니다.

저희도 하이버네이트를 사용하는 패키지를 벤더가 들고 들어와서 쓰는데, 문제생기니
골치아프더군요.
제목 : Re: 스트레스 툴 쓰는법좀 알려주시면 안될까요? --;
글쓴이: 김영근(naucika) 2005/02/02 13:08:21 조회수:1268 줄수:32
현재 Web Application Stress Tool(MS) 을 사용해 볼려고 하는데요.
각페이지를 레코딩하고, 필요없는것들은 삭제를 한후에,
돌려보는데 맨 첨 페이지만 hit수가 1번 증가하고 그이후론 깜깜무소식입니다.
왜그런건지..

host : localhost
1. get /main.jsp -> 그냥 인덱스 페이집니다.
2. post /servlet/ens.common.auth.Authmgr -> 인증관련 서블릿이구요.
   QueryString 에 id/passwd 를 넣었습니다.
3. get /market/sfa/sfainfo/afc200/cust_search.jsp -> 고객검색 화면이구요.

위에서, 웹서버쪽의 콘솔내용을 보면, 2번까지는 옵니다.
인증완료되고, redirect까지 처리합니다.
3번부터는 오지않구요. 3번이후 대략 10개정도의 페이지가 등록되 있습니다.

그런데 stress tool 의 report 내용을 보면, hit가 1번에만 1번 있고,
나머지는 전부 0입니다.
에러내용도 없고, respose코드도 200으로 나오구요.
콘솔을 보면 2번이후부터는 아예 접근도 안합니다.

별다른 설정이 없는것 같은데.. 세션을 유지해야 하는 다른 설정이 있나요?
세션이 유지가 안된다면, 3번부터 접근이 안되는건 맞긴한데..

기본 셋팅내용은 default로 건드리지 않았구요.
Stress Level 을 1로 (테스트용) Test Run Time 을 1분으로 설정해놓았습니다.

이런건 첨해보는거라.. 도움을 주셨으면 감사하겠습니다. (_ _);



온세상을 자바로~ -_-!!
http://cafe.naver.com/javalove
제목 : Re: OutOfMemory 발생상황에서 Dump를 떳는데요. 해석이..
글쓴이: 손님(guest) 2005/02/03 16:14:55 조회수:1252 줄수:107
실환경에서 모니터링을 하고있는데, 어떤 오류도 아직 발생하지 않았습니다. 아직 이틀동안이지만..
덤프는 걸어놓긴 했는데, 에러가 발생하지 않으니 갑갑하네요.
전체적으로 JVM 힙메모리 차지하는 비율이 점점 증가하고 있습니다.

그래서, 일단 스트레스 테스트 툴로, 테스트를 해봤는데요.
유저 1명, 쓰레드 50, 100 으로 했습니다.
테스트 페이지는 로그인->활동검색->고객검색->기타등등 으로 히트수가 높은것 순으로 
했구요.

50개에선 별무리 없이 갔습니다만, 100개에서 드뎌 OutOfMemory가 발생되며, 덤프가
생기더군요. 그런데 이상한게 익셉션이 엄청난 양을 잡아먹고 있습니다.

아래는 덤프 내용입니다.

Requesting 74 mb of heapspace to process heapdump ...
 done.
Finding pure Roots
...................................................................... done.
DFS from pure Roots
...............................................................        done.
DFS from objects unreached from Roots (6,123)
                                                                       done.
Dump comments        : // Version: J2RE 1.4.2 IBM Windows 32 build
                       cn142-20040926
                       // Breakdown - Classes: 4865, Objects: 3911278,
                       ObjectArrays: 270052, PrimitiveArrays: 1837999
                       // EOF:  Total 'Objects',Refs(null) :
                       6024194,7241268(566154)
Dump has flags       : false
# Objects            : 6,024,194
# Refs               : 7,241,268
# Unresolved refs    : 0
Heap usage           : 534,302,592
Total object range   : 802,536,208

Pure roots           : 9,704
... which reach      : 6,018,071
Artificial roots     : 2,477
Softlinks followed   : 10

HR Memory Usage      : 200/307 mb

Enter: o{a,s,t,d,m,n}, g{c,s}, t{c,s,n}, i, p, d{t,d,m} or help for more info
> d

Enter total-size threshold [1048576]
>
Enter max depth or -ve for unlimited [8]
>
Enter 0x<addr> to dump from one address or any value for all roots [-]
>
Dumping object(s)  : 12181 roots, sorted by total-size
Filter             : total-size >= 1,048,576 and depth at most 8
Prune              : N

<0> [484,054,504] 0x11452960 [32] java/lang/ClassNotFoundException
<1>   [484,054,472] 0x1143ca78 [80] array of java/lang/Object
<2>     [484,053,952] 0x0032ba88 [304] class java/lang/ref/Finalizer
<3>       [483,920,104] 0x1443bdb8 [32] java/lang/ref/Finalizer
<4>         [483,786,368] 0x1443d6c8 [32] java/lang/ref/Finalizer
<5>           [483,652,632] 0x1443b328 [32] java/lang/ref/Finalizer
<6>             [483,518,896] 0x1443a808 [32] java/lang/ref/Finalizer
<7>               [483,385,160] 0x12ddff60 [32] java/lang/ref/Finalizer
<8>                 [483,251,424] 0x12ddf270 [32] java/lang/ref/Finalizer
                      - 4 children of 0x12ddf270 too deep.
<8>                 {483,518,896} 0x1443a808 [32] java/lang/ref/Finalizer
                    - 2 children of 0x12ddff60 filtered.
<7>               {483,652,632} 0x1443b328 [32] java/lang/ref/Finalizer
                  - 2 children of 0x1443a808 filtered.
<6>             {483,786,368} 0x1443d6c8 [32] java/lang/ref/Finalizer
                - 2 children of 0x1443b328 filtered.
<5>           {483,920,104} 0x1443bdb8 [32] java/lang/ref/Finalizer
              - 2 children of 0x1443d6c8 filtered.
            - 2 children of 0x1443bdb8 filtered.
          - 3 children of 0x0032ba88 filtered.
<2>     {484,053,952} 0x0032ba88 [304] class java/lang/ref/Finalizer
<2>     {484,053,952} 0x0032ba88 [304] class java/lang/ref/Finalizer
        - 13 children of 0x1143ca78 filtered.
      - 1 child of 0x11452960 filtered.
<0> [46,408,848] 0x03dd23b8 [304] class java/lang/Shutdown
<1>   [46,408,528] 0x10437350 [16] java/util/HashSet
<2>     [46,408,512] 0x10437320 [48] java/util/HashMap
<3>       [46,408,464] 0x104372d0 [80] array of java/util/HashMap$Entry
<4>         [46,408,240] 0x10842df8 [32] java/util/HashMap$Entry
<5>           [46,408,208] 0x10842e18 [16] java/lang/Shutdown$WrappedHook
<6>             [46,408,192] 0x101eafe0 [96] java/util/logging/LogManager$Clean
r
<7>               [46,408,032] 0x1029cb78 [56] java/lang/ThreadGroup
<8>                 [46,403,504] 0x101f76a8 [56] java/lang/ThreadGroup
                      - 4 children of 0x101f76a8 too deep.
                    - 3 children of 0x1029cb78 filtered.
<7>               <46,400,496 parent:0x101ea978> 0x101eb1c0 [160] org/jboss/mx/
oading/UnifiedClassLoader3
                  - 3 children of 0x101eafe0 filtered.
              - 1 child of 0x10842df8 filtered.
            - 3 children of 0x104372d0 filtered.
      - 1 child of 0x03dd23b8 filtered.

2/12181 roots were dumped. There were 18 objects expanded.

Enter: o{a,s,t,d,m,n}, g{c,s}, t{c,s,n}, i, p, d{t,d,m} or help for more info
>

서버로그를 찾아봐도 ClassNotFoundException 은 발생한적이 없는데,
이게 왜 이렇게 많이 잡아먹은거죠?? 위 덤프를 어찌 해석해야 하는건가요? ㅜ.ㅜ

제목 : Re: IBM JDK에서 Memory Leak의 발경 방법
글쓴이: 조대협(guest) 2005/02/04 09:41:33 조회수:1099 줄수:8
안녕하세요?
조대협입니다. 
Memory Leak의 발견과 조치가 언제나 큰 화두가 되는데요. 이원영님이 언급하신 Heap Dump에 
덧붙여서 Heap dump를 좀더 쉽게 분석할 수 있는 HeapAnalyzer라는 내용을 첨부합니다.

문서 배포는 자유입니다만, 배포시에 출처 명기 부탁드립니다.
수고하세요.

Download 자바스터디_AIX에서_Memory_Leak의_발견.doc (116736 Bytes) 자바스터디_AIX에서_Memory_Leak의_발견.doc (116736 Bytes)
제목 : Re: 그렇군요. 역시 Statement 가 나타나긴 합니다만..
글쓴이: 손님(guest) 2005/02/04 10:24:30 조회수:985 줄수:43
트리뷰 UI로 보니 정말 편하네요. 감사합니다. ^^

한눈에 들어오는것도 좋고, 따라가다 보면, 무엇으로 구성되어 있는지 잡을수 있을것 같습니다.
그래도 ClassNotFoundException은 좀 그렇네요. 자그마치 483메가를 차지하는..
ClassNotFound 따라가보니, ../trace/sql/PreparedStatemnet가 나옵니다.
적은양이긴 하지만, 이것들로 계속 구성된것이 아닌가 싶구요.

일단, Stress Test 결과, Statment 를 수정하지 않고 했을때,
동시유저수 100명에서 OOM이 발생되고, 110 명 120 명씩 늘렸을때,
계속 Reject되는 req들이 여럿 발생했습니다.

수정후에, 다시 몇번 테스트를 해보았는데요.
성능면에선 그리 나아진건 없으나, 일단 Reject되는 건수가 0였습니다.
몇번 테스트 해봤는데, 2~30명에서 2~3건 발생되는것 외엔 나머지 모두 0였죠.
OOM도 전혀 발생한적이 없었습니다. 신기하더군요.. --;

현재 운용중인 시스템은 계속 모니터링 하고있지만, 확실히 Memory Leak이였습니다.
Full GC하위로,  지속적으로 메모리가 누수되고 있었습니다.
몇몇 페이지를 수정하였지만, 이대로 계속가게되면 일주일정도 버틸수 있을것 같던데요.

어쨋든 적어도 주요 핵심 원인은 근거를 바탕으로 밝혀진것 같습니다.
그게 아닌 또다른 원인이 있더라도, 일단 이걸 전부 확실히! 수정하고
또 한 일주일 모니터링을 해봐야 겠습니다.

운영중인 서버를 대상으로 스트레스 테스트를 해보고 싶은데, 그럴순 없어 
안타깝네요.. 

이원영님한테도 정말 감사합니다. 도움이 정말 컷습니다.

%P.S
1)
스트레스 테스트 툴중 원영님이 추천하신 로드런너의 10일짜리 버젼을 받았는데,
스크립트 작성은 되나, 실제로 테스트 단계에서 라이센스가 필요하다고 하며
진행이 더이상 안됩니다. 10일 제한이라도 테스트는 되는거 아닌가요..

2)
여기 게시글 올릴때, 중간에 사진 넣는건 어떻게 하는걸까요?
태그가 먹히는걸까요? 테스트해볼려도 한번올리면 지울수가 없으니..^^;

3) 로그중에 not close pstmt같은것 말구요, 20050203/214126:W:16625 식으로
fetched 되었다고 찍히는 로그내용은 뭔가요? W이랑:수치가 뭘 의미하는지,
강제로 fetch한 내용인가요? 이게 무척 자주 찍히는데요...

Download anal0.JPG (165771 Bytes) anal0.JPG (165771 Bytes)
Download anal.JPG (168703 Bytes) anal.JPG (168703 Bytes)
제목 : Re: 열심히 소스를 수정하세요: pstmt.close()
글쓴이: 이원영(javaservice) 2005/02/04 12:19:39 조회수:1153 줄수:30
..trace/sql/PreparedStatemnet는 tracing을 위한 Wrapping클래스 이름입니다.
HeapDump의 결과가 .../trace/sql/PreparedStatemnet 들로 구성되어 있다는 것은,
PreparedStatement를 close하지 않았다는 증거가 될 수 있습니다.
또한, ...agent.log 파일에 "not close pstmt"라는 것이 곧 해당 URL, 해당 SQL과
관련된 소스코드 근방에서 pstmt.close()를 하지 않았다는 뜻입니다. SQL문장이
함께 로깅되니, 소스의 위치를 찾는 것은 어렵지 않을 것입니다.
서블렛 + JDBC 연동시 코딩 고려사항 -제1탄- 
http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=servlet&c=r_p&n=968185187


PS1: 로드런너가 10일짜리, 25유저로 제한되지 않던가요?
PS2: 자바서비스넷의 이미지포함기능은 이미지를 아래에 첨부한 후에, 해당 글의 위치에서
   %% image.gif %% 와 같이 하시면  됩니다.
PS3: 20050203/214126:W:16625:/somewhere/some.jsp?a=b&c=d[3called][2fetched][select..]
 20050203/214126: 로그일자 및 시각
 W: Warning, E: Error
 16625: elapsed time(ms), 16초가 걸렸다는 것이지요.
 [3 called] 해당 요청에서 SQL이 3번 불려졌다는 것이구요
 [waiting] : ds.getConnection()에서 JDBC Connection Pool에서 가용한 연결을 기다리는 중
 [preparing] 해당 SQL을 conn.prepareStatement(...)수행중
 [prepared] 해당 SQL을 conn.prepareStatement(...)를 끝냈다는 뜻
 [executing] 해당 SQL을 현재 수행중이라는 뜻(pstmt.executeUpdte/Query())
 [4 fetching]: 현재 rs.next() 를 4번 돌고 있으며 진행중
 [10 fetched] : 현재 rs.next()를 끝냈고, 현재까지 rs.next()를 10회 돌았다는 뜻


자바서비스컨설팅 이원영
Phone: 010-6239-6498
E-mail: lwy@javaservice.com
MSN: javaservice@hanmail.net
제목 : Re: 오류수정이후 힙메모리 모니터링 결과입니다.
글쓴이: 김영근(naucika) 2005/02/17 11:04:49 조회수:1193 줄수:13
15일, 16일, 17일 3일치인데요.
이정도면, 정상적이라고 얘기할 수 있을까요?
그리고, 3일차에 갑자기 100메가 정도로 떨어져 버리네요? GC가 발생한건가요?
2일이나 지나서 GC가 이렇게 대량의 메모리를 제거하는건가요? 제가 알기론
어느정도 시스템 유휴상태가 되면, 꽤 자주 하는것으로 알고있는데. 100메가정도가
제거된 이유가 GC때문이 맞을까요?
사용자 삽입 이미지
사용자 삽입 이미지
제목 : Re: GC 그래프에 대한 의견
글쓴이: 조대협(guest) 2005/02/18 09:58:11 조회수:1252 줄수:7
전체 Heap이 1G 인가요?
거의 200~300M정도의 수준에서 머무는데.. 
제 의견으로는 아주 상당히 안정적인 것으로 보입니다.
시스템의 부하가 그리 많지 않나보네요. 100M가량 갑자기 떨어지는것은 Full GC가 일어났다고
보이는데요. GC로그에서 FullGC가 저시간즈음 일어나지 않았는지 확인해보세요.
수고하세요.
Posted by 알 수 없는 사용자
|

K사 프로젝트 중 다음과 같은 에러가 발생. 27일에는 단기적으로 발생하다가 28일에는 DB 커넥션에 거의 실패를 했음. 29일부터 31일까지 계속 커넥션이 되지 않음.

- 드라이버를 사용하여 커넥션을 연결한 경우.
java.sql.SQLException: IO 예외 상황: Broken pipe

- 톰켓의 데이터소스를 사용한 경우.
2006. 1. 31. 오전 10:39:26 org.apache.tomcat.util.threads.ThreadPool logFull
심각: All threads (150) are currently busy, waiting. Increase maxThreads (150) or check the servlet status
2006. 1. 31. 오전 10:39:36 org.apache.coyote.http11.Http11Protocol pause
정보: Pausing Coyote HTTP/1.1 on http-5010
2006. 1. 31. 오전 10:39:36 org.apache.jk.common.ChannelSocket acceptConnections
경고: Exception executing accept
java.net.SocketException: Invalid argument
 at java.net.PlainSocketImpl.socketSetOption(Native Method)
 at java.net.PlainSocketImpl.setOption(PlainSocketImpl.java:240)
 at java.net.Socket.setSoLinger(Socket.java:814)
 at org.apache.jk.common.ChannelSocket.accept(ChannelSocket.java:300)
 at org.apache.jk.common.ChannelSocket.acceptConnections(ChannelSocket.java:638)
 at org.apache.jk.common.SocketAcceptor.runIt(ChannelSocket.java:847)
 at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:684)
 at java.lang.Thread.run(Thread.java:534)
2006. 1. 31. 오전 10:39:37 org.apache.catalina.core.StandardService stop
정보: Stopping service Catalina
2006. 1. 31. 오전 10:39:37 org.apache.catalina.core.StandardHostDeployer remove
정보: Removing web application at context path /admin
2006. 1. 31. 오전 10:39:37 org.apache.catalina.logger.LoggerBase stop
정보: unregistering logger Catalina:type=Logger,path=/admin,host=localhost
2006. 1. 31. 오전 10:39:38 org.apache.catalina.core.StandardHostDeployer remove
메모리와 java 프로세스가 10~15%로 증가하다가 1%로 떨어지는 현상발생

http://javaservice.co.kr/~java/bbs/search.cgi?m=resource&b=engine&p=0&c=search&k=java.net.SocketException&o=tb&sb=engine


단지 커넥션의 문제인 줄 알았던 이슈. 로그를 보니 threadPool  이라는 메세지를 찾았다. server.xml 의 maxThread 갯수는 150 개 였는데 pop3로 메일을 가져오는 프로세스를 타는 순간 스레드갯수가 늘어난다. 스레드 갯수가 부족하여 다른 request 도 처리하지 못하고. 메일을 가져오지도 못한 상태로 멈춰버리는 현상이 발생한 것.
250 으로 늘리니 정상적으로 동작했다. 하지만 cpu 점유율이 25% 까지 증가. 메모리 증가.
정상적으로 메일을 가져온 후로는 cpu점유와 메모리 점유가 정상적으로 떨어졌다.

이후로 커넥션의 문제도 사라졌다. 커넥션 풀을 사용하는 경우. 스레드의 갯수가 부족했던 것이 원인이었던 듯.
톰켓의 데이터소스와 개별적으로 만든 커넥션 풀. 두가지를 모두 사용하고 있었으니 스레드의 갯수가 부족하게 되면서 커넥션관리가 제대로 되지 않았던 듯하다.
Broken pipe 의 경우도 네트웍의 문제라기보다는 커넥션 풀의 문제였던것이다.

실제로 jdbc로 테스트를 해본 결과 문제없이 동작했다;

'java' 카테고리의 다른 글

서블렛 + JDBC 연동시 코딩 고려사항 -제1탄-  (0) 2006.02.21
리소스 누수현상  (0) 2006.02.21
Jakarta POI - 쓰기  (0) 2005.11.30
이미지포함된 워드 파일 다운로드 시  (0) 2005.11.30
Jakarta POI - 읽기  (0) 2005.11.30
Posted by 알 수 없는 사용자
|

select * from nls_database_parameters;
select * from sys.props$

update 도 가능하다.

update sys.props$
set value$='US7ASCII'
where name='NLS_CHARACTERSET';

'DB' 카테고리의 다른 글

WHERE versus HAVING  (0) 2006.07.16
오라클에서 제공하는 작업 스케쥴링 사용하기  (0) 2006.07.16
캐릭터셋 변경  (0) 2006.01.06
MySQL 캐릭터셋 설정  (0) 2005.12.10
DATABASE 한글문서  (0) 2005.12.07
Posted by 알 수 없는 사용자
|

캐릭터셋 변경

DB 2006. 1. 6. 20:46

CHARACTER SET Conversion

(1) 확인
SVRMGR> select name c1, value$ c1 from sys.props$;

(2) 변경전 작업 -- rbs maxextents, rbs tablespace add,
system tablespace add
SVRMGR> alter rollback segment <rbs3> storage (maxextents 505)
SVRMGR> alter tablespace rbs add
datafile '</~/oradata/VIS/rbs02.dbf>' size 500m;

(3) 변경
SVRMGR> startup
SVRMGR> connect intenral
SVRMGR> update sys.props$ set value$='KO16KSC5601' where name='NLS_CHARACTERSET';
SVRMGR> update sys.props$ set value$='KO16KSC5601' where name='NLS_NCHAR_CHARACTERSET';
SVRMGR> commit;
SVRMGR> shutdown

* init<SID>.ora에서 job_queue_processes, aq_tm_processes,snapshot_refresh_processes항목이 있으면 막아준다...

SVRMGR> startup mount exclusive
SVRMGR> alter system enable restricted session;
SVRMGR> alter database open;
SVRMGR> alter database character set internal_use KO16KSC5601;
SVRMGR> alter database national character set internal_use KO16KSC5601;
SVRMGR> shutdown

* 오라클 환경화일에서 character set을 변경한다.
SVRMGR> startup
SVRMGR> exit
Posted by 알 수 없는 사용자
|

filedescriptor

OS 2006. 1. 2. 15:39

K사 프로젝트에서 서버에 apache + tomcat 4 + oracle 9i

ulimit -a
time(seconds)
unlimited file(blocks)
unlimited data(kbytes)
unlimited stack(kbytes) 8192 coredump(blocks)
unlimited nofiles(descriptors)  256 vmemory(kbytes) unlimited

nofiles : 하나의 프로세스가 열 수 있는 파일의 갯수, 즉 thread 가 256 개 이상 생성이 되지 않았을 듯.

tomcat 의 thread 는 75 개 설정했음.
oracle 9i 가 있었으니, 2000 이상 되어야 했을텐데 너무 적은 수가 잡혀 있었음.

현상 : oracle 의 쿼리 속도가 엄청 느림, 혹은 다운. was 처리 속도 느림, 혹은 다운.
CPU 사용률이나 메모리 사용율은 그닥 높지 않았음.

'OS' 카테고리의 다른 글

grep과 find 활용  (0) 2008.06.04
하드웨어정보 shell  (0) 2008.05.20
UNIX 하드웨어 정보  (0) 2008.05.20
리눅스 cmdline  (0) 2008.05.15
cygwin  (0) 2006.07.22
Posted by 알 수 없는 사용자
|

MySQL 캐릭터셋 설정

DB 2005. 12. 10. 21:17

종래에는 홈페이지가 자국인들만 이용할 것을 예상하고 제작하므로써 외국인 즉, 비한글 사용자들에 대한 서비스를 미쳐 염두에 두지 못하므로써 한글이 ?????$%^&( 처럼 깨져 보이는 무지함을 보였던 것이 사실입니다.
이제는 홈페이지 제작도 유니코드(UTF-8) 인코딩 방식이 세계적 추세이며 이러한 추세에 맞추어 MySql도 기본언어를 utf8(UTF-8 인코딩) 방식으로 변경하였으므로, 홈페이지를 종래 euc-kr 등 한글로만 표기하던 방식도 UTF-8 인코딩 방식으로 업데이트해야 할 때 입니다. UTF-8 인코딩 방식은 한국어, 일어, 중국어, 태국어, 아랍어 등 만국어 표기가 가능한 방식입니다.

APM에서 UTF-8을 구현하려면 꽤 많은 부분을 수정해야 합니다 .
다음은 APM에서 UTF-8 구현 방법과 소스코드를 첨부합니다.
적용 환경
----------------
OS : WinXP SP2
Apache : httpd-2.0.52-3
PHP : php-5.0.3
MySQL : mysql-4.1.11
----------------
* 아래 내용 말고도 OS와 브라우저가 지원해 주어야 하지만, OS는 Win98 부터 지원하고 있으며 많이 쓰는 익스플로러도 역시 지원하고 있으니 손쉽게 설정을 변환할 수 있습니다.


1. apache 환경파일 편집 (httpd.conf)
2. php 환경파일 편집 (php.ini)
3. mysql 환경파일 편집 (my.cnf)
4. apache, mysql 서비스 재시작
5. mysql에서 캐릭터셋 확인 및 디비생성
6. php 소스코드에 mysql_query("set names utf8;"); 함수 추가
7. php 소스에 한글문자열이 있으면 파일저장할때 UTF-8 파일형식으로 저장
8. 웹브라우즈의 보기-인코딩-UTF-8로 선택
9. 아웃룩 익스프레스 : 도구->옵션->읽기->글꼴->인코딩:유니코드(UTF-8)->


   기본설정 클릭. 국가별 설정->"모든 받는 메시지에 기본 인코딩 사용"에는 체크를 해제.
   (여기에 체크하면 EUC-KR 이나 다른 언어로 작성된 메일은 깨지게 됨)

1. apache/conf/httpd.conf 에서 캐릭터셋 수정
/*------------
AddDefaultCharset UTF-8

2. etc/php.ini 에서 캐릭터셋 수정
/*------------
;default_charset = "iso-8859-1"
default_charset = "utf-8"

3. etc/my.cnf (또는 my.ini) 에서 캐릭터셋 수정
/*------------
[client]
#password = your_password
default-character-set=utf8

[mysqld]
init_connect=SET collation_connection = utf8_general_ci
init_connect=SET NAMES utf8
default-character-set=utf8
character-set-server=utf8
collation-server=utf8_general_ci

[mysql]
default-character-set=utf8

4. 환경변수를 모두 수정후 apache 및 mysql 서비스 재시작
/*------------

5. mysql에서 캐릭터셋 확인
/*------------
# mysql
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 247 to server version: 4.1.10a

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> use test
Database changed
mysql> show variables like 'c%';
+---------+-----------+
| Variable_name | Value |
+---------+-----------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
| collation_connection | utf8_general_ci |
| collation_database | utf8_general_ci |
| collation_server | utf8_general_ci |
| concurrent_insert | ON |
| connect_timeout | 5 |
+---------+-----------+
12 rows in set (0.00 sec)

* MySql에서 데이터베이스 생성
mysql>CREATE DATABASE 디비명 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

-- 가장 좋은 방법

********************************************
my.ini   or my.cnf

[mysqld]

init_connect=SET collation_connection =euckr_korean_ci
init_connect=set names euckr
character-set-server=euckr
collation-server=euckr_korean_ci

character-set-client-handshake = FALSE
********************************************

alter table TABLE default character set euckr collate euckr-korean-ci;
alter table fp_board default character set euckr;

mysql -u xxx -p --default-character-set=euckr DB명 < 새파일

mysqldump -u xxxx -p --default-character-set=utf8 DB명 > 파일
conv -c -f utf-8 -t euckr dump한파일 > 새파일명
즉.. utf8 된 파일을 갖다가 euckr 성향으로 바꾸게 하는 것입니다

Posted by 알 수 없는 사용자
|

DATABASE 한글문서

DB 2005. 12. 7. 14:23

Posted by 알 수 없는 사용자
|

Primary Key 생성시 인덱스 테이블스페이스를 따로 지정하고 싶으면..
USING INDEX TABLESPACE tablespace_name 을 추가한다.

ALTER TABLE emp
ADD CONSTRAINT emp_PK PRIMARY KEY (empno)
USING INDEX TABLESPACE tablespace_name

'DB' 카테고리의 다른 글

nls_database 세팅보기(캐릭터 셋 보기)  (0) 2006.01.06
캐릭터셋 변경  (0) 2006.01.06
MySQL 캐릭터셋 설정  (0) 2005.12.10
DATABASE 한글문서  (0) 2005.12.07
리스너의 start 시 UNKOWN 발생  (0) 2005.11.28
Posted by 알 수 없는 사용자
|

Jakarta POI - 쓰기

java 2005. 11. 30. 01:12

III. Formula(수식) 지원

엑셀을 읽고 쓸때 수식을 지원합니다.
org.apache.poi.hssf.usermodel.HSSFCell의 setCellFormula("formulaString") 메쏘드는 스프레드시트에 수식을 추가하는데 사용되며 getCellFormula() 메쏘드는 수식을 대표하는 문자열을 해석하는데 사용됩니다. 하지만 엑셀에서 사용하는 수식을 모두 사용 할 수는 없습니다.

 

지원되는 부분
-. 셀 참조, 시트참조, 지역참조
-. 상대적 혹은 절대적 참조
-. 수연산 및 논리연산
-. 시트 혹은 매크로 함수

-. 수식 결과값 반환

 

부분적 지원
문자열을 포함하는 수식을 해석할 수는 있지만 문자열값을 반환하는 수식은 아직 지원하지 않습니다.

지원되지 않는 부분

-. 배열 수식
-. 1진법 수식
-. 3D 참조
-. 에러 값 (cells containing #REF's or #VALUE's)


VII. 엑셀 쓰기예제

쓰기도 역시 읽기와 비슷합니다.

엑셀 워크북을 생성합니다. 행과 셀을 생성하려면 당연한 절차겠죠?

HSSFWorkbook workbook = new HSSFWorkbook();

시트를 생성합니다.

시트명을 파라미터로 바로 생성 합니다.

HSSFSheet sheet = workbook.createSheet("sheet name");

만약 한글로 시트명을 만들려면 다음과 같이 인코딩이 필요합니다.

HSSFSheet sheet = workbook.createSheet();
workbook.setSheetName( 0 , "한글" , HSSFWorkbook.ENCODING_UTF_16 );

셀에 사용할 스타일을 미리 생성해 둡니다.

HSSFCellStyle style = wb.createCellStyle();
style.setBorderBottom(HSSFCellStyle.BORDER_THIN);
style.setBottomBorderColor(HSSFColor.BLACK.index);
style.setBorderLeft(HSSFCellStyle.BORDER_THIN);
style.setLeftBorderColor(HSSFColor.GREEN.index);
style.setBorderRight(HSSFCellStyle.BORDER_THIN);
style.setRightBorderColor(HSSFColor.BLUE.index);
style.setBorderTop(HSSFCellStyle.BORDER_MEDIUM_DASHED);
style.setTopBorderColor(HSSFColor.BLACK.index);

등 여러가지 스타일을 만들 수 있습니다.

스타일은 다음 주소를 참고하세요
http://jakarta.apache.org/poi/apidocs/org/apache/poi/hssf/usermodel/HSSFCellStyle.html

로우를 하나 생성합니다.

HSSFRow row = sheet.createRow(0);

셀츨 하나 생성하여 스타일을 주고 값을 입력합니다.

HSSFCell cell = row.createCell((short)0);
cell.setCellStyle(style);
cell.setCellValue("jakarta project!");

만약 한글을 입력한다면 인코딩 해야 하며 값 세팅전에 해야 합니다.

cell.setEncoding(HSSFCell.ENCODING_UTF_16);  //한글 처리
cell.setCellStyle(style);
cell.setCellValue("자카드타 프로젝트!");

모든 셀이 다 입력되었으면 파일을 만듭니다.

FileOutputStream fs = new FileOutputStream("excelfile.xls");
workbook.write(fs);
fs.close();

VIII. 쓰기샘플 소스

<%@ page language="java" contentType="text/html;charset=euc-kr" %>
<%@ page import="java.io.*" %>
<%@ page import="org.apache.poi.poifs.dev.*" %>
<%@ page import="org.apache.poi.hssf.record.*" %>
<%@ page import="org.apache.poi.hssf.record.formula.*" %>
<%@ page import="org.apache.poi.hssf.model.*" %>
<%@ page import="org.apache.poi.hssf.usermodel.*" %>
<%@ page import="org.apache.poi.hssf.util.*" %>


<html>
<body>

<%

    String filepath = "C:\\Tomcat 5.0\\webapps\\ROOT\\write.xls";


    try {

        String[] cell_value = {"자카르타","프로젝트","www.jakartaproject.com"};


        HSSFWorkbook workbook = new HSSFWorkbook();


        HSSFSheet sheet = workbook.createSheet();
        workbook.setSheetName(0 , "한글명" ,HSSFWorkbook.ENCODING_UTF_16);


        HSSFCellStyle style = workbook.createCellStyle();
        style.setBorderBottom(HSSFCellStyle.BORDER_THIN);
        style.setBottomBorderColor(HSSFColor.BLACK.index);
        style.setBorderLeft(HSSFCellStyle.BORDER_THIN);
        style.setLeftBorderColor(HSSFColor.GREEN.index);
        style.setBorderRight(HSSFCellStyle.BORDER_THIN);
        style.setRightBorderColor(HSSFColor.BLUE.index);
        style.setBorderTop(HSSFCellStyle.BORDER_MEDIUM_DASHED);
        style.setTopBorderColor(HSSFColor.BLACK.index);           


        HSSFRow row = sheet.createRow(0);
        for (int i = 0 ; i < cell_value.length; i++){
            HSSFCell cell = row.createCell((short)i);
            cell.setEncoding(HSSFCell.ENCODING_UTF_16);
            cell.setCellStyle(style);
            cell.setCellValue(cell_value[i]);
        }
           
        FileOutputStream fs = null;
        try {
            fs = new FileOutputStream(filepath);
            workbook.write(fs);
        } catch (Exception e) {
        } finally {
            if (fs != null) fs.close();
        }
       
    } catch (Exception e) {
%>
        Error occurred:  <%= e.getMessage() %>
<%  
        e.printStackTrace();
    }   
   
%>

</body>
</html>


자 결과화면 입니다.

사용자 삽입 이미지


성공!
위의 소스를 기본으로 한다면 그리 어렵지 않을겁니다 ^^

참고로 셀병합은

HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet("new sheet");

HSSFRow row = sheet.createRow((short) 1);
HSSFCell cell = row.createCell((short) 1);
cell.setCellValue("This is a test of merging");

//셀병합
//Region(int 시작row, short 시작col, int 종료row, short 종료col)
sheet.addMergedRegion(new Region(1,(short)1,1,(short)2));

FileOutputStream fileOut = new FileOutputStream("workbook.xls");
wb.write(fileOut);
fileOut.close();

와 같이하면 됩니다.

'java' 카테고리의 다른 글

리소스 누수현상  (0) 2006.02.21
java.sql.SQLException: IO 예외 상황: Broken pipe - thread  (0) 2006.01.31
이미지포함된 워드 파일 다운로드 시  (0) 2005.11.30
Jakarta POI - 읽기  (0) 2005.11.30
스케쥴러 구현  (0) 2005.11.30
Posted by 알 수 없는 사용자
|