블로그 이미지
좋은느낌/원철
이것저것 필요한 것을 모아보렵니다.. 방문해 주셔서 감사합니다..

calendar

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

Notice

    2008. 7. 1. 16:40 개발/Java
    On Tue, Jun 24, 2008 at 3:06 PM, Martin Skarsaune <martin@skarsaune.net>
    wrote:

    > Hi guys,
    >
    > Thanks for feedback on my previous post.
    >
    > Another problem is blocking mail sending though:
    >
    > I get this error in my logs:
    >
    > "
    >
    > /opt/ibm/WebSphere/AppServer/grip/logs/server1/SystemOut_08.06.24_13.29.45.log:javax.naming.ConfigurationException:
    > A JNDI operation on a "java:" name cannot be completed because the
    > serverruntime is not able to associate the operation's thread with any J2EE
    > application component.  This condition can occur when the JNDI client using
    > the "java:" name is not executed on the thread of a server application
    > request.  Make sure that a J2EE application does not execute JNDI
    > operations
    > on "java:" names within static code blocks or in threads created by that
    > J2EE application.  Such code does not necessarily run on the thread of a
    > server application request and therefore is not supported by JNDI
    > operations
    > on "java:" names. [Root exception is javax.naming.NameNotFoundException:
    > Name comp/env/mail not found in context "java:".]
    > "
    >
    > I assume continuum spawns background threads to build? Is that correct?


    Yes.

    >
    > Not really any hope of sending mails from WebSphere (6.1) then is there?
    >

    Our jndi config is fine and work is lot of app server, I don't have webshere
    to test it.
    If you have an issue with the resource name, you can :
    - change the res-ref-name in web.xml
    - change the 'jndiSessionName' in
    WEB-INF/classes/META-INF/plexus/application.xml
    - create a deployment descriptor

    Let us now how you solve it. If you look under WEB-INF directory, you'll see
    we have a specific deployment descriptor for JBoss, maybe we need to do the
    same for Websphere

    Emmanuel
    posted by 좋은느낌/원철
    2008. 7. 1. 16:38 개발/Java
    On Tue, Jun 24, 2008 at 3:06 PM, Martin Skarsaune <martin@skarsaune.net>
    wrote:

    > Hi guys,
    >
    > Thanks for feedback on my previous post.
    >
    > Another problem is blocking mail sending though:
    >
    > I get this error in my logs:
    >
    > "
    >
    > /opt/ibm/WebSphere/AppServer/grip/logs/server1/SystemOut_08.06.24_13.29.45.log:javax.naming.ConfigurationException:
    > A JNDI operation on a "java:" name cannot be completed because the
    > serverruntime is not able to associate the operation's thread with any J2EE
    > application component.  This condition can occur when the JNDI client using
    > the "java:" name is not executed on the thread of a server application
    > request.  Make sure that a J2EE application does not execute JNDI
    > operations
    > on "java:" names within static code blocks or in threads created by that
    > J2EE application.  Such code does not necessarily run on the thread of a
    > server application request and therefore is not supported by JNDI
    > operations
    > on "java:" names. [Root exception is javax.naming.NameNotFoundException:
    > Name comp/env/mail not found in context "java:".]
    > "
    >
    > I assume continuum spawns background threads to build? Is that correct?


    Yes.

    >
    > Not really any hope of sending mails from WebSphere (6.1) then is there?
    >

    Our jndi config is fine and work is lot of app server, I don't have webshere
    to test it.
    If you have an issue with the resource name, you can :
    - change the res-ref-name in web.xml
    - change the 'jndiSessionName' in
    WEB-INF/classes/META-INF/plexus/application.xml
    - create a deployment descriptor

    Let us now how you solve it. If you look under WEB-INF directory, you'll see
    we have a specific deployment descriptor for JBoss, maybe we need to do the
    same for Websphere

    Emmanuel
    posted by 좋은느낌/원철
    2008. 7. 1. 15:44 개발/Java

    J2EE를 마스터하는 것은 어려운 일이다. 기술과 신조어들이 나날이 늘어가기 때문이기도 하다. Java Naming and Directory Interface (JNDI)는 처음부터 Java 2 Platform, Enterprise Edition (J2EE)의 핵심에 있었지만 풋내기 J2EE 개발자들은 이를 충분히 활용하지 못한다. 이 글에서 J2EE 애플리케이션에서의 JNDI의 역할을 규명하고 애플리케이션을 전개로부터 분리하는 방법을 설명하겠다.

    J2EE 플랫폼이 엔터프라이즈 개발자의 삶을 향상시킨 것 만큼 J2EE의 많은 스팩과 기술을 배워야 하는 대가를 치러야 했다. Dolly Developer는 엔터프라이즈 애플리케이션의 전개에 수반되는 부담을 경감시키는 한 가지 기능인 JNDI를 발견하지 못한 많은 개발자들 중 하나이다. JNDI를 잘 활용했을 때 상황이 어떻게 향상되었는지를 살펴보자.



    너무나 익숙한 여행

    Dolly 개발자는 JDBC 데이터 소스를 사용하는 웹 애플리케이션을 코딩중이다. 그녀는 MySQL을 사용하고 있다는 것을 알기 때문에 MySQL JDBC 드라이버 클래스에 대한 레퍼런스를 인코딩하고 JDBC URL을 사용하여 웹 애플리케이션의 데이터베이스에 연결한다. 데이터베이스 커넥션 풀링은 중요하기 때문에 커넥션 풀링 패키지를 포함하고 이를 설정하여 단 64 커넥션만 사용해야 한다; 그녀는 데이터베이스 서버가 128 클라이언트 커넥션을 할당하도록 설정되었다는 것을 알고 있다.

    재앙으로 향하는 Dolly

    개발 단계 동안은 모든 것이 순조롭게 진행된다. 하지만 전개할 때 상황은 달라진다. 네트워크 관리자는 데스크탑에서 제품 또는 스테이징 서버로 액세스하지 못한다는 것을 지시했기 때문에 그녀는 각 전개 단계마다 다른 버전의 코드를 만들어야 한다. 상황이 이렇기 때문에 그녀에게는 테스트, 스테이징, 제품 마다 전개 가능한 새로운 JDBC URL이 필요하다. (설정 관리에 익숙한 사람들은 개별 구현을 각 환경에 전개한다는 개념 때문에 망설이지만 이는 매우 일반적인 상황이기 때문에 너무 그럴 필요는 없다.)

    Dolly가 그녀가 전개 가능한 다른 URL 들로 설정 문제를 “해결”했다라고 생각했을 때, 그녀는 데이터베이스 관리자가 제품에서 MySQL 인스턴스를 실행하는 것을 원하지 않는다는 것을 발견한다. 이것은 개발에는 알맞지만 중요한 미션을 위한 데이터용 비즈니스 표준은 DB2®이다. 이제 그녀의 구현은 데이터 URL을 달리 해야 할 뿐만 아니라 다른 드라이버를 갖추어야 한다.

    상황은 더 나빠진다. 그녀의 애플리케이션은 너무 유용해서 너무 중요해진다. 애플리케이션 서버에서 페일오버 기능을 얻고 4개의 서버 클러스터로 복제된다. 하지만 데이터베이스 관리자들은 빨간 깃발을 들어올리지만 그녀의 애플리케이션의 모든 인스턴스는 64 커넥션을 사용한다. 데이터베이스 서버는 전체적으로 200개의 사용 가능한 커넥션을 갖고 있다. 모두가 Dolly의 애플리케이션에 의해 연결되고 있다. 더욱이 DBA는 Dolly의 애플리케이션이 단 32개의 커넥션만을 필요하다는 것을 결정했고, 이것도 하루에 단 한 시간 동안이다. 그녀의 애플리케이션이 올라갈 때 애플리케이션은 데이터베이스 레이어에서의 경쟁 문제로 끝나서 그녀의 유일한 옵션은 클러스터링 된 커넥션의 수를 변경하여 클러스터가 늘어나면 다시 이와 같은 것을 준비하거나 애플리케이션이 다른 클러스터에서 복제되도록 한다. 애플리케이션 설정에 대해 내린 결정이 시스템과 데이터베이스 관리자들에겐 최상인 것 같았다.

    J2EE 역할

    Dolly는 J2EE 역할에 대한 지식을 기반으로 애플리케이션을 개발했다면 이런 딜레마는 피할 수 있었을 것이다. J2EE 스팩은 다양한 개발 역할들에 책임을 위임한다: 컴포넌트 공급자, 애플리케이션 어셈블러, 전개자, 시스템 관리자. (많은 조직들에서 컴포넌트 공급자와 어셈블러 역할은 통합되었다. 전개자와 시스템 관리자 역할 또한 통합되어 있다.) J2EE에서의 JNDI의 역할을 이해하기 전에 J2EE의 역할을 이해하는 것도 중요하다.

    컴포넌트 공급자
    이 역할은 J2EE 컴포넌트를 만드는 책임이 있다. 웹 애플리케이션, Enterprise JavaBean (EJB) 컴포넌트, 애플리케이션 컴포넌트(Swing 기반의 GUI 클라이언트 애플리케이션) 등이 될 수 있다. 컴포넌트 공급자에는 HTML 콘텐트 디자이너, 문서 프로그래머, 기타 개발자 역할들이 포함된다. 대부분의 J2EE 개발자들은 컴포넌트 공급자 역할을 수행하고 있다.

    애플리케이션 어셈블러
    이 역할은 다중의 J2EE 모듈을 인접해 있는 전개 가능한 모든 것에 묶는 것이다: enterprise archive (EAR) 파일. 애플리케이션 어셈블러는 컴포넌트를 선택하고, 이들이 인터랙팅 하는 방법을 정의하고, 보안과 트랜잭션 애트리뷰트를 설정하고, 애플리케이션을 EAR 파일로 패키징한다. WebSphere® Studio, IDEA, JBuilder, WebLogic Workshop 같은 많은 IDE 들은 EAR 파일의 인터랙티브 설정을 갖춘 애플리케이션 어셈블러를 지원하는 기능을 갖고 있다.

    전개자
    이 역할은 전개를 담당하고 있다. EAR을 J2EE 컨테이너(애플리케이션 서버)에 설치하면서 데이터베이스 커넥션 풀 같은 리소스들을 설정하고, 애플리케이션에 필요한 리소스들을 애플리케이션 서버의 특정 리소스들로 바인딩하고 애플리케이션을 시작한다.

    시스템 관리자
    이 역할은 컨테이너가 필요로 하는 리소스들이 해당 컨테이너에서 사용할 수 있는 것인지를 확인한다.

    역할 수행

    비즈니스 로직과 영속성을 위해 하나의 웹 애플리케이션과 하나의 EJB 컴포넌트를 포함하고 있는 엔터프라이즈 애플리케이션을 상상해 보자. 이 애플리케이션을 개발하는 데는 많은 컴포넌트 공급자들이 개입된다. 많은 경우 같은 사람이 이 모든 일들을 수행하곤 한다. 이 컴포넌트들은 데이터 전송 객체(JAR 파일), EJB 인터페이스(또 다른 JAR 파일), EJB 구현(이것 역시 또 다른 JAR 파일), 사용자 인터페이스 컴포넌트-서블릿, JSP, HTML 페이지, 정적 웹 콘텐트 들을 포함시킬 수 있다. 사용자 인터페이스 컴포넌트들은 웹 애플리케이션으로 패키징되고 여기에는 서블릿 클래스, JSP 파일, 정적 콘텐트, EJB 인터페이스를 비롯하여 다른 필요한 컴포넌트를 포함하고 있는 JAR 등이 포함된다.

    일반적인 웹 애플리케이션을 구현하는데 얼마나 많은 JAR 파일들이 사용되는지를 고려할 때 준비해야 할 많은 컴포넌트들이 있는 것 처럼 들린다. 종속성(dependency)은 여기에서 조심스럽게 다뤄져야 한다. 인터페이스들과 전송 객체들은 웹 애플리케이션과 EJB 구현에 대한 합당한 종속관계이다. 하지만 이러한 계열의 종속관계는 이 같은 방향으로 모두 실행되어야 한다: 주기적인 종속성은 피해야 한다. WAR 파일과 EJB JAR 파일 같은 J2EE 컴포넌트들은 전개 단위 밖에 있는 리소스에 대한 종속성을 선언해야 한다.

    애플리케이션 어셈블러는 웹 애플리케이션에서의 종속성을 추가하고 모든 것을 하나의 엔터프라이즈 애플리케이션으로 패키징 해야한다. 툴이 많은 도움이 된다. IDE는 모듈과 JAR의 종속성을 반영하는 프로젝트 구조를 만드는데 도움이 되고 모듈의 추가 또는 배제를 지정할 수 있도록 한다.

    전개자는 컴포넌트가 요구하는 리소스들이 전개 환경에 존재하는지를 확인하고 이들을 플랫폼의 사용 가능한 리소스들로 바인딩하는 책임이 있다. 예를 들어, 웹 애플리케이션의 외부 EJB 레퍼런스(전개 디스크립터의 ejb-ref) 이 지점에서 실제로 전개된 EJB 컴포넌트에 묶인다.

    외부 리소스로의 늦은 바인딩

    중요한 J2EE 애플리케이션은 작동하기로 되어있는 환경을 설명하고 있는 정보에 액세스해야 한다. 다시 말해서 컴포넌트를 개발하고 테스트 하는 데에는 개발자가 일종의 전개 의무를 지는 것이 필요하다는 것을 의미한다. 코드를 테스트하는 임시적인 목적일 경우에도 그렇다. 이렇게 함으로서 개발자 도메인 밖으로 벗어났다는 것을 이해하는 것이 중요하다. 그렇지 않으면 의도되지 않았던, 때로는 재앙 같은 함축을 지닌 JDBC 드라이버, URL, JMS 큐 이름, 기타 머신 리소스들에 의존하려는 유혹에 빠진다.

    구원자 JNDI

    Dolly의 문제에 대한 솔루션은 데이터 스토어에 대한 모든 직접적인 레퍼런스들을 애플리케이션 코드에서 제거하는 것이다. JDBC 드라이버에 대한 레퍼런스, 서버 이름, 사용자 이름 또는 패스워드, 심지어 데이터베이스 풀링 또는 커넥션 관리 도 없다.Dolly는 그녀의 코드를 작성하여 액세스하려고 하는 외부 리소스들이 무엇인지 무시해야 한다. 다른 사람들이 그러한 외부 리소스들을 활용해야 하는 링크를 제공할 것이라는 것을 이해해야 한다. 이는 전개자가 데이터베이스 커넥션을 그녀의 애플리케이션에 할당 할 수 있도록 한다. Dolly가 개입될 필요가 없다. (데이터 보안은 물론 Sarbanes-Oxley 호환성 까지 다양한 이유가 때문이다.)

    많은 개발자들은 코드와 외부 리소스들 간 강결합(tight coupling)이 잠재적으로 문제가 된다는 것을 알고 있다. 하지만 실제로는 역할의 분리를 종종 무시한다. (팀 크기 또는 전개의 관점에서)작은 개발 노력으로, 역할 분리를 무시하는 것은 충분히 성공적일 수 있다. (결국, 개인적인 애플리케이션일 경우, PostgreSQL 인스턴스로 애플리케이션을 잠그는 것이 좋고, 여기에 의존할 생각은 말아야 한다.)

    J2EE 스팩은 모든 J2EE 컨테이너들이 JNDI 스팩의 구현을 제공해야 한다는 것을 요구한다. J2EE에서의 JNDI의 역할은 “스위치보드(switchboard)” 이다—런타임 시 J2EE 컴포넌트가 다른 컴포넌트, 리소스, 서비스를 찾는 일반적인 메커니즘이다. 대부분의 경우 컨테이너가 제공되는 JNDI 공급자는 제한된 데이터 스토어로서 작용하여 관리자들이 하나의 애플리케이션에서 실행 속성을 설정할 수 있고 다른 애플리케이션들이 이들을 레퍼런스 할 수 있게 한다. (Java Management Extensions (JMX)도 이 목적으로 사용될 수 있다.) J2EE 애플리케이션에서의 JNDI의 주 역할은 인다이렉션 레이어를 제공하여 컴포넌트들이 필요한 리소스들을 인다이렉션을 의식하지 않고 찾을 수 있도록 하는 것이다.

    문제 해결

    다시 Dolly의 상황으로 돌아가보자. 그녀의 간단한 웹 애플리케이션에서 그녀는 그녀의 애플리케이션 코드에서 직접 JDBC 커넥션을 사용했다. Listing 1을 보면, JDBC 드라이버, 데이터베이스 URL, 서블릿의 사용자 이름과 패스워드의 이름을 분명히 코딩했음을 알 수 있다:


    Listing 1. 전형적인 (하지만 좋지 않은) JDBC 사용

    
    
    Connection conn=null;
    try {
      Class.forName("com.mysql.jdbc.Driver",
                    true, Thread.currentThread().getContextClassLoader());
      conn=DriverManager.getConnection("jdbc:mysql://dbserver?user=dolly&password=dagger");
      /* use the connection here */
      conn.close();
    } 
    catch(Exception e) {
      e.printStackTrace();
    } 
    finally {
      if(conn!=null) {
        try {
          conn.close();
        } catch(SQLException e) {}
      }
    }
    

    이러한 방식으로 설정 정보를 지정하는 대신 Dolly(와 그녀의 동료들)는 JNDI를 사용하여 JDBC DataSource를 찾았다면 더 좋았을 것이다. (Listing 2):


    Listing 2. JNDI를 사용하여 데이터 소스 얻기

    
    
    Connection conn=null;
    try {
      Context ctx=new InitialContext();
      Object datasourceRef=ctx.lookup("java:comp/env/jdbc/mydatasource");
      DataSource ds=(Datasource)datasourceRef;
      conn=ds.getConnection();
      /* use the connection */
      conn.close();
    } 
    catch(Exception e) {
      e.printStackTrace();
    } 
    finally {
      if(conn!=null) {
        try {
          conn.close();
        } catch(SQLException e) { }
      }
    }
    

    JDBC 커넥션을 얻을 수 있으려면 소소한 전개 설정을 하여 로컬 컴포넌트의 JNDI 콘텍스트의 JDBC DataSource를 검색해야 한다. 조잡할 것 같지만 배우기 쉽다. 불행히도, 컴포넌트를 테스트하기 위해서 개발자는 전개자의 경지를 건너서 애플리케이션 서버를 설정하는 것을 준비해야 한다.

    JNDI 레퍼런스 설정하기

    JNDI가 java:comp/env/jdbc/mydatasource 레퍼런스를 해결하려면 전개자는 <resource-ref>태그를 web.xml 파일(웹 애플리케이션용 전개 디스크립터)에 삽입해야 한다. <resource-ref>태그는 “이 컴포넌트는 외부 리소스에 대한 의존성을 갖고 있다.”는 것을 의미한다. (Listing 3):


    Listing 3. resource-ref 엔트리

    
    
    <resource-ref>
      <description>Dollys DataSource</description>
      <res-ref-name>jdbc/mydatasource</res-ref-name>
      <res-ref-type>javax.sql.DataSource</res-ref-type>
      <res-auth>Container</res-auth>
    </resource-ref>
    

    <resource-ref> 엔트리는 jdbc/mydatasource라고 하는 컴포넌트 네이밍 콘텍스트의 리소스가 전개자에 의해 설정될 것이라는 것을 알려준다. 컴포넌트 네이밍 콘텍스트는 접두사 java:comp/env/로 표시되어 완전한 자격의 로컬 리소스 이름은 java:comp/env/jdbc/mydatasource이다.

    이것은 단지 외부 리소스에 대한 로컬 레퍼런스를 정의하고 이 레퍼런스가 가르키게 될 실제 리소스는 만들지 않는다. (자바와 비슷한 언어들은 <resource-ref>Object foo같은 레퍼런스를 선언하지만, Object를 실제로 참조하기 위해 foo를 설정하지 않는다.)

    전개자의 역할은 DataSource를 만드는 것이다. (또는, 우리의 자바 예제에서 처럼 foo가 지목할 Object를 만든다.) 각 컨테이너에는 데이터 소스를 설정하는 고유의 메커니즘이 있다. JBoss 에서 데이터 소스는 서비스와 함께 정의되고($JBOSS/server/default/deploy/hsqldb-ds.xml 참조), 이것이 DataSource를 위한 글로벌 JNDI 이름이라는 것을 지정한다. (기본적으로, DefaultDS). 일단 리소스가 만들어지면 여전히 중요한 제 3 단계가 남아있다: 리소스를 애플리케이션 컴포넌트가 사용하는 로컬 이름으로 연결 또는 바인딩하는 것. 이 웹 애플리케이션의 경우 벤더 스팩의 전개 디스크립터 확장은 바인딩을 지정하는데 사용된다. (Listing 4). (JBoss는 벤더 스팩의 웹 애플리케이션 전개 디스크립터에 jboss-web.xml이라고 하는 파일을 사용한다.)


    Listing 4. 벤더 스팩의 전개 디스크립터에서 리소스를 JNDI 이름으로 바인딩하기

    
    
    <resource-ref>
       <res-ref-name>jdbc/mydatasource</res-ref-name>
       <jndi-name>java:DefaultDS</jndi-name>
    </resource-ref>
    

    로컬 리소스 ref 이름 (jdbc/mydatasource)은 java:DefaultDS라는 글로벌 리소스로 매핑되어야 한다는 것을 나타내고 있다. 글로벌 리소스 이름이 변경되면 애플리케이션 코드는 변하지 않는다. 단 이 매핑만 그렇다. 여기에는 두 가지 레벨의 인다이렉션이 있다. 하나는 리소스를 정의하고 이름을 짓는 것이고 (java:DefaultDS), 다른 하나는 로컬 컴포넌트 스팩의 이름 (jdbc/mydatasource) 을 이름이 지어진 리소스에 바인딩하는 것이다. (사실, EAR 레벨에서 리소스들을 매핑할 수 있듯이 인다이렉션의 3 번째 레벨에도 가능하다.)

    과거의 DataSource 옮기기

    물론 J2EE의 리소스들은 JDBC 데이터 소스에 제한되지 않는다. 여러 유형의 레퍼런스들이 있다. 리소스 레퍼런스, 환경 엔트리, EJB 레퍼런스 등이 그것이다. 특히 EJB 레퍼런스들은 J2EE의 JNDI에 대한 또 다른 중요한 역할이 있다. 다른 애플리케이션 컴포넌트 찾기이다.

    한 회사가 전개 가능한 EJB 컴포넌트를 구입하여 Order Ontology Processing Services (OOPS) 에서 고객 주문들을 처리할 때 어떤 일이 발생하는지 생각해보자. 이 예제에서는 ProcessOrders V1.0이라고 칭하겠다. ProcessOrders 1.0은 두 조각들로 나타난다: 인터페이스와 지원 클래스 세트(홈 인터페이스 및 원격 인터페이스와 전속 객체 지원), 그리고 실제 EJB 컴포넌트들. OOPS는 이 분야의 전문이기 때문에 선택되었다.

    이 회사는 J2EE 스팩을 따라 EJB 레퍼런스를 사용하는 웹 애플리케이션을 작성한다. 전개자는 ProcessOrders 1.0을 JNDI 트리로 ejb/ProcessOrders/1.0로서 바인딩하고 웹 애플리케이션의 리소스 이름이 글로벌 JNDI 이름을 가르키도록 한다. 여기까지는 EJB 컴포넌트의 일반적인 사용법이다. 하지만 이 회사의 개발 사이클과 공급자의 개발 사이클 사이의 인터랙션을 고려해보면 상황은 더 복잡해진다. 여기에서도 JNDI가 도움이 될 수 있다.

    OOPS가 새 버전인 ProcessOrders V1.1을 발표했다고 가정해보자. 이 새 버전에는 이 회사 내부의 새로운 애플리케이션이 필요로 하는 몇 가지 기능을 갖추었고 EJB 컴포넌트를 위해 비즈니스 인터페이스를 확장한다.

    이 회사에게 몇 가지 선택권이 있다. 이 모든 애플리케이션들을 업데이트하여 새로운 버전을 선택하거나, 고유의 애플리케이션을 작성하거나, JNDI의 레퍼런스를 사용하여 다른 애플리케이션에 영향을 주지 않고 각 애플리케이션이 EJB 컴포넌트의 고유 버전을 사용하도록 할 수 있다. 모든 애플리케이션들을 한번에 업데이트하는 것은 관리가 매우 힘들고, 모든 컴포넌트의 전체 회귀 테스트가 필요하며 함수 테스트가 실패했을 때 또 다른 디버깅이 기다리고 있다.

    인하우스(in-house) 컴포넌트를 작성하는 것은 종종 불필요한 중복 작업이다. 컴포넌트가 해당 비즈니스 영역에서 전문 기술을 닦은 기업에 의해 작성된다면 주어진 IT shop은 비즈니스 기능을 마스터하기 위해 관리할 것 같지 않다. 특성화된 컴포넌트 공급자도 마찬가지다.

    이미 알고 있겠지만 최상의 솔루션은 JNDI를 사용하는 것이다. EJB JNDI 레퍼런스는 JDBC 리소스 레퍼런스들과 매우 비슷하다.
    각 레퍼런스의 경우 전개자는 새로운 컴포넌트를 특정 이름 (ejb/ProcessOrders/1.1) 에 있는 글로벌 트리로 바인딩 한다. 그리고 EJB 컴포넌트를 필요로 하는 다른 모든 컴포넌트를 위해 그 컴포넌트용 전개 디스크립터에서 EJB 레퍼런스를 결정한다. 더 오래된 애플리케이션들(1.0V 기반)은 변경 및 재테스트를 할 필요가 없고 구현 시간, 비용, 복잡성도 줄어든다. 서비스가 버저닝되는 환경에서 이것은 강력한 방식이다. 이러한 종류의 설정 관리는 애플리케이션 아키텍쳐의 모든 획득 가능한 컴포넌트들, EJB 컴포넌트에서 JMS 큐 까지 수행된다. 간단한 설정 스트링 또는 다른 객체 들까지도 수행된다. 시간이 지나면서 서비스가 변경할 때 관리 비용도 낮아지고 전개 및 통합의 수고로움도 줄어든다.

    결론

    모든 프로그래밍 문제는 단 한 개 이상의 추상화(또는 인다이렉션) 레이어로 해결될 수 있다는 오래된 컴퓨터과학 분야의 농담이 있다. J2EE에서, JNDI는 J2EE 애플리케이션들을 하나로 묶는 풀의 역할을 한다. 이것이 엔터프라이즈를 통해 확장성있고 기능이 많으면서 유연한 애플리케이션의 제공을 가능하게 하는 JNDI가 제공하는 인다이렉션이다. 이것이 J2EE가 약속하는 것이고 계획 수립과 앞선 생각이 이 모든 것을 실현할 수 있다. 사실 사람들이 생각하는 것 보다 쉽다.


    출처 : http://www.ibm.com/developerworks/kr/library/j-jndi/

    posted by 좋은느낌/원철
    2008. 7. 1. 15:22 개발/Java
    자바란 무엇인가?
    왜 자바가 중요한가?
    자바 컴포넌트 기술이란 무엇인가?
    자바는 SOA/웹 서비스와 어떻게 관련이 있는가?
    자바 프로그래밍 능력을 어떻게 향상시킬 수 있는가?
    자바 프로그래머들이 사용할 수 있는 IBM 툴과 제품에는 무엇이 있는가?




    developerWorks 자바 존은 자바 기술과 애플리케이션 관련하여 수 백 개의 기술자료, 튜토리얼, 팁을 제공한다. 하지만 새로운 주제를 시작하는 사용자들에게는 오히려 이 많은 정보들이 부담스러울 것이다. 따라서 이 페이지에서는 자바의 기초를 설명한다. 기술자료, 튜토리얼과, 팁, IBM 교육 서비스, 웹 캐스트, 워크샵, IBM 제품 연구 등 다양한 리소스 형태가 준비되어 있다.


    자바란 무엇인가?

    자바(Java technology)는 객체 지향 프로그래밍 언어이자 Sun Microsystems에서 개발한 플랫폼이다. 자바는 하나의 자바 가상 머신(JVM)(언어와 기저의 소프트웨어 및 하드웨어 간 트랜슬레이터)의 개념에 근거하고 있다. 모든 프로그래밍 언어의 구현들은 JVM을 모방하여 자바 프로그램들이 JVM 버전을 가진 모든 시스템 상에서 실행될 수 있다.

    자바 프로그램컴파일되고 (자바 바이트코드 라고 하는 중간 언어로 번역), 인터프리팅(JVM에 의해 파싱 및 실행되는 바이트코드) 되기 때문에 자바 프로그래밍 언어는 평범한 것은 아니다. 컴파일이 일단 발생하면 프로그램이 실행될 때 마다 인터프리팅이 발생한다. 컴파일 된 바이트코드는 JVM을 위해 최적화된 머신 코드의 형태가 된다. 인터프리터는 JVM의 구현이다.

    세 가지 버전으로 되어있는 자바 플랫폼(아래 자바 플랫폼 버전 참조)은 자바와 JVM과 애플릿과 애플리케이션 개발과 전개를 돕는 소프트웨어 컴포넌트들의 컬렉션인 자바 애플리케이션 프로그래밍 인터페이스(API)로 구성되어 있다. 자바 API는 관련 클래스와 인터페이스들의 라이브러리로 나뉜다. 라이브러리는 패키지로 알려져 있다.

    자바 플랫폼 버전들
    자바의 인기가 높아지면서 개발자들은 보다 단순한 것을 요구하게 되었다. Sun Microsystems는 Standard Edition, Enterprise Edition, Micro Edition 등의 다양한 버전의 자바 플랫폼을 개발하여 이러한 요구 사항에 부응하고 있다.

    보다 자세히

    • J2SE(Java 2 Standard Edition). 자바로 애플릿과 애플리케이션을 작성, 전개 구동하는 개발자를 위한 표준 Java 2 SDK, 툴, 런타임, API이다. Magic with Merlin 칼럼에서는 J2SE version, 1.4의 버전에 대한 개요 자료를 제공한다. 최신 버전인 J2SE 1.5 (일명 "Tiger")에 대해서는 Magic with Merlin 필자가 새로운 시리즈, Taming Tiger를 계획하고 있다.

    • J2EE(Java 2 Enterprise Edition). 표준화된 모듈식 컴포넌트에 기반하고 있고, 그러한 컴포넌트에 완벽한 서비스 세트를 제공하며, 복잡한 프로그래밍 없이 애플리케이션 작동의 상세를 자동으로 핸들함으로서 멀티-티어 엔터프라이즈 애플리케이션의 구현과 전개를 간소화 한다. J2EE pathfinder 칼럼에서 J2EE에 대한 이해를 높일 수 있다.

    • J2ME(Java 2 Micro Edition). 이 버전은 고도로 최적화 된 자바 런타임 환경으로서 스마트 폰이나 페이저부터 셋톱(set-top) 박스 까지 (물리적으로나 메모리 기능에서) 작은 장치들을 다룬다. 두 개의 튜토리얼의 4부 구성된 튜토리얼과 J2ME와 Mobile Information Device Profile (MIDP)를 다룬 관련 기술자료를 참조하라.


    위로


    왜 자바가 중요한가?

    일반적으로 자바의 큰 장점은 플랫폼과 운영 체계간 이식성에 있고 다이얼업 통신 같은 저대역 채널을 통해서 전달될 수 있다는 점이다. 또한 확장성도 있다. 기존 애플리케이션들이 제한된 메모리 리소스를 가진 장치에도 쉽게 순응한다. 게다가 네트워크를 통해서 안전한 방식으로 구동 되도록 설계되었기 때문에 인터넷을 통해 실행될 때에도 같은 수준의 보안을 확립할 수 있다. 자바는 데스크탑에서 웹의 리소스로 사용자의 컴퓨팅 경험을 확대한다.

    자바는 IBM의 온 디맨드 비즈니스 이니셔티브에 중요하다. 자바 (그리고 리눅스)는 기업에서 오픈 표준을 지원하는 기술들 중 하나였고 XML과 웹 서비스를 사용하여 비즈니스 라인들 간 정보와 애플리케이션 공유라는 새 장을 열었다. 게다가 많은 IBM 제품들과 기술 컨설팅 서비스의 중추적인 역할을 한다.

    보다 자세히:

    • 온 디맨드 비즈니스의 철학과, 시장의 요구에 빠르게 부응할 수 있는 엔터프라이즈 시스템을 구현하는데 이것이 어떤 역할을 하는지 배워보자.


    위로


    자바 컴포넌트 기술이란 무엇인가?

    자바의 약어를 익히는 일은 만만치 않은 일이다. 이번 섹션에서는 컴포넌트, 패키지 옵션, 각 버전의 확장 리스트를 소개하겠다. 각각의 기술 마다 짧은 설명과 링크가 곁들여져 있다. 대부분의 리소스들이 에디션을 통해 사용할 수 있다.

    J2SE:

    • Java Access Bridge for Microsoft Windows는 일종의 다리 역할을 한다. Windows 기반 기술이 Java Accessibility API와 인터랙팅 할 수 있다. ("Coding for accessibility 참조.")

    • JavaBeans Component Architecture는 플랫폼 중립적인 스팩으로서 자바 플랫폼을 위한 표준 컴포넌트 소프트웨어 API를 정의한다.

    • Javadoc은 소스 코드의 doc 주석에서 API 문서를 HTML로 만드는 툴이다. ("I have to document THAT? 참조.")

    • Java Foundation Classes (Swing) (JFC)는 자바 클래스 라이브러리 세트로서 자바 기반의 클라이언트 애플리케이션을 위한 GUI와 그래픽 기능의 구현을 지원한다. ("The Java 2 user interface 참조.")

    • Java Platform Debugger Architecture (JPDA)는 Java 2용 디버깅 지원 인프라이다. JPDA에는 세 개의 레이어드 API가 있다.
      • JDI (Java Debug Interface)는 고급의 프로그래밍 언어 인터페이스로서 원격 디버깅을 지원한다.

      • JDWP (Java Debug Wire Protocol)는 디버깅 프로세스와 디버거 프론트엔드 간 전달되는 정보와 요청의 포맷을 정의한다.

      • JVMDI (Java Virtual Machine Debug Interface)는 저급의 원시 인터페이스로서 JVM이 디버깅을 위해 제공하는 서비스를 정의한다.

      ("The future of software development 참조.")

    • Java 2D API는 이미지 구성과 알파 채널 이미지를 제공하는 고급 2D 그래픽과 이미징을 위한 클래스이자, 정확한 컬러 공간 정의와 변환을 제공하는 클래스이자, 디스플레이 지향의 이미징 오퍼레이터이다. ("Introduction to Java 2D 튜토리얼 참조.")

    • Java Web Start는 설치 절차 없이 한번의 클릭으로 완전한 애플리케이션(스프레드시트)을 다운로드 및 시작할 수 있는 기능을 제공하기 때문에 자바 애플리케이션의 개발이 간단해 진다. ("Java Web Start 참조.")

    • Java Database Connectivity (JDBC)는 자바에서 테이블 형식의 데이터 소스에 액세스 하는데 사용하는 API이다. 광범위한 SQL 데이터베이스에 크로스 DBMS 연결과, 스프레드시트나 플랫 파일 같은 기타 테이블 형식의 데이터 소스로의 액세스를 지원한다. ("What's new in JDBC 3.0 참조.")

    • Remote Method Invocation (RMI)는 부트스트래핑 네이밍 서비스를 제공하며, 유형을 절단하지 않고, 객체 직렬화를 사용하여 프로그래머들이 분산된 자바 기반 애플리케이션을 만들 수 있다. 원격 자바 객체들의 메소드가 다른 호스트에 있는 자바 가상 머신에서 호출될 수 있다. ("Java distributed objects: Using RMI and CORBA 참조.")

    • Java Advanced Imaging (JAI)는 개발자가 이미지를 쉽게 조작할 수 있도록 단순하고 고급의 프로그래밍 모델을 지원하는 객체 지향 인터페이스를 제공하는 API이다. ("JSP 코드로 이미지 관리하기(한글)참조.")

    • Java Authentication and Authorization Service (JAAS)는 자바 버전의 표준 Pluggable Authentication Module (PAM)을 구현하고 사용자 기반 인증을 지원하여 액세스 제어를 인증 및 실행하는 서비스 패키지이다. ("Java security with JAAS and JSSE", "Java security, Part 2: Authentication and authorization", "Extend JAAS for class instance-level authorization" 참조.)

    • Java Cryptography Extension (JCE)은 암호화, 키 생성, 동의, Message Authentication Code (MAC) 알고리즘을 위한 프레임웍과 구현을 제공하는 패키지이다. 암호화를 제공하고 보안 스트림과 보호 객체를 지원한다. ("Java security: Crypto basics 튜토리얼 참조.")

    • Java Data Objects (JDO)는 표준의 인터페이스 기반 자바 모델 영속성의 추상화로서 애플리케이션 프로그래머가 자바 도메인 모델 인스턴스를 영속 스토어(데이터베이스)에 직접 저장할 수 있다. 또한 이 같은 메소드들을 직접적인 파일 입출력, 직렬화, JDBC, EJB Bean Managed Persistence (BMP), Container Managed Persistence (CMP) Entity Beans으로 대체할 수 있다. (튜토리얼 "Hands-on Java Data Objects","Object-relation mapping without the container 참조.")

    • Java Management Extensions (JMX)는 장치, 애플리케이션, 서비스 중심 네트워크의 관리와 모니터링을 위한 웹 기반, 모듈식의 동적인 애플리케이션을 구현하는 툴을 제공한다. JMX Remote는 이러한 에이전트에 원격으로 액세스하는 수단을 표준화하는 기능을 제공하여 JMX 스팩을 확장한 것이다. ("From black boxes to enterprises: JMX 1.1 style 참조.")

    • Java Media Framework (JMF)는 오디오, 비디오, 기타 시간 기반 미디어들을 자바 애플리케이션과 애플릿에 추가시킨다. (튜토리얼 "Java Media Framework basics참조.")

    • Java Naming and Directory Interface (JNDI)는 자바 애플리케이션에 다중 네이밍 및 디렉토리 서비스에 대한 통합 인터페이스를 제공하는 표준 확장으로서 이종의 엔터프라이즈 네이밍 및 디렉토리 서비스에 완벽한 연결이 가능하다. ( "Industrial-strength JNDI optimization"; "Navigate the JNDI maze 참조.")

    • Java Secure Socket Extensions (JSSE)는 보안 인터넷 통신을 가능케 하는 패키지 세트로서 자바 버전의 SSL (Secure Sockets Layer)과 TLS (Transport Layer Security) 프로토콜을 구현하고 데이터 암호화, 서버 인증, 메시지 무결성, 선택적 클라이언트 인증을 위한 기능을 포함시켰다. ("Java security with JAAS and JSSE", 튜토리얼 "Using JSSE for secure socket communication 참조.")

    • Java Speech API (JSAPI) -- Grammar Format (JSGF)과 Markup Language (JSML) 스팩이 포함되어 있으며, 자바 애플리케이션에서 스피치 기술과 사용자 인터페이스를 통합시켰다. JSAPI는 크로스 플랫폼 API를 정의하여 명령어와 제어 인지자, 명령 시스템, 스피치 신디사이저를 지원한다. ("The Java 2 user interface 참조.")

    • Java 3D는 확장성 있는, 플랫폼 독립의 3D 그래픽을 자바 애플리케이션으로 결합하는데 쉽게 사용할 수 있다. 간단한 고급 프로그래밍 모델을 지원하는 객체 지향 인터페이스를 제공한다. (튜토리얼 "Java 3D joy ride 참조.")

    J2EE 기술:

    • Java API for XML Processing (JAXP)는 DOM, SAX, XSLT를 사용하여 XML 문서의 프로세싱을 지원한다. 애플리케이션이 특정 XML 구현과 독립적으로 XML 문서를 파싱 및 변형할 수 있고, 개발자는 애플리케이션 코드를 변경하지 않고도 XML 프로세서들을 바꿀 수 있다. ("자바 프로그래밍으로 온라인 XML 데이터 검색하기 (한글) 참조.")

    • Java API for XML Registries (JAXR)는 다양한 종류의 XML 레지스트리에 액세스 하는 표준 API를 제공한다. (웹 서비스의 구현, 전개, 발견을 위한 인프라를 실행한다. ("Java technology standards 참조.)

    • Java API for XML-based RPC (JAX-RPC)를 사용하여 SOAP 기반의 상호 운용성 및 이식성 있는 서비스를 개발한다. ("Java technology standards 참조.)

    • SOAP with Attachments API for Java (SAAJ)를 사용하여 SOAP 1.1 스팩과 SOAP with Attachments 노트에 순응하는 메시지를 생산 및 소비할 수 있다. ("Send and receive SOAP messages with SAAJ"; "자바 웹 서비스 (한글)" , developerWorks XML 섹션 참조.)

    • Common Object Request Broker Architecture (CORBA)는 자바를 보완하는 이종의 컴퓨팅을 위한 오픈 표준이다. 분산 객체 프레임웍, 그 프레임웍을 지원하는 서비스, 다른 언어와의 상호 운용성을 제공한다. (튜토리얼 "Java distributed objects: Using RMI and CORBA"; "기업에서의 RMI-IIOP (한글)" 참조.)

    • ECperf는 웹 비즈니스 시스템의 퍼포먼스와 확장성을 측정하는데 사용되는 벤치마크이다. 애플리케이션을 구성하는 EJB 컴포넌트, 싱글 유저/인터랙티브 테스팅에 JSP를 사용하는 웹 클라이언트, 스키마 스크립트와 로드 프로그램, makefile과 전개 디스크립터, 규칙을 실행하고 클라이언트 로드를 시뮬레이트 하는 드라이버 프로그램 등이 포함된다. (Java technology standards 참조.)

    • Enterprise JavaBeans (EJB)는 트랜잭션, 보안, 데이터베이스 연결성 등을 지원하여 미들웨어 애플리케이션의 개발을 간소화 하는 컴포넌트 모델을 사용한다. (튜토리얼 "Getting started with Enterprise JavaBeans technology"와 EJB best practices 참조.)

    • Java Authorization Contract for Containers (Java ACC)는 컨테이너가 사용할 권한 공급자의 설치와 설정을 정의하는 스팩이다. Java ACC는 공급자가 사용할 인터페이스를 정의한다. (튜토리얼 "Developing accessible GUIs with Swing참조.")

    • JavaMail은 메일 시스템을 모델링 하는 추상 클래스 세트를 제공하는 API이다. (튜토리얼 "Fundamentals of JavaMail API 참조.")

    • Java Message Service (JMS)는 JMS 기술 순응의 메시징 시스템들에 의해 지원을 받을 메시징 개념과 프로그래밍 전략들을 정의하여 자바 플랫폼을 위한 이식 가능한 메시지 기반의 애플리케이션의 개발을 실행하도록 프로바이더 프레임웍을 추가하는 API 이다. ("Get the message: Messaging in J2EE 1.4", "Enterprise messaging with JMS", "벤더로 부터 독립된 JMS 솔루션 구현하기 (한글)", 튜토리얼 "Introducing the Java Message Service 참조.")

    • JavaServer Faces (JSF)는 재사용 가능한 UI 컴포넌트를 한 페이지 안에 정렬하여 웹 애플리케이션을 만드는 프로그래밍 모델을 제공한다. 이러한 컴포넌트들을 애플리케이션 데이터 소스로 연결하고 클라이언트가 생성한 이벤트를 서버측 이벤트 핸들러로 연결한다. (JSF for nonbelievers, UI development with JavaServer Faces", "Struts, Tiles, JavaServer Faces 통합하기 (한글)" 참조.")

    • JavaServer Pages (JSP)를 사용하여 동적인 플랫폼 독립의 웹 페이지를 빠르고 쉽게 개발할 수 있다. 디자이너들은 동적 콘텐트를 변경하지 않고 페이지 레이아웃을 변경할 수 있다. 이 기술을 XML 계열의 태그를 사용한다. (튜토리얼 "Introduction to JavaServer Pages technology", JSP best practices 참조.)

    • Java Servlets는 자바가 웹 서버를 확장하고 향상시키는 방식이다. CGI 프로그램의 퍼포먼스 제한 없이 웹 기반 애플리케이션을 구현하는 컴포넌트 기반, 플랫폼 독립의 메소드를 제공한다. (Roy Miller의 Introduction to Java Servlet technology 참조.)

    • J2EE Connector Architecture (JCA)는 이종의 EIS(Enterprise Information Systems)에 J2EE 플랫폼을 연결하는 표준 아키텍처를 정의한다. 확장성 있고 안전한 방식을 제공하고 EIS 벤더들은 애플리케이션 서버에 플러그인 될 수 있는 표준 리소스 어댑터를 제공한다. ("Choosing among JCA, JMS, and Web services for EAI", "Integrate remote enterprise information systems with JCA, JMS, and Web services", "Introduction to the J2EE Connector Architecture 참조.")

    • J2EE Management Specification (JMX)는 J2EE 플랫폼, J2EE 관리 모델을 위한 관리 정보 모델을 정의한다. 이 모델은 많은 관리 시스템과 프로토콜과 상호 운용될 수 있도록 설계 된다. 이 모델은 많은 관리 시스템과 프로토콜과 상호 운용되도록 설계되었다. Common Information Model (CIM), SNMP Management Information Base (MIB), EJB 컴포넌트를 통한 자바 객체 모델, J2EE Management EJB Component (MEJB)에 대한 표준 매핑이 포함되어 있다. ("From black boxes to enterprises: Management, JMX 1.1 style 참조.")

    • Java Transaction API (JTA)는 고급의 구현 및 프로토콜 독립적인 API로서 애플리케이션과 애플리케이션 서버들이 트랜잭션에 액세스 할 수 있다. Java Transaction Service (JTS) 는 JTA를 지원하는 Transaction Manager의 구현을 지정하고 OMG Object Transaction Service (OTS) 1.1 스팩의 자바 매핑을 정의한다. JTS 는 transactions using the Internet Inter-ORB Protocol (IIOP)를 사용하여 트랜잭션을 전파한다. ("JTS 이해하기 - 트랜잭션에 대한 소개 (한글) 참조.")

    J2ME:

    • Connected Limited Device Configuration (CLDC)은 리소스 제한 모바일 정보 장치용 자바 런타임 환경을 구성하는 두 개의 설정 중 하나이다. CLDC는 가장 기본적인 라이브러리와 가상 모신 기능을 구성한다. 이것은 각 J2ME 구현에 나타나며 여기에는 K 가상 머신(KVM)이 포함된다.

    • Mobile Information Device Profile (MIDP)는 리소스 제한 모바일 정보 장치용 자바 런타임 환경을 구성하는 두 개의 설정 중 하나이다. MIDP는 코어 애플리케이션 기능을 제공한다. 사용자 인터페이스, 네트워크 연결, 로컬 데이터 스토리지, 애플리케이션 수명 관리 등이 포함된다. (튜토리얼 "Implementing Push technology with J2ME and MIDP 참조.")

    • Connected Device Configuration (CDC) 은 네트워크로 연결된 사용자와 임베디드 장치간 공유될 수 있는 애플리케이션을 구현 및 전달하는 표준 기반의 프레임웍이다. ("Securing wireless J2ME 참조.")

    자바 네트워킹:

    • JAIN은 자바 기반의 API로서 차세대 텔레콤 제품과 서비스의 빠른 개발을 이끈다. JSLEE는 이벤트 중심의 컴포넌트 기반 컨테이너 기술로서 고성능의 비동기식의 오류에 강한 애플리케이션 서버이다. (Java technology standards 참조.)

    • Java Metadata Interface (JMI)는 동적인 플랫폼 중립적인 인프라를 구현하는 스팩으로서 메타데이터의 생성, 스토리지, 액세스, 발견, 교환을 실행한다. Object Management Group (OMG)의 Meta Object Facility (MOF) 스팩에 기반하고 있으며 UML (Unified Modeling Language)을 사용하여 기술되는 기본적인 모델링 생성물들로 구성되어 있다. (Java technology standards 참조)

    • JavaGroups는 분산된 시스템 솔루션을 갖춘 디자인, 구현, 실험을 위한 소프트웨어 툴킷(API 라이브러리)이다. ("High-impact Web tier clustering, Part 1" Part 2 참조.)

    • Jini는 하드웨어와 소프트웨어를 위한 네트워크 중심의 서비스를 구현하는 개방 아키텍처이다. Jini 시스템 아키텍처는 세 개 범주, 프로그래밍 모델, 인프라, 서비스로 구성되어 있다. ("Jini networking technology, the next step 참조.")

    • JXTA는 오픈 프로토콜로서 네트워크 상에서 연결된 어떤 장치라도 P2P 방식으로 통신 및 협업할 수 있다. JXTA 피어는 가상 네트워크를 만든다. 이곳에서 모든 피어들은 다른 피어들이나 리소스들과 직접 인터랙팅 한다. 피어나 리소스가 방화벽과 NAT 뒤에 있거나 다른 네트워크 트랜스포트에 있어도 통신이 가능하다. ("Making P2P interoperable: The JXTA story", "JXTA 2: A high-performance, massively scalable P2P network 참조.")


    위로


    자바는 SOA/웹 서비스와 어떻게 관련이 있는가?

    서비스 지향 아키텍처는 애플리케이션(웹 서비스)의 기능 단위들을 잘 정의된 인터페이스들과 서비스들간 컨트랙트에 연관시키는 컴포넌트 모델이다. 인터페이스는 하드웨어, 운영 체계, 프로그래밍 언어와 독립적인 방식으로 정의된다. 다른 시스템에서 구현된 서비스들도 일관되고 통용된 방식으로 인터랙팅 할 수 있다. SOA는 약결합 방식의 모델로서, 전통적인 강결합 방식의 객체 지향 모델에 대한 대안이라고 할 수 있다.

    비즈니스 규칙과 프로세스는 XML로 정의되어 소프트웨어 애플리케이션은 플랫폼 및 프로그래밍 언어와는 독립된 방식으로 통신할 수 있다. XML은 데이터 이식성을 높이고 메시지의 생성을 수월하게 한다. XML과 자바는 웹 서비스를 구현 및 전개하는데 이상적인 조합이라 할 수 있다.

    보다 자세히:



    위로


    자바 프로그래밍 능력을 어떻게 향상시킬 수 있는가?

    두 가지 방법이 있다. 정규 코스를 밟는 것(인증 또는 교육)과 독학(코드 작성 연습)이다. 숙련된 개발자들의 지식을 배우는 것 외에도, 인증 과정을 밟으면서 기술을 향상시킬 수 있다. 스스로 실험해 보고, 리소스도 사용해 보면서 자바 기술을 발전시킬 수 있다.

    보다 자세히:



    위로


    자바 프로그래머들이 사용할 수 있는 IBM 툴과 제품에는 무엇이 있는가?

    IBM은 자바 사용에 있어서 선두적인 위치에 서 있다. 자바 다운로드와 제품 페이지에서 전체 리스트를 볼 수 있겠지만, 우선 먼저 중요한 내용만 간추렸다.

    보다 자세히:



    위로
    posted by 좋은느낌/원철
    2008. 7. 1. 15:20 개발/Java

    현재 프로젝트가 개발은 Tomcat으로, 서버는 JEUS를 사용하고 있습니다. 이런 환경을 제가 만든건 아니고 ㅡ.; EJB개발하는것도 아닌데 JEUS가 너무 무거우니 간단하게 그냥 tomcat가자는 다른분의 의견이 받아들여진 것이었죠.

     

    Servlet spec 2.4를 기준으로 작업을 하니 웬만한건 양쪽다 먹는데 JNDI datasource를 가져오는 부분의 방법이 서로 틀리더군요.

     

    Spring에서 datasource정의하는 부분도 JEUS, Tomcat용으로 따로 사용을 하곤 했죠.

    이건 그냥 Spring 설정파일 바꾸는거니 그런대로 할만 했습니다.

     

    그러다가 Spring+Ibatis조합을 안통하고 그냥 JDBC 코딩을 해야 될 상황이 생겼는데 이때는 무지하게 귀찮아지더군요. 컴파일을 새로해야 되니 너무 귀찮아서요.

     

    Liferay라는 오픈소스를 보다 정답을 찾았죠. Server의 종류를 알아내는 방법이 있더군요.

    ClassLoader.getSystemClassLoader().loadClass();을 이용하더군요.

    허접한 개발자라 몰랐습니다. 이런방법이 있을줄..

     

    다음은 그 클래스 소스입니다.

     

    package com.test;

     

    public class ServerDetector {

     

                   public static final String JEUS_CLASS = "/jeus/server/JeusServer.class";

     

                   public static final String JEUS_ID = "jeus";

     

                   public static final String GERONIMO_CLASS = "/org/apache/geronimo/system/main/Daemon.class";

     

                   public static final String GERONIMO_ID = "geronimo";

     

                   public static final String GLASSFISH_CLASS = "/com/sun/appserv/ClassLoaderUtil.class";

     

                   public static final String GLASSFISH_ID = "glassfish";

     

                   public static final String JBOSS_CLASS = "/org/jboss/Main.class";

     

                   public static final String JBOSS_ID = "jboss";

     

                   public static final String JETTY_CLASS = "/org/mortbay/jetty/Server.class";

     

                   public static final String JETTY_ID = "jetty";

     

                   public static final String JONAS_CLASS = "/org/objectweb/jonas/server/Server.class";

     

                   public static final String JONAS_ID = "jonas";

     

                   public static final String OC4J_CLASS = "oracle.oc4j.util.ClassUtils";

     

                   public static final String OC4J_ID = "oc4j";

     

                   public static final String ORION_CLASS = "/com/evermind/server/ApplicationServer.class";

     

                   public static final String ORION_ID = "orion";

     

                   public static final String PRAMATI_CLASS = "/com/pramati/Server.class";

     

                   public static final String PRAMATI_ID = "pramati";

     

                   public static final String RESIN_CLASS = "/com/caucho/server/resin/Resin.class";

     

                   public static final String RESIN_ID = "resin";

     

                   public static final String REXIP_CLASS = "/com/tcc/Main.class";

     

                   public static final String REXIP_ID = "rexip";

     

                   public static final String SUN7_CLASS = "/com/iplanet/ias/tools/cli/IasAdminMain.class";

     

                   public static final String SUN7_ID = "sun7";

     

                   public static final String SUN8_CLASS = "/com/sun/enterprise/cli/framework/CLIMain.class";

     

                   public static final String SUN8_ID = "sun8";

     

                   public static final String TOMCAT_BOOTSTRAP_CLASS = "/org/apache/catalina/startup/Bootstrap.class";

     

                   public static final String TOMCAT_EMBEDDED_CLASS = "/org/apache/catalina/startup/Embedded.class";

     

                   public static final String TOMCAT_ID = "tomcat";

     

                   public static final String WEBLOGIC_CLASS = "/weblogic/Server.class";

     

                   public static final String WEBLOGIC_ID = "weblogic";

     

                   public static final String WEBSPHERE_CLASS = "/com/ibm/websphere/product/VersionInfo.class";

     

                   public static final String WEBSPHERE_ID = "websphere";

                  

     

                   private static ServerDetector _instance = new ServerDetector();

     

                   private String _serverId;

     

                   private Boolean _jeus;

     

                   private Boolean _geronimo;

     

                   private Boolean _glassfish;

     

                   private Boolean _jBoss;

     

                   private Boolean _jetty;

     

                   private Boolean _jonas;

     

                   private Boolean _oc4j;

     

                   private Boolean _orion;

     

                   private Boolean _pramati;

     

                   private Boolean _resin;

     

                   private Boolean _rexIP;

     

                   private Boolean _sun7;

     

                   private Boolean _sun8;

     

                   private Boolean _tomcat;

     

                   private Boolean _webLogic;

     

                   private Boolean _webSphere;   

                  

                   private ServerDetector() {

                   }       

                  

     

                   public static String getServerId() {

                                  ServerDetector sd = _instance;

     

                                  if (sd._serverId == null) {

                                                   if (ServerDetector.isJeus()) {

                                                                  sd._serverId = JEUS_ID;

                                                   }

                                                   else if (ServerDetector.isGeronimo()) {

                                                                  sd._serverId = GERONIMO_ID;

                                                   }

                                                   else if (ServerDetector.isGlassfish()) {

                                                                  sd._serverId = GLASSFISH_ID;

                                                   }

                                                   else if (ServerDetector.isJBoss()) {

                                                                  sd._serverId = JBOSS_ID;

                                                   }

                                                   else if (ServerDetector.isJOnAS()) {

                                                                  sd._serverId = JONAS_ID;

                                                   }

                                                   else if (ServerDetector.isOC4J()) {

                                                                  sd._serverId = OC4J_ID;

                                                   }

                                                   else if (ServerDetector.isOrion()) {

                                                                  sd._serverId = ORION_ID;

                                                   }

                                                   else if (ServerDetector.isPramati()) {

                                                                  sd._serverId = PRAMATI_ID;

                                                   }

                                                   else if (ServerDetector.isResin()) {

                                                                  sd._serverId = RESIN_ID;

                                                   }

                                                   else if (ServerDetector.isRexIP()) {

                                                                  sd._serverId = REXIP_ID;

                                                   }

                                                   else if (ServerDetector.isSun7()) {

                                                                  sd._serverId = SUN7_ID;

                                                   }

                                                   else if (ServerDetector.isSun8()) {

                                                                  sd._serverId = SUN8_ID;

                                                   }

                                                   else if (ServerDetector.isWebLogic()) {

                                                                  sd._serverId = WEBLOGIC_ID;

                                                   }

                                                   else if (ServerDetector.isWebSphere()) {

                                                                  sd._serverId = WEBSPHERE_ID;

                                                   }

                                                   else if (ServerDetector.isWebSphere()) {

                                                                  sd._serverId = WEBSPHERE_ID;

                                                   }

                                                   if (ServerDetector.isJetty()) {

                                                                  if (sd._serverId == null) {

                                                                                  sd._serverId = JETTY_ID;

                                                                  }

                                                                  else {

                                                                                  sd._serverId += "-" + JETTY_ID;

                                                                  }

                                                   }

                                                   else if (ServerDetector.isTomcat()) {

                                                                  if (sd._serverId == null) {

                                                                                  sd._serverId = TOMCAT_ID;

                                                                  }

                                                                  else {

                                                                                  sd._serverId += "-" + TOMCAT_ID;

                                                                  }

                                                   }

                                                  

                                                   System.out.println("Detected server ===========================> " + sd._serverId);

                                                  

                                                   if (sd._serverId == null) {

                                                                  throw new RuntimeException("Server is not supported");

                                                   }

                                  }

     

                                  return sd._serverId;

                   }

     

                   public static boolean isJeus() {

                                  ServerDetector sd = _instance;

     

                                  if (sd._jeus == null) {

                                                   sd._jeus = _detect(JEUS_CLASS);

                                  }

     

                                  return sd._jeus.booleanValue();

                   }       

                  

                   public static boolean isGeronimo() {

                                  ServerDetector sd = _instance;

     

                                  if (sd._geronimo == null) {

                                                   sd._geronimo = _detect(GERONIMO_CLASS);

                                  }

     

                                  return sd._geronimo.booleanValue();

                   }

     

                   public static boolean isGlassfish() {

                                  ServerDetector sd = _instance;

     

                                  if (sd._glassfish == null) {

                                                   sd._glassfish = _detect(GLASSFISH_CLASS);

                                  }

     

                                  return sd._glassfish.booleanValue();

                   }

     

                   public static boolean isJBoss() {

                                  ServerDetector sd = _instance;

     

                                  if (sd._jBoss == null) {

                                                   sd._jBoss = _detect(JBOSS_CLASS);

                                  }

     

                                  return sd._jBoss.booleanValue();

                   }

     

                   public static boolean isJetty() {

                                  ServerDetector sd = _instance;

     

                                  if (sd._jetty == null) {

                                                   sd._jetty = _detect(JETTY_CLASS);

                                  }

     

                                  return sd._jetty.booleanValue();

                   }

     

                   public static boolean isJOnAS() {

                                  ServerDetector sd = _instance;

     

                                  if (sd._jonas == null) {

                                                   sd._jonas = _detect(JONAS_CLASS);

                                  }

     

                                  return sd._jonas.booleanValue();

                   }

     

                   public static boolean isOC4J() {

                                  ServerDetector sd = _instance;

     

                                  if (sd._oc4j == null) {

                                                   sd._oc4j = _detect(OC4J_CLASS);

                                  }

     

                                  return sd._oc4j.booleanValue();

                   }

     

                   public static boolean isOrion() {

                                  ServerDetector sd = _instance;

     

                                  if (sd._orion == null) {

                                                   sd._orion = _detect(ORION_CLASS);

                                  }

     

                                  return sd._orion.booleanValue();

                   }

     

                   public static boolean isPramati() {

                                  ServerDetector sd = _instance;

     

                                  if (sd._pramati == null) {

                                                   sd._pramati = _detect(PRAMATI_CLASS);

                                  }

     

                                  return sd._pramati.booleanValue();

                   }

     

                   public static boolean isResin() {

                                  ServerDetector sd = _instance;

     

                                  if (sd._resin == null) {

                                                   sd._resin = _detect(RESIN_CLASS);

                                  }

     

                                  return sd._resin.booleanValue();

                   }

     

                   public static boolean isRexIP() {

                                  ServerDetector sd = _instance;

     

                                  if (sd._rexIP == null) {

                                                   sd._rexIP = _detect(REXIP_CLASS);

                                  }

     

                                  return sd._rexIP.booleanValue();

                   }

     

                   public static boolean isSun() {

                                  if (isSun7() || isSun8()) {

                                                   return true;

                                  }

                                  else {

                                                   return false;

                                  }

                   }

     

                   public static boolean isSun7() {

                                  ServerDetector sd = _instance;

     

                                  if (sd._sun7 == null) {

                                                   sd._sun7 = _detect(SUN7_CLASS);

                                  }

     

                                  return sd._sun7.booleanValue();

                   }

     

                   public static boolean isSun8() {

                                  ServerDetector sd = _instance;

     

                                  if (sd._sun8 == null) {

                                                   sd._sun8 = _detect(SUN8_CLASS);

                                  }

     

                                  return sd._sun8.booleanValue();

                   }

     

                   public static boolean isTomcat() {

                                  ServerDetector sd = _instance;

     

                                  if (sd._tomcat == null) {

                                                   sd._tomcat = _detect(TOMCAT_BOOTSTRAP_CLASS);

                                  }

     

                                  if (sd._tomcat == null) {

                                                   sd._tomcat = _detect(TOMCAT_EMBEDDED_CLASS);

                                  }

     

                                  return sd._tomcat.booleanValue();

                   }

     

                   public static boolean isWebLogic() {

                                  ServerDetector sd = _instance;

     

                                  if (sd._webLogic == null) {

                                                   sd._webLogic = _detect(WEBLOGIC_CLASS);

                                  }

     

                                  return sd._webLogic.booleanValue();

                   }

     

                   public static boolean isWebSphere() {

                                  ServerDetector sd = _instance;

     

                                  if (sd._webSphere == null) {

                                                   sd._webSphere = _detect(WEBSPHERE_CLASS);

                                  }

     

                                  return sd._webSphere.booleanValue();

                   }

     

                   private static Boolean _detect(String className) {

                                  try {

                                                   ClassLoader.getSystemClassLoader().loadClass(className);

     

                                                   return Boolean.TRUE;

                                  }

                                  catch (ClassNotFoundException cnfe) {

                                                   ServerDetector sd = _instance;

     

                                                   Class c = sd.getClass();

     

                                                   if (c.getResource(className) != null) {

                                                                  return Boolean.TRUE;

                                                   }

                                                   else {

                                                                  return Boolean.FALSE;

                                                   }

                                  }

                   }

    }

    posted by 좋은느낌/원철
    2008. 7. 1. 11:18 Web

    ● 상태코드범위와 의미

    100 - 199 : 정보성, 서버가 http요청을 받았으며 클라이언트는ㄴ 요청을 계속할수 있음을 나타냄.

    200 - 299 : 성공, 서버가 요청을 성공적으로 수신하고 이해했으며 수용했음.

    300 - 399 : 리다이렉션 , 요청을 완료하기 위해 더 많은 액션이 필요함

    400 - 499 : 클라이언트 에러 , 클라이언트로 인해 에러가 발생했음을 나타낸다.

    500 - 599 : 서버에러, 요청을 처리하는 도중에 발생한 서버측 에러를 나타낸다.


    ● http 응답 상태 코드

    200 : OK, 요청이 정상적으로 성공했음을 나타낸다.

    302 : Moved Temporarily, 클라이언트가 요청한 리소스가 임시적으로 이동했으며 클라이언트가 리소스에 접근하기 위해 현재의 URI를 반환해야 함을 나타냄. 클라이언트는 자동으로 GET또는 HEAD 요청을 리다이렉트 할것이다.

    400 : Bad Request, 클라이언트의 요청이 부정확한 형태이며 서버가 요청을 처리 할 수 없음

    401 : Unauthorized, 클라이언트가 제공되지 않은 사용자 인증이 필요한 리소스를 요청했거나 인증에 실패했음.

    403 : Forbidden, 서버가 클라이언트의 요청을 이해하지만 클라이언트가 요청한 리소스의 접근을 거절.

    404 : Not Found, 요청된 URI에 리소스를 위치시킬수 없는 웹서버가 사용한다. <-- 뭔말이지? 파일없단거지 뭐..ㅋㅋ

    500 : Internal Server Error, 요청을 처리하는 서버나 리소스에 예기치 않은 에러가 발생하여 요청을 처리할수 없음을 나타냄. <-- 거의 문법오류이거나 Exception의 경우...

    posted by 좋은느낌/원철
    2008. 6. 30. 17:55 일상/교회
    출처 : http://blog.naver.com/jjkkhh2232?Redirect=Log&logNo=50004370920

    여러분은 믿음이 약한 이를 받아들이고 그의 생각을 시비거리로 삼지 마십시오.....그런데 어찌하여 우리는 형제나 자매를 비판합니까? 우리는 모두 다 하나님의 심판대 앞에 서게 될 것입니다(롬 14:1, 10).

    그러므로 여러분은 먹고 마시는 일이나 명절이나 초승달 축제나 안식일 문제로 어떤 사람도 여러분을 심판하지 못하게 하십시오. 이런 것들은 앞으로 올 것들의 그림자일뿐이요, 그 실체는 그리스도에게 있습니다.(골 2:16-17)

    필자는 이미 주5일근무제논쟁을 통하여 새로운 안식일-주일논쟁의 토대가 마련되었고 이번 대구에서 발생한 안식일과 십일조와 관련된 목사제명문제가 오히려 한국교회에 긍정적인 기여를 할 것이라고 생각한다. 심각한 안식일논쟁은 예수 당시(예, 마 12:1-14; 눅 13:10-17; 14:1-6; 요 5:1-18)와 초대교회(예, 골 2:16-17; cf. 롬 14:5-8; 히 4:1-11)에서도 있었지만, 교회사적으로도 안식일 논쟁은 그 역사가 깊다. 이러한 배경하에서 우리가 안식일에 대한 신학적 의미와 현대적 적용을 제대로 하기 위해서는 신약내에서의 일요일(혹은 주의 날)과 안식일과의 관계성을 살펴볼 뿐만 아니라, 교회사적인 이해와 발전도 살펴보아야 한다.

    다음의 구분은 Carson(1999:14ff.)의 것을 따랐다.

    1) 전통적인 청교도적 안식일-주일엄수주의: W. Rordorf-J. Franke-R. T. Beckwith & W. Stott로 이어지는 계통으로 안식일인 토요일이 일요일인 주일-안식일로 전이되었다고 주장한다.

    2) 수정된 청교도적 안식일-주일엄수주의: P. K. Jewitt가 대표적인 인물로 전이의 개념보다는 초대교회의 일부 일요일준수의 관습에 근거하여 주장하였다.

    3) 제7일안식교의 안식일-토요일엄수주의: S. Bacchiocchi가 안식교의 대표적인 인물로 일요일예배는 초대교회의 산물이 아니며, 태양숭배의 제의에서 차용한 것이라고 주장하였다.

    4) 반(反)안식일 엄수주의:D. A. Carson(p. 16)등이 주장한 것으로, 세부적으로는 다음과 같은 견해를 가진다:

    ① 신약에서 안식일에서 주일로의 전환신학(\'transfer\' theology)을 발전시켰다고 보기는 어렵다 ② 안식일준수가 창조시부터의 원리로 보기는 어렵다.
    ③ 신약의 저자들이 의식법과 윤리법으로 구분하였다고 보지 않는다.
    ④ 주일의 준수는 2세기부터 발생한 것이라고 보기는 어렵다.
    ⑤ 일요예배가 기독교인의 안식일은 아니다.

    1. 구약에서의 안식과 관련된 제도와 절기들

    구약에 안식일 개념이 한가지로 유지되어 왔다고 이해하는 것은 잘못이다. 구약 속에서도 안식일의 개념은 변천되었으며 구약의 안식일 개념은 다양한 하부요소들을 포함하고 있다. 여기서는 개략적으로 그 의미와 하부요소들을 나열하고 그 신학적 의미와 내용을 정리하는데 그칠 것이다.

    1.1. 하나님의 창조와 연관된 안식일 규정(창 2:1-3; 출 20:11; 31:17)

    안식(싸바쓰)의 개념은 창세기에서 하나님의 창조원리에서도 발견된다. 그러나 안식일준수라는 개념이 창조원리에서 등장하는 것도 아니며 법제화 되었다고 말할 수는 없다. 오히려 창세기 본문은 창조사역완성과 갱신의 의미의 안식이 나타날 뿐이다. 다시 말하면 안식이란 단순히 일의 정지나 사역의 그침의 의미보다는 기존의 일의 마무리(終結)와 새로운 일을 위한 원기회복(refreshment)을 의미할 뿐이다. 또한 낮과 밤이 반복되는 6일의 순환과는 다른 시작과 끝이 없는 7일째의 안식은 그 개념이 쉽게 종말론적인 개념으로 확대(擴大)되기도 한다. 7일째날을 복주시고 거룩케 하셨다는 것은 하나님의 주권적 사역의 일환이었다는 점도 말해준다. 다시 말하자면, 7일과 인간의 행위와는 무관하게 하나님의 주권적 선언과 행위로 말미암아 거룩해진다는 것이다. 다시 말하자면, 창조의 안식일은 하나님을 위한 것이었다.

    안식일규정으로서의 법제화는 창세기보다는 출 20:11에서 찾아보아야 한다. 안식일은 언약의 표이며 언약준수의 시금석으로 작용한다(출 31:12-17; 겔 20:10-26; 22:8; 26; 23:28). 출애굽기에서는 하나님의 노동이 인간의 노동과 대조되어 인간의 측면에서의 노동과 쉼의 개념과 대조되어 입법화되었다. 중요한 점은 특히 출 34:21; 23:12에서는, 모든 노동하는 존재들(주인과 가족과 종과 동물들까지도)에 대한 배려가 나타난다. 이러한 개념들은 안식년에서 발견되는 가난한 자들과 땅과 들짐승에 대한 배려까지도 포함된다고 하겠다. 여기서의 안식일의 거룩성은 무차별적으로 노동에서의 쉼(즉 창조의 원리의 회복)을 상기하는 것이고 준수하는 것이었다.

    여기서 우리는 인간애적인 개념을 넘어서는 창조원리의 적용과 하나님의 보편적인 사랑과 정의의 집행을 염두에 두는 것이다. 안식에 대한 거부는 단순히 날짜를 지킴으로서 얻는 종교적 만족감이나 탁월성의 보상이라기보다는 창조섭리에 대한 거부로 여겨져서 사형이 집행되었다(출 31:12-17). 추가적으로 신 5:12-15장에서는 이스라엘 백성을 이집트에서 탈출시키는 새창조적인 개념에서의 안식의 개념과 노예의 중한 짐으로부터의 놓임을 기념하는 날로서의 휴식이 강조되었다.

    1.1.2. 제의적 규정들(레 23:3; 민 28:9-10; cf. 레 24:5-9)과 세부적인 금기사항들(출 16:22-30; 35:3; 민 15:32-36)

    긍정적으로는 하나님의 창조 섭리 혹은 구속사적 업적을 감사하며 찬양하며 예배하는 일들에 대한 규정도 있으며 부정적으로는 하나님이 안식(일)의 주관자시며 그 안식의 거룩성이 하나님의 본성에서 나온다는 점을 상기시키기 위함이다. 중요한 것은 언약의 당사자인 야웨와 이스라엘 사이에서 법적인 구속력을 갖고 있었으므로 안식일준수의 거부는 언약파기에 따르는 처벌을 받아야 했다. 우리는 여기서 그리스도와 새 이스라엘백성이 맺은 언약의 조건이 안식일준수였는가, 즉 안식일준수가 그리스도의 언약의 표였는가도 고려해볼 필요가 있다.

    1.1.3. 선지서에 나타난 안식일준수의 특징들(사 1:13; cf. 호 2:13; 사 66:23; 겔 45:16-17; 46:1-12)

    마찬가지로 그러한 안식의 본뜻과 그에 대한 제의적 규정들과 세부적인 금기사항에 대한 일종의 적용으로서 선지서는 안식일을 다루고 있다고 하겠다. 제의와 삶과 무관한 성일(聖日)과 절기와 희생제물과 안식일의 준수는 무가치한 것이며 하나님의 뜻을 알지 못하는 것으로 여겨졌다(호 2;13; 사 66:23; 사 1:13; 호 2:13; 암 8:5; cf. 사 56:18). 또한 안식일의 위반이 예루살렘의 멸망의 원인인 것처럼 여겨진다. 안식일이 언약준수의 표일 수는 있지만, 안식일 준수가 곧 언약 그 자체는 아니다.

    정리컨대, 구약에서의 안식일준수의 개념은 인류애와 신학적인 내용적 특징을 갖고 있지만, 이스라엘과 야웨와의 언약적 측면에서 이해해야 할 것이며 단순한 쉼의 문제뿐만 아니라, 야웨에 대한 경배와 찬양적 요소들도 포함하였다. 구약에서도 안식일에서 일을 해야 할 경우가 있었다. 그러나 일상적인 노동행위는 중지해야 했다. 그날에 감사와 찬송과 하나님의 신실성에 대한 선포와 노래로 기뻐하고 찬양하며 하나님을 신뢰하였던 것 같다(시 92편). 그러나 이러한 안식일의 예배와 찬양적 목적은 일주일 전반에서의 표면적인 의무를 축소하거나 대체하는 절대적인 중요성을 갖고 있는 것은 아니다.

    2. 신약에서의 그리스도와 종말론적 안식일

    신구약중간시대와 예수시대의 유대교의 안식일은 수많은 규정들의 확대와 문자적 준수를 발견하게 된다. 이러한 경향성은 지극히 세밀한 결의론적 조항들의 산출과 안식일위반에 대한 관대한 이해의 발전, 그리고 언약적 의미와 축제적 성격의 강조가 결여되는 경향을 갖게 되었다. 그러한 배경과 구약적 배경 속에서 복음서에서는 안식일의 주관자이시며 안식을 이루시는 분으로서의 그리스도에 대한 이해가 등장한다.

    이것은 전혀 새롭거나 율법을 파괴하는 행동이나 이해가 아니었다. 소위 율법주의자들과 기독교인들과의 갈등도 선별적 문자주의 혹은 규례주의자들에 대한 반응으로 발생하였다는 점도 상기되어야 한다. 바리새인들과 율법주의자들의 관심은 안식일을 어떻게 지킬 것인가? 였다면, 예수의 관심은 왜 안식일을 지키는가? 였다. 일견 예수의 안식일의 완성은 인류애적인-정의적인 개념보다는 안식일의 주관자로서 종말론적인 완성의 측면에 더 강조점이 있었던 것 같다.

    예수는 습관대로 안식일에 회당을 찾아 가르치셨고 실제의 사역(귀신의 축출과 다양한 치유사역들, 이삭을 베어먹을 때의 교훈, 안식일 회당에서의 희년복음의 선포 등)을 통하여 안식일의 참 의미를 청중들에게 전하려고 하셨던 것으로 보인다. 또한 예수는 하나님이 일하시는 것처럼 자신도 안식일에서도 일한다고 말한다. 이는 전혀 모순되는 행동이 아니다.

    바울에게서 안식일-주일관은 크게 부각되지 않지만, 그에게 있어서 날과 절기를 지키는 것은 무의미하다. 바울은 그리스도의 법을 따랐으며 성령의 법에 의존하였다. 단언할 때, 날을 지키는 것과 지키지 않는 것으로 신앙의 척도로 판단하는 것은 옳은 것이 아니라고 말하는 점이다. 이것은 자유의 내용이다. 그러나 우리의 종말론적 안식관은 히브리서에서 더 현저하게 발견될 수 있다. 히브리서에서는 그리스도를 통한 안식의 성취와 현재적 안식화(安息化), 그리고 미래적 안식의 약속으로 구분될 수 있다. 다시 말하자면, 이미 그리스도를 통하여 안식은 시작되었으며 우리가 그리스도의 구원에 참여함으로 현재적으로 안식에 들어가기를 힘쓰는 것이며 종말론적으로 그리스도의 재림을 통하여 창조시에서 제시되었던 종말론적 안식(eschatological rest)은 완성될 것이다.

    2.1 신구약에서의 '주의 날'의 개념

    구약에서 '주의 날'은 주로 종말론적인 측면에서 심판과 징벌의 날을 의미하였다. 신약에서는 안식후 첫날이라는 표현으로도 사용되기도 했지만, '주의 날'이라는 표현이 계1:10에서 단 한 번 사용된다. 우리는 지나치게 불완전한 증거들을 갖고 신약본문들내에서 전이신학이나 토요일-안식일준수관(제7일안식교도들은 이점을 주장한다)을 고집할 수는 없는 것이다. 오히려 우리는 속사도시대 때 등장하는 문헌들에서 기독교인들이 유대인의 안식일과의 차별성을 강조하고 있다는 점도 유의해야 할 것이다.

    결과적으로 주의 날이 매주 행해지는 특정한 예배의 날로 고정되었다. 그러나 문제는 이러한 특정한 예배의 날(일요일)의 기원이 신약시대로 돌아갈 수 있느냐는 것이다. 물론 일요일에 예수 그리스도가 부활하였다는 역사적 확증과 신념은 변할 수 없는 것이지만, 안식후 첫 날에 예수 그리스도가 부활하였다는 점만으로 기독교의 일요예배의 기원을 삼을 수는 없다. 그러나 예배와 모임의 측면에서의 초대기독교인들의 집회는 적어도 유대인 기독교인들에게 있어서는 일시적이었을지라도, 안식일모임과 기독교인들의 가정모임이 병존했던 것으로 보인다.

    어쨌든 계시록에서의 주의 날이 일요일이라는 특정한 날일 수는 있으나, 이것은 그리스도의 부활이라는 날과의 연관성 속에서 기독교인들의 예배모임을 규정짓는 것일 수 있으나, 그렇다고 해서 그리스도의 주권이나 그리스도에 대한 예배가 '주의 날'로만 축소될 수는 있는 것은 아니라는 점은 명심해야 한다. 또한 고린도전서(고전 16:2)에 나오는 일주일의 첫날이라는 개념은 기독교인들의 집회날이거나 예배중에 헌금을 내라는 증거본문이 아니다.

    비록 그것이 소위 일요일에 받았던 요한의 묵시였을 지라도, 요한계시록의 종말론적 교회관과 예배개념은 우리의 주일예배의 그것과는 스케일의 면에서 상당한 차이가 있다. 앞서 말한대로, 초대교회공동체 내에서 유대인의 안식일과 기독교인의 주의 날에 대한 대조적인 이해가 태동하였다고 볼 수는 있지만, 신약의 신앙공동체가 주의 날이 안식일을 대체하거나 계승하는 날로 보았다는 확고한 증거는 찾을 수가 없다. 물론 안식일의 연장이라는 개념도 찾을 수 없다.

    3. 신약시대 이후의 안식일-주일 논쟁

    속사도시대(주후 120-400년경)-- 얼마 남아있지 않는 자료들을 통해서 볼 때 유대인의 안식일준수와 기독교인의 주일에 대한 차별적 추구는 점차로 명확해지는 것처럼 보인다. 그러나 이러한 <차별과 단절>의 의식은 후대에 가서 <차별과 연속>의 의식으로 변천하였던 것 같다. 속사도들은 주일과 안식일을 예배의 날로서 연관시켰으나, 쉬는 날(안식일)로서는 연관시키지 않았던 것 같다. 오히려 쉬는 날로서의 일요일은 콘스탄틴대제의 칙령에 의해서(321년) 행해졌다.

    초창기에는 (이그나티우스의 경우에) 안식일적 개념은 극명하게 배제된 것처럼 보인다. 아마도 이것은 신약시대에 나타나는 율법주의적 경향이 속사도시대 때 더 극심해졌기 때문에 사용되었던 극약처방일 수도 있다. 안식일준수가 정죄되었고 주의 날이 대안으로 제시되고 있다. 그러나 이러한 대조는 후대에 안식일에서 주일로의 전이신학을 발전시킬 수 있는 토대를 마련하였다는 점도 부인될 수 없다. 바나바의 경우는 상당히 알레고리적으로 구약을 이해하지만, 그에게 있어서 안식일은 미래적인 성취로 이해하였다. 그는 일요일을 축하의식과 부활승천을 기념하는 날로 보았다. 디다케는 주의 날에 거룩한 식사를 하고 죄를 고백하도록 하라고 권면한다. 마지막으로 디오그네투스 서신에서도 유대교의 안식일준수에 대한 반대의 논증은 명확하게 나타난다. 추가로 일요모임 혹은 예배에 대한 증거들은 산발적인데, 다음과 같다. 소아시아 버두니아 지방총독을 지낸 플리니는 트라얀 황제에게 보낸 서신(109년)에서 황제의 칙령을 따라 모든 일요일의 저녁집회를 금지하자, 기독교인들이 저녁식사시간에 모이는 것을 그만 두었다고 보고한다. 이후로 비두니아 지방의 일요일 저녁집회는 사라지고 나중에 제국 전체로 확대된 것 같고 결과적으로 일요일 아침예배로 이전되었다고 본다. 그는 기독교인들이 지정된 날 동트기 전에 미리 모여 모임의 의식을 가졌다고 한다. 저스틴(Justin) 역시 자신의 변증서에서 일요일 아침에 예배를 보았다고 명시하였다.

    중세-- 명확한 의미에서 안식일에서 주일로의 전이신학(轉移神學)은 330년이후 유세비우스의 시편 91편주석에서 발견된다. 그러나 그에게도 그 전이는 예배적 측면이었지, 안식의 측면은 아니었다. 심지어 6세기까지 일요일에 일하는 것을 금하는 교회법적 시도는 거의 없었다. 우리가 알고 있는 전통적인 일요일적 안식일엄수주의는 토마스 아퀴나스(1225-74)에게서 나온다. 그는 스콜라철학적 자연법이론에 근거하여 십계명은 자연법이므로 여전히 그리스도인들에게 유효하다고 보았다. 그에 따르면, 의식법적인 측면(7일)은 폐지되었으나, 도덕적 측면(노동의 금지)은 유효하다고 하였다.

    종교개혁시대--종교개혁자들은 이러한 스콜라철학적 안식일엄수주의를 강력히 공격하거나 포기하였다. 예를 들어서 장 칼뱅(기독교강요 2.8.28-34)은 안식일이 그리스도안에서 성취되었으며 폐지되었고 일요일예배의식은 편의와 질서를 위한 것이지만, 일요일의 휴식은 예배시간을 확보하기 위한 것일뿐 그 이상의 의미는 없는 것이라고 말했고 일요일-안식일준수라는 교회규정을 고수하는 자들의 미신은 유대인들보다 세배나 더 유치하고 육욕적인 안식일 엄수주의적 미신이라고 말할 정도였다(2.8.34).

    종교개혁이후시대--불행하게도 17세기 이후에 청교도들은 종교개혁 이전의 안식일엄수주의로 회귀하였고 한국장로교신앙의 직접적인 모태가 되었던 웨스트민스터신앙고백서(소요리문답, 예배모범 등)에는 전형적인 그러나 더 심한 청교도적 안식일엄수주의가 내포되었다. 이후에도 이러한 청교도적 안식일주의외에도 이와 유사한 운동들은 제7일안식일침례교나 제7일안식일재림교에 의해서 날짜가 변경된 채로 유사하게 주장되었다.

    우리가 이러한 개괄적인 연구를 통하여, 구약적 안식일과 유대교적 안식일, 기독교적 안식일간의 차이를 알 수 있었다. 결과적으로 우리가 지키고 있는 이러한 기독교안식일의 개념은 오히려 유대주의적-스콜라철학적 안식일엄수주의-청교도적 안식일엄수주의일뿐 성경적인 안식사상이나 바울의 자유와 관용의 정신이나 심지어 요한 칼빈의 안식일준수에 대한 이해와도 상응하지 못한다는 점을 깨닫게 한다.

    4. 결론

    개인적으로 필자는 본 논문을 정리하면서 더 확고한 안식일-주일관을 나름대로 정립할 수 있게 되었다. 이 말은 Carson의 견해들을 따르는 것이 더 성경적이고 합리적인 것일 수 있다는 것을 의미한다. 그러나 이러한 이론적 동의는 건전한 안식관에 대한 공유와 실천적 대안을 가질 때에야 비로소 진정한 호소력을 가질 수 있다고 본다.

    필자는 이 상황 속에서 몇가지 제안을 하고자 한다.

    1) 기독교의 본질은 예수 그리스도라는 측면에서 모든 대화나 화해는 시작되어야 한다. 그리스도의 공동체내에서 이러한 세부적인 신학적 의미에 대해서 일차적으로 동의되지 않는다 하더라도 우리는 그리스도안에서의 동질성과 형제애적 측면에서 이 문제를 다루어야 한다.

    2) 이러한 주제에 대해서 발생하는 교회 내에서의 정죄와 갈등과 분열은 성경적으로나 교회사적인 측면에서 볼 때, 불건전하며 합당한 대처방안이 아니다. 마찬가지로 안식의 개념에 대한 충분한 논의 없이 이러한 다양한 주장들에 대한 억압이나 무관심으로 대처하거나 일부 대형교회들이 구상하듯이, 주말교회나 전원교회로 주5일근무제 등 안식일-주일에 대한 논란을 해결하려는 시도는 바람직하지 못하다(물론 기존교인들의 휴가욕구와 기존교회들의 일요일예배 참여 유도라는 측면에서 긍정적인 면이 없는 것은 아니나, 지방이나 농촌지역의 교회들과 연계하여 도움을 주고 섬기는 프로그램보다는 일부 대형교회들이 휴양지나 지방에 분점교회나 예배의식프로그램을 운영하는 것은 옳지 못하다).

    3) 안식일-주일논쟁이 이해당사자들뿐만 아니라, 제3자들에 의해서도 학술적이고 객관적이고 실제적인 측면에서 지속적으로 전개되어야 한다.

    4) 일요일에 세속적 노동의 중단과 예배의식에 참여 및 교회봉사라는 측면보다는, 쉼과 예배, 그리스도와 구원, 종말적 임재> 그리고 <정의와 자비와 인권>이라는 신구약성경의 안식의 개념의 이해와 실제화가 본 논의의 중심으로 다루어야 한다.

    5) 소위 주일예배는 한시간 정도의 '예배' 퍼포먼스 그 이상이라는 점이 인식되어야 한다.

    기독교인의 일요일예배의식이란 특정한 '하루'동안 나머지 6일의 삶의 영역에서의 그리스도에 대한 예배(즉, 그리스도의 주권인정과 주권의 확장)와 그가 우리에게 마련해주시는 안식의 의미를 일깨워주고 신앙공동체내에서 실현되고 예시되는 한에서만 의미가 살아있는 것이다. 이러한 예배의식은 신약시대의 공동체들에게 있어서 하나의 삶의 한 부분과 영역이었지, 삶과 괴리된 현재와 같은 하나의 '지켜야' 할 의식(儀式)이었던 적은 없다. 다시 말하자면, 기독교인들에게 있어서 안식의 의미는 단순히 지킬 것인가 말 것인가의 의식적 차원도 아니고, 선별적이고 자의적인 측면에서의 안식일엄수주의가 아니라, 창조의 질서와 종말론적 기대 속에서 본래의 뜻이 인지되고 회복되어야 할 것이다.
    posted by 좋은느낌/원철
    2008. 6. 30. 17:51 개발/Java

    출철 : http://msinterdev.org/blog/entry/APIsForJAVA

    pistosApi

    http://pistos.pe.kr/javadocs/main.do


    JConfig

    http://www.jconfig.org/

    JConfig와 Spring - http://www.zonic-temple.org/blog.jz?fn=jconfig_spring


    displayTag - http://openframework.or.kr/displaytag/ (번역)

                      http://displaytag.sourceforge.net/


    프레임워크  및 자바 기술 관련 아티클

    http://julymon.enbee.com/


    Appfuse Wiki

    http://raibledesigns.com/wiki/


    오픈소스 프레임워크 네이버카페 - 위키 페이지

    http://openframework.or.kr

    Appfuse 한국어 번역 위키 역시 같이 있음


    그중 XmlBeans 번역 - http://openframework.or.kr/JSPWiki/Wiki.jsp?page=XMLBeans

          Groovy JDBC 번역 - http://openframework.or.kr/JSPWiki/Wiki.jsp?page=Groovyjdbcprogramming

          마소Groovy - http://openframework.or.kr/JSPWiki/Wiki.jsp?page=Groovy

          JMeter Start 페이지 - http://openframework.or.kr/JSPWiki/Wiki.jsp?page=Jmeterstartpage

          등등의 좋은 번역물및 포스트 다수


    Spring 카페

    http://cafe.naver.com/springframework.cafe


    서영아빠님 블로그

    http://blog.naver.com/ecogeo.do


    오픈시드닷넷

    http://www.OpenSeed.net


    좋은 자료들(장윤기님)

    http://www.javamania.pe.kr


    박영록님 블로그

    http://youngrok.com/wiki/wiki.php


    이종하님의 홈페이지

    http://eclipse.new21.org/phpBB2/index.php

    IT 거버넌스

    http://itgs.egloos.com/


    MyJavaPack

    http://www.open-centric.com/myjavapack/index.html


    물개선생님 블로그

    http://openseed.net/seal/


    토비님 블로그

    http://toby.epril.com/index.php


    서민구님

    http://mkseo.pe.kr/frame.htm

    이희승님


    김형준님

    http://jaso.co.kr/tatter/index.php


    써니님(개발에 대한 탁월한 지혜와 식견)

    http://sunnykwak.egloos.com


    우하하님

    http://woohaha.egloos.com/


    아키텍트로 계시는 투덜이님 블로그

    http://agrumpy.egloos.com/


    숏달님 블로그

    http://nicejay.egloos.com/


    이글루스 siegzion님..(개발자)

    http://siegzion.egloos.com/



    엠파스  쪽박님 블로그(Spring등 관련 )

    http://blog.empas.com/blackcomb7


    POSA및 주옥같은 정리[케빈님]

    http://kevinheo.cafe24.com/

    http://blog.naver.com/kyeongwook94


    최범균님 강좌페이지

    http://javacan.madvirus.net/main/content/contentCategory.jsp


    자바아티클 및 관련글 번역

    http://blog.empas.com/kkamdung/list.html?c=212880




    =================================================================

    좋은 컨텐츠를 포스팅 하시는 분들의 블로그들

    =================================================================

    뚱가님의 블로그[오픈소스와 J2SE관련 번역글및 포스트]

    http://blog.naver.com/toongas


    Ajax강의/컬럼 과 다른(자바관련등) 자료들

    http://blog.naver.com/jinoxst


    굿현님의 블로그

    http://goodhyun.com/


    이동국님의 블로그 중 iBatis SQLMap 관련

    http://openframework.or.kr/blog/?cat=4


    이글루스의 serenic - Spring등 오픈소스 관련 좋은 포스트

    http://serenic.egloos.com/


    개발관련

    http://blog.empas.com/kkamdung


    http://blog.empas.com/naruma


    건호랩님의 블로그 - 자바 관련 팁

    http://blog.naver.com/devstory


    한기아빠님 블로그 - 웹서비스등

    http://blog.naver.com/junghyun


    자바 강의 자료

    http://blog.naver.com/skyymj106

    =================================================================







    이클립스 튜토리얼 프로젝트의 한글 서비스

    https://eclipse-tutorial.dev.java.net/


    http://ubuntu.or.kr/wiki.php - 우분투 한국어 위키

    Ubuntu Linux - Welcome to Ubuntu Linux => 오리지날 홈

    * KLDP 비비에스

    http://bbs.kldp.org/


    * 오픈소스 프로젝트 지원

    http://kldp.net/


    mail 관련

    http://www.vipan.com/htdocs/javamail.html


    * DB관련

    오라클어드민

    http://blog.naver.com/kddasang.do

    Pro*C관련

    http://hobak.emailkorea.net/hobak/proc/

    오라클 technical bulletin

    http://211.106.111.113:8880/bulletin/list.jsp


    * 웹관련

    일모리님 블로그

    http://ilmol.com/wp/


    js 퍼포먼스 테스트

    http://www.umsu.de/jsperf/


    자바스크립트 정리잘된 블로그(쎄이님)

    http://blog.naver.com/tear230



    자바스크립트 관련 레퍼런스& 소스 사이트

    http://jsguide.net/

    http://www.cginjs.com

    http://java.up2.co.kr/

    코리아인터넷닷컴 자바스크립트

    안광해의 자바월드

    이용석의 자바스크립트와 다이나믹 HTML


    자바및 자바스크립트 정리노트

    http://www.javaclue.org/index.jsp ( 자바클루)








    Oracle관련 사이트

    SQL tip & 참고자료들 블로그 : http://blog.naver.com/orapybubu

    -----------------------------------------------------------------------------------

    JVM tunniing Blog -> http://blog.chosun.com/blog.screen?blogId=155&menuId=36901



    거장들

    ------------------------------------------------------------------------------------

    마틴파울러

    http://www.martinfowler.com/



    레퍼런스 사이트

    -------------------------------------------------------------------------------------

    http://www.ambysoft.com : OR 매핑 또는 Agile Modeling 관련 많은 컬럼이 있습니다.


    http://www.theserverside.com/talks/




    고급 아티클

    http://www.theserverside.com: 설명이 필요 없는 사이트 입니다. 자바 또는 엔터프라이즈 애플리케이션 개발자라면 반드시 매일 또는 매주 한번 이상은방문해봐야 하는 사이트 입니다. 초급에서 중급, 고급 기술로 넘어가기 위해서는 여기 올라오는 컬럼 또는 과거의 컬럼을 가능한많이 읽어 보고 이해해야 합니다.


    http://www.onjava.com : oreilly에서 운영하는 사이트로 자바 관련 기술 컬럼들이 많이 있습니다.


    Tips[도움이 될만한 유틸들]

    xml formatter

    http://homepage.ntlworld.com/wayne_grant/xmlformatter.html


    Tools관련

    posted by 좋은느낌/원철
    2008. 6. 30. 17:47 개발/Java
    출처 : http://msinterdev.org/blog/archive/200804?TSSESSIONmsinterdevorgblog=7701d2cc7e6ea2d431e379532dd3888d

    서블렛 + JDBC 연동시 코딩 고려사항 -제2탄-
    [JDBC Connection Pooling]

    최근수정일자 : 2001.01.19
    최근수정일자 : 2001.03.20(샘플예제추가)
    최근수정일자 : 2001.10.22(디버깅을 위한 로직추가)
    최근수정일자 : 2001.10.29(Oracle JDBC2.0 샘플추가)
    최근수정일자 : 2001.11.08(윤한성님 도움 OracleConnectionCacheImpl 소스수정)
    최근수정일자 : 2001.11.09(Trace/Debugging을 위한 장문의 사족을 담)

    5. JDBC Connection Pooling 을 왜 사용해야 하는가 ?

     Pooling 이란 용어는 일반적인 용어입니다. Socket Connection Pooling, Thread
     Pooling, Resource Pooling 등 "어떤 자원을 미리 Pool 에 준비해두고 요청시 Pool에
     있는 자원을 곧바로 꺼내어 제공하는 기능"인 거죠.

     JDBC Connection Pooling 은 JDBC를 이용하여 자바에서 DB연결을 할 때, 미리 Pool에
     물리적인 DB 연결을 일정개수 유지하여 두었다가 어플리케이션에서 요구할 때 곧바로
     제공해주는 기능을 일컫는 용어입니다. JDBC 연결시에, (DB 종류마다, 그리고 JDBC
     Driver의 타입에 따라 약간씩 다르긴 하지만 ) 대략 200-400 ms 가 소요 됩니다. 기껏
     0.2 초 0.4 초 밖에 안되는 데 무슨 문제냐 라고 반문할수도 있습니다.
     
     하지만, 위 시간은 하나의 연결을 시도할 때 그러하고, 100 - 200개를 동시에 연결을
     시도하면 얘기가 완전히 달라집니다.

     아래는 직접 JDBC 드라이버를 이용하여 연결할 때와 JDBC Connection Pooling 을 사용
     할 때의 성능 비교 결과입니다.


     ------------------------------------------------------------------
     테스트 환경
     LG-IBM 570E Notebook(CPU:???MHz , MEM:320MB)
     Windows NT 4.0 Service Pack 6
     IBM WebSphere 3.0.2.1 + e-Fixes
     IBM HTTP Server 1.3.6.2
     IBM UDB DB2 6.1

     아래에 첨부한 파일는 자료를 만들때 사용한 JSP소스입니다. (첨부파일참조)

     [HttpConn.java] 간단한 Stress Test 프로그램


     아래의 수치는 이 문서 이외에는 다른 용도로 사용하시면 안됩니다. 테스트를 저의
     개인 노트북에서 측정한 것이고, 또한 테스트 프로그램 역시 직접 만들어한 것인
     만큼, 공정성이나 수치에 대한 신뢰를 부여할 수는 없습니다.
     그러나 JDBC Connection Pooling 적용 여부에 따른 상대적인 차이를 설명하기에는
     충분할 것 같습니다.

     테스트 결과

     - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     매번 직접 JDBC Driver 연결하는 경우
     java HttpConn http://localhost/db_jdbc.jsp -c <동시유저수> -n <호출횟수> -s 0

     TOTAL( 1,10)  iteration=10 ,  average=249.40 (ms),   TPS=3.93
     TOTAL(50,10)  iteration=500 , average=9,149.84 (ms), TPS=4.83
     TOTAL(100,10)  iteration=1000 , average=17,550.76 (ms), TPS=5.27
     TOTAL(200,10)  iteration=2000 , average=38,479.03 (ms), TPS=4.89
     TOTAL(300,10)  iteration=3000 , average=56,601.89 (ms), TPS=5.01

     - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     DB Connection Pooing 을 사용하는 경우
     java HttpConn http://localhost/db_pool_cache.jsp -c <동시유저수> -n <호출횟수> -s 0

     TOTAL(1,10)  iteration=10 , average=39.00 (ms), TPS=23.26
     TOTAL(1,10)  iteration=10 , average=37.10 (ms), TPS=24.33
     TOTAL(50,10)  iteration=500 , average=767.36 (ms), TPS=45.27
     TOTAL(50,10)  iteration=500 , average=568.76 (ms), TPS=61.26
     TOTAL(50,10)  iteration=500 , average=586.51 (ms), TPS=59.79
     TOTAL(50,10)  iteration=500 , average=463.78 (ms), TPS=67.02
     TOTAL(100,10)  iteration=1000 , average=1,250.07 (ms), TPS=57.32
     TOTAL(100,10)  iteration=1000 , average=1,022.75 (ms), TPS=61.22
     TOTAL(200,10)  iteration=1462 , average=1,875.68 (ms), TPS=61.99
     TOTAL(300,10)  iteration=1824 , average=2,345.42 (ms), TPS=61.51

     NOTE: average:평균수행시간, TPS:초당 처리건수
     ------------------------------------------------------------------

     즉, JDBC Driver 를 이용하여 직접 DB연결을 하는 구조는 기껏 1초에 5개의 요청을
     처리할 수 있는 능력이 있는 반면, DB Connection Pooling 을 사용할 경우는 초당
     60여개의 요청을 처리할 수 있는 것으로 나타났습니다.
     12배의 성능향상을 가져온거죠. (이 수치는 H/W기종과 측정방법, 그리고 어플리케이션에
     따라 다르게 나오니 이 수치자체에 너무 큰 의미를 두진 마세요)


     주의: 흔히 "성능(Performance)"을 나타낼 때, 응답시간을 가지고 얘기하는 경향이
      있습니다. 그러나 응답시간이라는 것은 ActiveUser수가 증가하면 당연히 그에 따라
      느려지게 됩니다. 반면 "단위시간당 처리건수"인 TPS(Transaction Per Second) 혹은
      RPS(Request Per Second)는 ActiveUser를 지속적으로 끌어올려 임계점을 넘어서면,
      특정수치 이상을 올라가지 않습니다. 따라서 성능을 얘기할 땐 평균응답시간이 아니라
      "단위시간당 최대처리건수"를 이야기 하셔야 합니다.
      성능(Performance)의 정의(Definition)은 "단위시간당 최대처리건수"임을 주지하세요.
      성능에 관련한 이론은 아래의 문서를 통해, 함께 연구하시지요.
      [강좌]웹기반시스템하에서의 성능에 대한 이론적 고찰
      http://www.javaservice.net/~java/bbs/read.cgi?m=resource&b=consult&c=r_p&n=1008701211

     PS: IBM WebSphere V3 의 경우, Connection 을 가져올 때, JNDI를 사용하게 되는데
       이때 사용되는 DataSource 객체를 매번 initialContext.lookup() 을 통해 가져오게
       되면, 급격한 성능저하가 일어납니다.(NOTE: V4부터는 내부적으로 cache를 사용하여
       성능이 보다 향상되었습니다)
       DataSource를 매 요청시마다 lookup 할 경우 다음과 같은 결과를 가져왔습니다.

       java HttpConn http://localhost/db_pool.jsp -c <동시유저수> -n <호출횟수> -s 0

       TOTAL(1,10)  iteration=10 , average=80.00 (ms), TPS=11.61
       TOTAL(50,10)  iteration=500 , average=2,468.30 (ms), TPS=16.98
       TOTAL(50,10)  iteration=500 , average=2,010.43 (ms), TPS=18.18
       TOTAL(100,10)  iteration=1000 , average=4,377.24 (ms), TPS=18.16
       TOTAL(200,10)  iteration=1937 , average=8,991.89 (ms), TPS=18.12

       TPS 가 18 이니까 DataSource Cache 를 사용할 때 보다 1/3 성능밖에 나오지 않는 거죠.



    6. JDBC Connection Pooling 을 사용하여 코딩할 때 고려사항

     JDBC Connecting Pooling은 JDBC 1.0 스펙상에 언급되어 있지 않았습니다. 그러다보니
     BEA WebLogic, IBM WebSphere, Oracle OAS, Inprise Server, Sun iPlanet 등 어플리케
     이션 서버라고 불리는 제품들마다 그 구현방식이 달랐습니다.
     인터넷에서 돌아다니는 Hans Bergsten 이 만든 DBConnectionManager.java 도 그렇고,
     JDF 에 포함되어 있는 패키지도 그렇고 각자 독특한 방식으로 개발이 되어 있습니다.
     
     JDBC를 이용하여 DB연결하는 대표적인 코딩 예를 들면 다음과 같습니다.
     
     ------------------------------------------------------------------
     [BEA WebLogic Application Server]

        import java.sql.*;

        // Driver loading needed.
        static {
          try {
            Class.forName("weblogic.jdbc.pool.Driver").newInstance();          
          }
          catch (Exception e) {
            ...
          }
        }

        ......

        Connection conn = null;
        Statement stmt = null;
        try {
          conn = DriverManager.getConnection("jdbc:weblogic:pool:<pool_name>", null);
          stmt = conn.createStatement();
          ResultSet rs = stmt.executeQuery("select ....");
          while(rs.next()){
            .....
          }
          rs.close();
        }
        catch(Exception e){
          .....
        }
        finally {
          if ( stmt != null ) try{stmt.close();}catch(Exception e){}
          if ( conn != null ) try{conn.close();}catch(Exception e){}
        }


     ------------------------------------------------------------------
     IBM WebSphere Application Server 3.0.2.x / 3.5.x

        /* IBM WebSphere 3.0.2.x for JDK 1.1.8 */
        //import java.sql.*;
        //import javax.naming.*;
        //import com.ibm.ejs.dbm.jdbcext.*;
        //import com.ibm.db2.jdbc.app.stdext.javax.sql.*;

        /* IBM WebSphere 3.5.x for JDK 1.2.2 */
        import java.sql.*;
        import javax.sql.*;
        import javax.naming.*;

        // DataSource Cache 사용을 위한 ds 객체 static 초기화
        private static DataSource ds = null;
        static {
          try {
            java.util.Hashtable props = new java.util.Hashtable();
            props.put(Context.INITIAL_CONTEXT_FACTORY,
                      "com.ibm.ejs.ns.jndi.CNInitialContextFactory");
            Context ctx = null;
            try {
              ctx = new InitialContext(props);
              ds = (DataSource)ctx.lookup("jdbc/<data_source_name>");
            }
            finally {
              if ( ctx != null ) ctx.close();
            }
          }
          catch (Exception e) {
           ....
          }
        }

        .....   

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

     ------------------------------------------------------------------
     IBM WebSphere Application Server 2.0.x
     
        import java.sql.*;
        import com.ibm.servlet.connmgr.*;


        // ConnMgr Cache 사용을 위한 connMgr 객체 static 초기화

        private static IBMConnMgr connMgr = null;
        private static IBMConnSpec    spec = null;
        static {
          try {
            String poolName = "JdbcDb2";  // defined in WebSphere Admin Console
            spec = new IBMJdbcConnSpec(poolName, false,  
                  "com.ibm.db2.jdbc.app.DB2Driver",
                  "jdbc:db2:<db_name>",   // "jdbc:db2://ip_address:6789/<db_name>",
                  "userid","password");                    
            connMgr = IBMConnMgrUtil.getIBMConnMgr();
          }
          catch(Exception e){
            .....
          }
        }

        .....

        IBMJdbcConn cmConn = null; // "cm" maybe stands for Connection Manager.
        Statement stmt = null;
        try {
          cmConn = (IBMJdbcConn)connMgr.getIBMConnection(spec);    
          Connection conn = jdbcConn.getJdbcConnection();
          stmt = conn.createStatement();
          ResultSet rs = stmt.executeQuery("select ....");
          while(rs.next()){
            .....
          }
          rs.close();
        }
        catch(Exception e){
          .....
        }
        finally {
          if ( stmt != null ) try{stmt.close();}catch(Exception e){}
          if ( cmConn != null ) try{cmConn.releaseIBMConnection();}catch(Exception e){}
        }
        // NOTE: DO NOT "conn.close();" !!


     ------------------------------------------------------------------
     Oracle OSDK(Oracle Servlet Development Kit)

        import java.sql.*;
        import oracle.ec.ctx.*;


        .....
        
        oracle.ec.ctx.Trx trx = null;
        Connection conn = null;
        Statement stmt = null;
        try {
          oracle.ec.ctx.TrxCtx ctx = oracle.ec.ctx.TrxCtx.getTrxCtx();
          trx = ctx.getTrx();
          conn = trx.getConnection("<pool_name>");
          stmt = conn.createStatement();
          ResultSet rs = stmt.executeQuery("select ....");
          while(rs.next()){
            .....
          }
          rs.close();
        }
        catch(Exception e){
          .....
        }
        finally {
          if ( stmt != null ) try{stmt.close();}catch(Exception e){}
          if ( conn != null ) try{ trx.close(conn,"<pool_name>");}catch(Exception e){}
        }
        // NOTE: DO NOT "conn.close();" !!


     ------------------------------------------------------------------
     Hans Bergsten 의 DBConnectionManager.java

        import java.sql.*;

        .....
        
        db.DBConnectionManager connMgr = null;
        Connection conn = null;
        Statement stmt = null;
        try {
          connMgr = db.DBConnectionManager.getInstance();
          conn = connMgr.getConnection("<pool_name>");
          stmt = conn.createStatement();
          ResultSet rs = stmt.executeQuery("select ....");
          while(rs.next()){
            .....
          }
          rs.close();
        }
        catch(Exception e){
          .....
        }
        finally {
          if ( stmt != null ) try{stmt.close();}catch(Exception e){}
          if ( conn != null ) connMgr.freeConnection("<pool_name>", conn);
        }
        // NOTE: DO NOT "conn.close();" !!

     ------------------------------------------------------------------
     JDF 의 DB Connection Pool Framework

        import java.sql.*;
        import com.lgeds.jdf.*;
        import com.lgeds.jdf.db.*;
        import com.lgeds.jdf.db.pool.*;


        private static com.lgeds.jdf.db.pool.JdbcConnSpec spec = null;
        static {
          try {
            com.lgeds.jdf.Config conf = new com.lgeds.jdf.Configuration();
            spec =  new com.lgeds.jdf.db.pool.JdbcConnSpec(
                 conf.get("gov.mpb.pbf.db.emp.driver"),
                 conf.get("gov.mpb.pbf.db.emp.url"),
                 conf.get("gov.mpb.pbf.db.emp.user"),
                 conf.get("gov.mpb.pbf.db.emp.password")
              );
          }
          catch(Exception e){
            .....
          }
        }

        .....

        PoolConnection poolConn = null;
        Statement stmt = null;
        try {
          ConnMgr mgr = ConnMgrUtil.getConnMgr();
          poolConn = mgr.getPoolConnection(spec);
          Connection conn = poolConnection.getConnection();
          stmt = conn.createStatement();
          ResultSet rs = stmt.executeQuery("select ....");
          while(rs.next()){
            .....
          }
          rs.close();
        }
        catch(Exception e){
          .....
        }
        finally {
          if ( stmt != null ) try{stmt.close();}catch(Exception e){}
          if ( poolConn != null ) poolConn.release();
        }
        // NOTE: DO NOT "conn.close();" !!

     ------------------------------------------------------------------


     여기서 하고픈 얘기는 DB Connection Pool 을 구현하는 방식에 따라서 개발자의 소스도
     전부 제 각기 다른 API를 사용해야 한다는 것입니다.
     프로젝트를 이곳 저곳 뛰어 본 분은 아시겠지만, 매 프로젝트마나 어플리케이션 서버가
     다르고 지난 프로젝트에서 사용된 소스를 새 프로젝트에 그대로 적용하지 못하게 됩니다.
     JDBC 관련 API가 다르기 때문이죠.
     같은 제품일지라도 버전업이 되면서 API가 변해버리는 경우도 있습니다. 예를 들면,
     IBM WebSphere 버전 2.0.x에서 버전 3.0.x로의 전환할 때, DB 연결을 위한 API가
     변해버려 기존에 개발해둔 400 여개의 소스를 다 뜯어 고쳐야 하는 것과 같은 상황이
     벌어질 수도 있습니다.
     닷컴업체에서 특정 패키지 제품을 만들때도 마찬가지 입니다. 자사의 제품이 어떠한
     어플리케이션 서버에서 동작하도록 해야 하느냐에 따라 코딩할 API가 달라지니 소스를
     매번 변경해야만 하겠고, 특정 어플리케이션 서버에만 동작하게 하려니 마켓시장이
     좁아지게 됩니다.
     IBM WebSphere, BEA WebLogic 뿐만 아니라 Apache JServ 나 JRun, 혹은 Tomcat 에서도
     쉽게 포팅하길 원할 것입니다.

     이것을 해결하는 방법은 우리들 "SE(System Engineer)"만의 고유한 "Connection Adapter
     클래스"를 만들어서 사용하는 것입니다.

     예를 들어 개발자의 소스는 이제 항상 다음과 같은 유형으로 코딩되면 어떻겠습니까 ?


        -----------------------------------------------------------
        .....
        ConnectionResource resource = null;
        Statement stmt = null;
        try {
          resource = new ConnectionResource();
          Connection conn = resource.getConnection();

          stmt = conn.createStatement();
          ResultSet rs = stmt.executeQuery("select ....");
          while(rs.next()){
            .....
          }
          rs.close();
        }
        catch(Exception e){
          .....
        }
        finally {
          if ( stmt != null ) try{stmt.close();}catch(Exception e){}
          if ( resource != null ) resource.release();
        }
        // NOTE: DO NOT "conn.close();" !!
        -----------------------------------------------------------



     이 때 ConnectionResource 는 다음과 같은 형식으로 누군가 한분이 만들어 두면 되겠죠.

        -----------------------------------------------------------
        import java.sql.*;
        public class ConnectionResource
        {
          private java.sql.Connection conn = null;
          public ConnectionResource() throws Exception {
             ......
             conn =  ..... GET Connection by Connection Pooling API
          }
          public Connection getConnection() throws Exception {
             return conn;
          }
          public void release(){
             // release conn into "Connection Pool"
             .......
          }
        }
        -----------------------------------------------------------



      예를 들어 IBM WebSphere Version 3.0.2.x 의 경우를 든다면 다음과 같이 될 겁니다.

      -----------------------------------------------------------
      package org.jsn.connpool;
      /*
       * ConnectionResource V 1.0
       * JDBC Connection Pool Adapter for IBM WebSphere V 3.0.x/3.5.x
       * Author: WonYoung Lee, javaservice@hanmail.net, 011-898-7904
       * Last Modified : 2000.11.10
       * NOTICS: You can re-distribute or copy this source code freely,
       *         you can NOT remove the above subscriptions.
      */

      /* IBM WebSphere 3.0.2.x for JDK 1.1.8 */
      //import java.sql.*;
      //import javax.naming.*;
      //import com.ibm.ejs.dbm.jdbcext.*;
      //import com.ibm.db2.jdbc.app.stdext.javax.sql.*;

      /* IBM WebSphere 3.5.x for JDK 1.2.2 */
      import java.sql.*;
      import javax.sql.*;
      import javax.naming.*;

      public class ConnectionResource
      {
         private static final String userid = "userid";
         private static final String password = "password"
         private static final String datasource = "jdbc/<data_source_name>";
         private static DataSource ds = null;

         private java.sql.Connection conn = null;

         public ConnectionResource() throws Exception {
           synchronized ( ConnectionResource.class ) {
             if ( ds == null ) {
               java.util.Hashtable props = new java.util.Hashtable();
               props.put(Context.INITIAL_CONTEXT_FACTORY,
                  "com.ibm.ejs.ns.jndi.CNInitialContextFactory");
               Context ctx = null;
               try {
                 ctx = new InitialContext(props);
                 ds = (DataSource)ctx.lookup("jdbc/<data_source_name>");
               }
               finally {
                 if ( ctx != null ) ctx.close();
               }
             }
           }
           conn =  ds.getConnection( userid, password );
         }
         public Connection getConnection() throws Exception {
            if ( conn == null ) throw new Exception("Connection is NOT avaiable !!");
            return conn;
         }
         public void release(){
            // release conn into "Connection Pool"
            if ( conn != null ) try { conn.close(); }catch(Excepton e){}
            conn = null;
         }
       }
       -----------------------------------------------------------
              


     
      만약 Hans Bersten 의 DBConnectionManager.java 를 이용한 Connection Pool 이라면
      다음과 같이 만들어 주면 됩니다.

       -----------------------------------------------------------
       package org.jsn.connpool;
       import java.sql.*;
       public class ConnectionResource
       {
          private String poolname = "<pool_name>";
          private Connection conn = null;
          private db.DBConnectionManager connMgr = null;

          public ConnectionResource() throws Exception {
             connMgr = db.DBConnectionManager.getInstance();
             conn =  connMgr.getConnection(poolname);
          }
          public Connection getConnection() throws Exception {
             if ( conn == null ) throw new Exception("Connection is NOT avaiable !!");
             return conn;
          }
          public void release(){
             if ( conn != null ) {
               // Dirty Transaction을 rollback시키는 부분인데, 생략하셔도 됩니다.
               boolean autoCommit = true;
               try{ autoCommit = conn.getAutoCommit(); }catch(Exception e){}
               if ( autoCommit == false ) {
                 try { conn.rollback(); }catch(Exception e){}
                 try { conn.setAutoCommit(true); }catch(Exception e){}
               }

               connMgr.freeConnection(poolname, conn);
               conn = null;
             }
          }
       }
       -----------------------------------------------------------


      또, Resin 1.2.x 의 경우라면 다음과 같이 될 겁니다.
      -----------------------------------------------------------
      package org.jsn.connpool;
      /*
       * ConnectionResource V 1.0
       * JDBC Connection Pool Adapter for Resin 1.2.x
       * Author: WonYoung Lee, javaservice@hanmail.net, 011-898-7904
       * Last Modified : 2000.10.18
       * NOTICS: You can re-distribute or copy this source code freely,
       *         you can NOT remove the above subscriptions.
      */
      import java.sql.*;
      import javax.sql.*;
      import javax.naming.*;
      public class ConnectionResource
      {
         private static final String datasource = "jdbc/<data_source_name>";
         private static final String userid = "userid";
         private static final String password = "password"
         private static DataSource ds = null;

         private java.sql.Connection conn = null;

         public ConnectionResource() throws Exception {
            synchronized ( ConnectionResource.class ) {
              if ( ds == null ) {
                Context env = (Context) new InitialContext().lookup("java:comp/env");
                ds = (DataSource) env.lookup(datasource);
              }
            }    
            conn =  ds.getConnection( userid, password );
         }
         public Connection getConnection() throws Exception {
            if ( conn == null ) throw new Exception("Connection is NOT avaiable !!");
            return conn;
         }
         public void release(){
            // release conn into "Connection Pool"
            if ( conn != null ) try { conn.close(); }catch(Excepton e){}
            conn = null;
         }
       }
       -----------------------------------------------------------


      Oracle 8i(8.1.6이상)에서 Oracle의 JDBC 2.0 Driver 자체가 제공하는 Connection
      Pool을 이용한다면 다음과 같이 될 겁니다.
      -----------------------------------------------------------
      package org.jsn.connpool;
      /*
       * ConnectionResource V 1.0
       * JDBC Connection Pool Adapter for Oracle JDBC 2.0
       * Author: WonYoung Lee, javaservice@hanmail.net, 011-898-7904
       * Last Modified : 2001.10.29
       * NOTICS: You can re-distribute or copy this source code freely,
       *         but you can NOT remove the above subscriptions.
      */
      import java.sql.*;
      import javax.sql.*;
      import oracle.jdbc.driver.*;
      import oracle.jdbc.pool.*;

      public class ConnectionResource
      {
         private static final String dbUrl = "jdbc:oracle:thin@192.168.0.1:1521:ORCL";
         private static final String userid = "userid";
         private static final String password = "password"
         private static OracleConnectionCacheImpl oraclePool = null;
         private static boolean initialized = false;

         private java.sql.Connection conn = null;

         public ConnectionResource() throws Exception {
            synchronized ( ConnectionResource.class ) {
              if ( initialized == false ) {
                DriverManager.registerDriver (new oracle.jdbc.driver.OracleDriver());
                oraclePool = new OracleConnectionCacheImpl();
                oraclePool.setURL(dbUrl);
                oraclePool.setUser(userid);
                oraclePool.setPassword(password);
                oraclePool.setMinLimit(10);
                oraclePool.setMaxLimit(50);
                //oraclePool.setCacheScheme(OracleConnectionCacheImpl.FIXED_WAIT_SCHEME);
                // FIXED_WAIT_SCHEME(default), DYNAMIC_SCHEME, FIXED_RETURN_NULL_SCHEME
                //oraclePool.setStmtCacheSize(0); //default is 0

                initialized = true;
              }
            }    
            conn = oraclePool.getConnection();
         }
         public Connection getConnection() throws Exception {
            if ( conn == null ) throw new Exception("Connection is NOT avaiable !!");
            return conn;
         }
         public void release(){
            // release conn into "Connection Pool"
            if ( conn != null ) try { conn.close(); }catch(Exception e){}
            conn = null;
         }
       }
       -----------------------------------------------------------

      또한 설령 Connection Pool 기능을 지금을 사용치 않더라도 향후에 적용할 계획이
      있다면, 우선은 다음과 같은 Connection Adapter 클래스를 미리 만들어 두고 이를
      사용하는 것이 효과적입니다. 향후에 이 클래스만 고쳐주면 되니까요.
      Oracle Thin Driver를 사용하는 경우입니다.

      -----------------------------------------------------------
      import java.sql.*;
      public class ConnectionResource
      {
         private static final String userid = "userid";
         private static final String password = "password"
         private static final String driver = "oracle.jdbc.driver.OracleDriver"
         private static final String url = "jdbc:oracle:thin@192.168.0.1:1521:ORCL";
         private static boolean initialized = false;

         private java.sql.Connection conn = null;

         public ConnectionResource() throws Exception {
            synchronized ( ConnectionResource.class ) {
              if ( initialized == false ) {
                  Class.forName(driver);
                  initialized = true;
              }
            }
            conn = DriverManager.getConnection( url, userid, password );
         }
         public Connection getConnection() throws Exception {
            if ( conn == null ) throw new Exception("Connection is NOT avaiable !!");
            return conn;
         }
         public void release(){
            if ( conn != null ) try { conn.close(); }catch(Excepton e){}
            conn = null;
         }
       }
       -----------------------------------------------------------

     
     프로그램의 유형이나, 클래스 이름이야 뭐든 상관없습니다. 위처럼 우리들만의 고유한
     "Connection Adapter 클래스"를 만들어서 사용한다는 것이 중요하고, 만약 어플리케이션
     서버가 변경된다거나, DB Connection Pooling 방식이 달라지면 해당 ConnectionResource
     클래스 내용만 살짝 고쳐주면 개발자의 소스는 전혀 고치지 않아도 될 것입니다.

     NOTE: ConnectionResource 클래스를 만들때 주의할 것은 절대 Exception 을 클래스 내부에서
       가로채어 무시하게 하지 말라는 것입니다. 그냥 그대로 throw 가 일어나게 구현하세요.
       이렇게 하셔야만 개발자의 소스에서 "Exception 처리"를 할 수 있게 됩니다.

     NOTE2: ConnectionResource 클래스를 만들때 반드시 package 를 선언하도록 하세요.
       IBM WebSphere 3.0.x 의 경우 JSP에서 "서블렛클래스패스"에 걸려 있는 클래스를
       참조할 때, 그 클래스가 default package 즉 package 가 없는 클래스일 경우 참조하지
       못하는 버그가 있습니다. 통상 "Bean"이라 불리는 클래스 역시 조건에 따라 인식하지
       않을 수도 있습니다.
       클래스 다지인 및 설계 상으로 보더라도 package 를 선언하는 것이 바람직합니다.

     NOTE3: 위에서  "userid", "password" 등과 같이 명시적으로 프로그램에 박아 넣지
       않고, 파일로 관리하기를 원한다면, 그렇게 하셔도 됩니다.
       JDF의 Configuration Framework 을 참조하세요.


     PS: 혹자는 왜 ConnectionResource 의 release() 메소드가 필요하냐고 반문할 수도 있습
       니다. 예를 들어 개발자의 소스가 다음처럼 되도록 해도 되지 않느냐라는 거죠.

        -----------------------------------------------------------
        .....
        Connection conn = null;
        Statement stmt = null;
        try {
          conn = ConnectionResource.getConnection(); // <---- !!!

          stmt = conn.createStatement();
          .....
        }
        catch(Exception e){
          .....
        }
        finally {
          if ( stmt != null ) try{stmt.close();}catch(Exception e){}
          if ( conn != null ) try{conn.close();}catch(Exception e){} // <---- !!!
        }
        -----------------------------------------------------------

      이렇게 하셔도 큰 무리는 없습니다. 그러나, JDBC 2.0 을 지원하는 제품에서만
      conn.close() 를 통해 해당 Connection 을 실제 close() 시키는 것이 아니라 DB Pool에
      반환하게 됩니다. BEA WebLogic 이나 IBM WebSphere 3.0.2.x, 3.5.x 등이 그렇습니다.
      그러나, 자체제작된 대부분의 DB Connection Pool 기능은 Connection 을 DB Pool에
      반환하는 고유한 API를 가지고 있습니다. WebSphere Version 2.0.x 에서는
      cmConn.releaseIBMConnection(), Oracle OSDK 에서는 trx.close(conn, "<pool_name">);
      Hans Bersten 의 DBConnectionManager 의 경우는
      connMgr.freeConnection(poolname, conn); 등등 서로 다릅니다. 이러한 제품들까지
      모두 지원하려면 "release()" 라는 우리들(!)만의 예약된 메소드가 꼭 필요하게 됩니다.

      물론, java.sql.Connection Interface를 implements 한 별도의 MyConnection 을 만들어
      두고, 실제 java.sql.Connection 을 얻은 후 MyConnection의 생성자에 그 reference를
      넣어준 후, 이를 return 시에 넘기도록 하게 할 수 있습니다. 이때, MyConnection의
      close() 함수를 약간 개조하여 DB Connection Pool로 돌아가게 할 수 있으니까요.


     PS: 하나 이상의 DB 를 필요로 한다면, 다음과 같은 생성자를 추가로 만들어서 구분케
      할 수도 있습니다.

      -----------------------------------------------------------
      ....
      private String poolname = "default_pool_name";
      private String userid = "scott";
      private String password = "tiger";
      public ConnectionResource() throws Exception {
        initialize();
      }
      public ConnectionResource(String poolname) throws Exception {
        this.poolname = poolname;
        initialize();
      }
      public ConnectionResource(String poolname,String userid, String passwrod)
      throws Exception
      {
        this.poolname = poolname;
        this.userid = userid;
        this.password = password;
        initialize();
      }
      private void initialize() throws Exception {
        ....
      }
      ...
      -----------------------------------------------------------



    -------------------------------------------------------------------------------
    2001.03.20 추가
    2001.10.22 수정
    2001.11.09 수정(아래 설명을 추가함)

    실 운영 사이트의 장애진단 및 튜닝을 다녀보면, 장애의 원인이 DataBase 연결개수가
    지속적으로 증가하고, Connection Pool 에서 더이상 가용한 연결이 남아 있지 않아
    발생하는 문제가 의외로 많습니다. 이 상황의 십중팔구는 개발자의 코드에서 Pool로
    부터 가져온 DB연결을 사용하고 난 후, 이를 여하한의 Exception 상황에서도 다시
    Pool로 돌려보내야 한다는 Rule 를 지키지 않아서 발생한 문제가 태반입니다.
    이름만 말하면 누구나 알법한 큼직한 금융/뱅킹사이트의 프로그램소스에서도 마찬가지
    입니다.
    문제는 분명히 어떤 특정 응용프로그램에서 DB Connection 을 제대로 반환하지 않은
    것은 분명한데, 그 "어떤 특정 응용프로그램"이 꼭집어 뭐냐 라는 것을 찾아내기란
    정말 쉽지 않습니다. 정말 쉽지 않아요. 1초당 수십개씩의 Request 가 다양하게 들어
    오는 상황에서, "netstat -n"으로 보이는 TCP/IP 레벨에서의 DB연결수는 분명히 증가
    하고 있는데, 그 수십개 중 어떤 것이 문제를 야기하느냐를 도저히 못찾겠다는 것이지요.
    사용자가 아무도 없는 새벽에 하나씩 컨텐츠를 꼭꼭 눌러 본들, 그 문제의 상황은
    대부분 정상적인 로직 flow 에서는 나타나지 않고, 어떤 특별한 조건, 혹은 어떤
    Exception 이 발생할 때만 나타날 수 있기 때문에, 이런 방법으로는 손발과 눈만 아프게
    되곤 합니다.

    따라서, 애초 부터, 이러한 상황을 고려하여, 만약, 개발자의 코드에서 실수로 DB연결을
    제대로 Pool 에 반환하지 않았을 때, 그 개발자 프로그램 소스의 클래스 이름과 함께
    Warnning 성 메세지를 남겨 놓으면, 되지 않겠느냐는 겁니다.

    Java 에서는 java.lang.Object 의 finalize() 라는 기막힌 메소드가 있습니다. 해당
    Object instance의 reference 가 더이상 그 어떤 Thread 에도 남아 있지 않을 경우
    JVM의 GC가 일어날 때 그 Object 의 finalize() 메소드를 꼭 한번 불러주니까요.

    계속 반복되는 얘기인데, 아래 글에서 또한번 관련 의미를 찾을 수 있을 것입니다.
    Re: DB Connection Pool: Orphan and Idle Timeout
    http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=servlet&c=r_p&n=1005294960

    아래는 이것을 응용하여, Hans Bergsten 의 DBConnectionManager 를 사용하는
    ConnectionAdapter 클래스를 만들어 본 샘플입니다. Trace/Debuging 을 위한 몇가지
    기능이 첨가되어 있으며, 다른 ConnectionPool을 위한 Connection Adapter 를 만들때도
    약간만 수정/응용하여 사용하실 수 있을 것입니다.


    /**
     * Author : Lee WonYoung, javaservice@hanmail.net
     * Date   : 2001.03.20, 2001.10.22
     */

    import java.util.*;
    import java.sql.*;
    import java.io.*;
    import java.text.SimpleDateFormat;

    public class ConnectionResource
    {
        private boolean DEBUG_MODE = true;
        private String DEFAULT_DATASOURCE = "idb";  // default db name
        private long GET_CONNECTION_TIMEOUT = 2000; // wait only 2 seconds if no
                                                    // avaiable connection in the pool
        private long WARNNING_MAX_ELAPSED_TIME = 3000;

        private SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd/HHmmss");
        private db.DBConnectionManager manager = null;
        private Connection conn = null;
        private String datasource = DEFAULT_DATASOURCE;
        private String caller = "unknown";
        private long starttime = 0;

        private static int used_conn_count = 0;
        private static Object lock = new Object();

        // detault constructor
        public ConnectionResource()
        {
            manager = db.DBConnectionManager.getInstance();
        }

        // For debugging, get the caller object reference
        public ConnectionResource(Object caller_obj)
        {
            this();
            if ( caller_obj != null )
                caller = caller_obj.getClass().getName();
        }

        public Connection getConnection()  throws Exception
        {
            return getConnection(DEFAULT_DATASOURCE);
        }

        public Connection getConnection(String datasource)  throws Exception
        {
            if ( conn != null ) throw new Exception
                ("You must release the connection first to get connection again !!");

            this.datasource = datasource;
            // CONNECTION_TIMEOUT is very important factor for performance tuning
            conn = manager.getConnection(datasource, GET_CONNECTION_TIMEOUT);
            synchronized( lock ) { ++used_conn_count; }
            starttime = System.currentTimeMillis();
            return conn;
        }

        // you don't have to get "connection reference" as parameter,
        // because we already have the referece as the privae member variable.
        public synchronized void release() throws Exception {  
            if ( conn == null ) return;
            // The following is needed for some DB connection pool.
            boolean mode = true;
            try{
                mode = conn.getAutoCommit();
            }catch(Exception e){}
            if ( mode == false ) {
                try{conn.rollback();}catch(Exception e){}
                try{conn.setAutoCommit(true);}catch(Exception e){}
            }
            manager.freeConnection(datasource, conn);
            conn = null;
            int count = 0;
            synchronized( lock ) { count = --used_conn_count; }
            if ( DEBUG_MODE ) {
                long endtime = System.currentTimeMillis();
                if ( (endtime-starttime) > WARNNING_MAX_ELAPSED_TIME ) {
                    System.err.println(df.format(new java.util.Date()) +
                        ":POOL:WARNNING:" + count +
                        ":(" + (endtime-starttime) + "):" +
                        "\t" + caller
                    );
                }
            }
        }
        // finalize() method will be called when JVM's GC time.
        public void finalize(){
            // if "conn" is not null, this means developer did not release the "conn".
            if ( conn != null ) {
                System.err.println(df.format(new java.util.Date()) +
                    ":POOL:ERROR connection was not released:" +
                    used_conn_count + ":\t" + caller
                );
                release();
            }
        }
    }

    -------------------------------------------------------------------------------

    위의 Connection Adaptor 클래스를 Servlet 이나 JSP에서 사용할 때는 다음과 같이
    사용할 수 있습니다.


    사용법: DB Transaction 처리가 필요치 않을 때...


    ConnectionResource resource = null;
    Connection conn = null;
    Statement stmt = null;
    try{
        // For debugging and tracing, I recommand to send caller's reference to
        // the adapter's constructor.
        resource = new ConnectionResource(this); // <--- !!!
        //resource = new ConnectionResource();

        conn = resource.getConnection();
        // or you can use another database name
        //conn = resource.getConnection("other_db");

        stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("select ...");
        while(rs.next()){
            .....
        }
        rs.close();

    }
    //catch(Exception e){
    //    // error handling if you want
    //    ....
    //}
    finally{
        if ( stmt != null ) try{stmt.close();}catch(Exception e){}
        if ( conn != null ) resource.release(); // <--- !!!
    }

    -------------------------------------------------------------------------------
    사용법: Transaction 처리가 필요할 때.

    ConnectionResource resource = null;
    Connection conn = null;
    Statement stmt = null;
    try{
        // For debugging and tracing, I recommand to send caller's reference to
        // the adapter's constructor.
        resource = new ConnectionResource(this);
        //resource = new ConnectionResource();

        conn = resource.getConnection();
        // or you can use another database name
        //conn = resource.getConnection("other_db");

        conn.setAutoCommit(false); // <--- !!!

        stmt = conn.createStatement();
        stmt.executeUpdate("update ...");
        stmt.executeUpdate("insert ...");
        stmt.executeUpdate("update ...");
        stmt.executeUpdate("insert ...");
        stmt.executeUpdate("update ...");
        
        int affected = stmt.executeUpdate("update....");
        // depends on your business logic,
        if ( affected == 0 )
            throw new Exception("NoAffectedException");
        else if ( affected > 1 )
            throw new Exception("TooManyAffectedException:" + affected);

        conn.commit(); //<<-- commit() must locate at the last position in the "try{}"
    }
    catch(Exception e){
        // if error, you MUST rollback
        if ( conn != null ) try{conn.rollback();}catch(Exception e){}

        // another error handling if you want
        ....
        throw e; // <--- throw this exception if you want
    }
    finally{
        if ( stmt != null ) try{stmt.close();}catch(Exception e){}
        if ( conn != null ) resource.release(); // <-- NOTE: autocommit mode will be
                                                //           initialized
    }
    -----------------------------------------------------------------

    PS: 더 깊이있는 내용을 원하시면 다음 문서들을 참조 하세요...
        JDF 제4탄 - DB Connection Pool
        http://www.javaservice.net/~java/bbs/read.cgi?m=jdf&b=framework&c=r_p&n=945156790

        JDF 제5탄 - DB Connection Resource Framework
        http://www.javaservice.net/~java/bbs/read.cgi?m=jdf&b=framework&c=r_p&n=945335633

        JDF 제6탄 - Transactional Connection Resource Framework
        http://www.javaservice.net/~java/bbs/read.cgi?m=jdf&b=framework&c=r_p&n=945490586


    PS: IBM WebSphere의 JDBC Connection Pool 에 관심이 있다면 다음 문서도 꼭 참조
     하세요...
        Websphere V3 Connection Pool 사용법
        http://www.javaservice.net/~java/bbs/read.cgi?m=appserver&b=was&c=r_p&n=970209527
        WebSphere V3 DB Connection Recovery 기능 고찰
        http://www.javaservice.net/~java/bbs/read.cgi?m=appserver&b=was&c=r_p&n=967473008

    ------------------------
    NOTE: 2004.04.07 추가
    그러나, 2004년 중반을 넘어서는 이 시점에서는, ConnectionResource와 같은 Wapper의
    중요성이 별로 높이 평가되지 않는데, 그 이유는 Tomcat 5.0을 비롯한 대부분의 WAS(Web
    Application Server)가 자체의 Connection Poolinig기능을 제공하며, 그 사용법은 다음과
    같이 JDBC 2.0에 준하여 모두 동일한 형태를 띠고 있기 때문입니다.

    InitialContext ctx = new InitialContext();
    DataSource ds = (DataSoruce)ctx.lookup("java:comp/env/jdbc/ds");
    Connection conn = ds.getConnection();
    ...
    conn.close();

    결국 ConnectionResource의 release()류의 메소드는 Vendor별로, JDBC Connection Pool의
    종류가 난무하던 3-4년 전에는 어플리케이션코드의 Vendor종속성을 탈피하기 위해 의미를
    가졌으나, 지금은 굳이 필요가 없다고 보여 집니다.

    단지, caller와 callee의 정보, 즉, 응답시간을 추적한다거나, close() 하지 않은
    어플리케이션을 추적하는 의미에서의 가치만 남을 수 있습니다.  (그러나, 이것도,
    IBM WebSphere v5의 경우, 개발자가 conn.close() 조차 하지 않을 지라도 자동을 해당
    Thread의 request ending시점에 "자동반환"이 일어나게 됩니다.)

    ---------------------
    2005.01.21 추가
    JDBC Connection/Statement/ResultSet을 close하지 않은 소스의 위치를 잡아내는 것은
    실제 시스템 운영 중에 찾기란 정말 모래사장에서 바늘찾기 같은 것이었습니다.
    그러나, 제니퍼(Jennifer2.0)과 같은 APM을 제품을 적용하시면, 운영 중에, 어느 소스의
    어느 위치에서 제대로 반환시키지 않았는지를 정확하게 찾아줍니다.
    뿐만 아니라, 모든 SQL의 수행통계 및 현재 수행하고 있는 어플리케이션이 어떤 SQL을
    수행중인지 실시간으로 확인되니, 성능저하를 보이는 SQL을 튜닝하는 것 등, 많은 부분들이
    명확해져 가고 있습니다. 이젠 더이상 위 글과 같은 문서가 필요없는 세상을 기대해 봅니다.

    -------------------------------------------------------  
      본 문서는 자유롭게 배포/복사 할 수 있으나 반드시
      이 문서의 저자에 대한 언급을 삭제하시면 안됩니다
    ================================================
      자바서비스넷 이원영
      E-mail: javaservice@hanmail.net
      PCS:011-898-7904
    ================================================
    posted by 좋은느낌/원철
    2008. 6. 30. 17:46 개발/Java
    출처 : http://msinterdev.org/blog/archive/200804?TSSESSIONmsinterdevorgblog=7701d2cc7e6ea2d431e379532dd3888d

    자바서비스.넷  에 있는 예전 글 입니다.
    요즘엔 iBatis 니 Spring 이니 하는 것 들을 이용하기 때문에 별도로 커넥션을 관리 할 일이 없어지긴 하고 있지만, 기본 개념을 모른체로 하다보니 잘못 된 접근을 하는 경우가 발생하여 다시 한번 점검해 봅니다.


    최초작성일자: 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
    ================================================
    posted by 좋은느낌/원철