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

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

Notice

    2009. 4. 16. 11:30 개발/Java
    출처 : http://blog.naver.com/bbellsek/150002233551 

    자바 컴파일과 실행 에 사용되어지는 javac, java 명령어의 옵션 을 다음과 같이 정리하였습니다.


    javac - 자바컴파일러로써, 자바코드를 작성한 소스파일(.java)을 자바 가상머신이 인식할수 있는 바이트 코드(.class)
    타입으로 변환시켜주는 명령어 입니다.

    사용법: javac <options> <souce files>
    예를들어, Hello.java, Greeting.java 두개의 파일이 존재한다면,
    javac Hello.java Greeting.java
    javac *.java (*을 사용해서, 모든 확장자가 .java인 파일을 컴파일할수 있다.)

     

    1) 옵션:


    a) -classpath:

     -classpath(cp) path(파일 절대 경로):
     컴파일러가 컴파일 하기 위해서 필요로 하는 참조할 클래스 파일들을 찾기 위해서 컴파일시 파일 경로를 지정해주는
    옵션. 예를 들어,  Hello.java파일이 C:Java 디렉터리에 존재하고, 필요한 클래스 파일들이 C:JavaEngclasses에 위치한다면,
    javac -classpath C:JavaEngclasses C:JavaHello.java 로 해주면 된다. 만약 참조할 클래스 파일들이 C:JavaEngclasses외의 
    다른 디렉터리에도 존재한다면, C:JavaKorclasses 일경우, 
    javac -classpath C:JavaEngclasses;C;JavaKorclasses C:JavaHello.java
    그리고, 현재 디렉터리역시 포함하고 싶다면,
    javac -classpath .;C:JavaEngclasses;C;JavaKorclasses C:JavaHello.java
    기본적으로, dos에서는 .는 현재 디렉터리를 의미하고, ..는 현재 디렉터리의 상위디렉터리를 의미한다. 
    또한 classpath 대신 단축어인 cp를 사용해도 된다.
    javac -cp C:JavaEngclasses C:JavaHello.java 

     

    b) -d: 
     -d directory
     클래스 파일을 생성할 루트 디렉터리를 지정합니다.
    기본적으로 컴파일러는 -d옵션을 주지 않으면, 소스파일이 위치한 디렉터리에 클래스 파일을 생성시킵니다.
    예를 들어,  Hello.java파일이 C:Java 디렉터리에 존재하고 클래스 파일의 루트디렉터리를 C:JavaClassfiles라고 하면, 
    javac -d C:JavaClassfiles C:JavaHello.java 입니다.

    만약 -d 옵션을 사용하려고 하는데, 루트디렉터리(위예에서는 C:JavaClassfiles) 가 존재 하지 않는다면, 
    "The system cannot find the path specified"라는 에러 메시지를 보게 됩니다. 
    현재 작업 디렉터리가 C:JavaClassfiles 에 위치하면, 
    javac -d .Classfiles Hello.java 와 같이 상대 디렉터리로 표현할수 있습니다.

     

    c) -encoding:
    -encoding encoding name
    소스 파일에 사용된 문자열 인코딩을 설정합니다.
    만약 위옵션이 설정되어 있지 않으면, 플래폼의 기본적인 컨버터가 사용되어 집니다.

     

    d) -g:
    모든 디버깅 정보를 생성시킵니다.
    만약 위옵션이 설정되어 있지 않으면, 기본적으로, 라인넘버만 생성시킵니다.
    -g:none: 디버깅 정보를 전혀 생성 시키지 않습니다.
    -g:{lines, vars, source}:
    위처럼 명시적으로, 몇몇 디버깅 정보를 생성시킬수 있습니다.
    lines은 라인정보, vars는 지역변수, sounce는 소스 파일 정보를 나타냅니다.

     

    e) -nowarn:

    경고 메시지 (warning message)를 생성시키지 않습니다.

     

    f) -verbose:

    컴파일러와 링커가 현재 어느 소스파일이 컴파일되고 있고, 어느 파일이 링크되고 있는지 
    그정보를 출력한다.

     

    h) -deprecation:

    소스 코드내에서, 사용되어진 deprecated API의 위치 를 출력 합니다.

    ex)
    C:Java> javac World.java
    Note: World.java uses a deprecated API. Recompile with "-deprecation" for details
    .
    1 warning
    C:Java> javac -deprecation World.java
    World.java:52: Note: The method java.awt.Dimension size() in class java.awt.Compon
    ent has been deprecated.
    Dimension d = size();

    Note: World.java uses a deprecated API. Please consult the doc-umentation for a be
    tter alternative.

     

    i) -sourcepath:

    -sourcepath 소스패스

    소스파일의 위치를 지정합니다.

     

    j) -target:

    -target 자바버젼

    지정된 자바버젼의 VM에서 작동 되어지도록 클래스파일을 생성 시킵니다.

    1.1
    jvm 1.1 버젼에서 호환되어질수 있는 클래스 파일생성
    1.2
    jvm 1.2 버젼에서 호환되어질수 있는 클래스 파일생성
    1.3
    jvm 1.3 버젼에서 호환되어질수 있는 클래스 파일 생성

    ex)

    javac -target 1.2 Helloworld.java 

     

    k) -bootclasspath 패스:

    특정한 bootstrap또는 확장 클래스를 지정할수 있다.
    기본적으로, 자바컴파일러는 javac(컴파일러명령)이 설치된 플래폼의 bootstrap과 확장클래스들을 통해서, 컴파일작업을 수행하지만,
    bootclasspath 옵션을 사용하면, cross-compiling이라고 해서, 다른 자바플래폼의 bootstrap과 확장클래스들을 통해서, 컴파일 할수 있는 기능을 지원한다.
    예를들어,
    javac -target 1.1 -bootclasspath jdk1.1.7/lib/classes.zip -extdirs "" OldCode.java
    컴파일러에게 현재 자신의 bootstrap을 사용하지 말고, jdk1.1.7/lib/classes.zip bootstrap클래스들을 사용해서 컴파일 하라고
    명령하는것이다.
    참고로, 모바일자바에서, 모바일폰에 설정된, jvm에 맞도록, 소스코드를 컴파일하기 위해서, 주로 사용되어지는 옵션이다.

     

     

    l) -extdirs 디렉터리:
    특정한, 확장 디렉토리를 지정한다.cross-compiling시 주로, 사용되어지는 옵션이면, 각디렉터리들은 콜론(:)에 의해서, 분리되어진다.
    컴파일시, 기술한 디렉터리의 클래스 파일을 참조한다.

    posted by 좋은느낌/원철
    2009. 2. 24. 17:00 개발/JSP/Servlet

    출처 : http://www.potatosoft.com/tt/218

    Java 를 기반으로 구현된 모든 코드에서 국제화 등을 이유로 리소스 파일 작성을 할때에, 문자열 등의 리소스 파일은 ascii 값의 ISO-8859-1 인코딩으로 작성해 주어야 합니다.

    프로퍼티 파일들을 UTF-8 로 읽어주도록 변경되면 좋겠지만 어쨌든 현재는 그렇습니다.

    이 때문에 jdk 에는 native2ascii 라는 실행파일이 들어 있는데( $JAVA_HOME/bin 에 들어있음 ) 이놈을 이용해서 프로퍼티 파일을 파라메터로 건내주거나 파일 실행 후 문자열을 입력해보면 Latin-1 인코딩의 ascii 값을 알 수 있습니다.

    ANT에서도 native2ascii task 를 지원하기 때문에 일반적으로 컴파일 및 빌드 과정과는 별개로 native2ascii 를 이용해서 리소스 파일들을 바꾸어 주는 작업을 추가하게 됩니다.

    Quick and Dirty Hack for UTF-8 Support in ResourceBundle 라는 포스팅을 보면 PropertyResourceBundle 의 wrapper class 구현이 있는데 이런 걸 이용하는 것이 편하긴 합니다. 다만 해당 포스팅의 아래쪽 comment 를 보면 3가지 정도의 bug case 에 대한 지적이 있는데 특히 Brian 이 지적한 내용은 반드시 적용해서 사용하셔야겠습니다.

    Very cool however I found a bug in your impl. when asking for a resource bundle with a parent. This is how I fixed it. Notice the call to getString which makes sure to recursively go over the parent bundles. Also checking for null.

    protected Object handleGetObject(String key) {
        String value = (String)bundle.getString(key);
        if (value==null) return null;
        try {
            return new String (value.getBytes("ISO-8859-1"),"UTF-8") ;
        } catch (UnsupportedEncodingException e) {
        // Shouldn't fail - but should we still add logging message?
        return null;
        }
    }

    And another small thing: not all ISO-8859-1 chars are a subset of UTF-8. The (C) sign seems to clash. If I put a \u00a9 in a UTF-8 file, it fails. But if I put the actual sign, it works. For some reason it is encoded as two bytes even though it is below 192 decimal.

    Not a big deal though. Still very cool solution.

    첫번째 지적한 경우에 해당하는 문자는 저작권 표시와 유로화 표시, 그리고 무한대 표시 등이 있는 것 같네요.


    이런 프로그래밍적인 것 말고 이클립스 플러그인 형태의 '편집기'를 원하는 분이라면 Properties Editor 라는 것을 추천합니다.

    이클립스의 Help -> software updates -> add site 를 선택하고 http://propedit.sourceforge.jp/eclipse/updates/ 를 추가해서 업데이트를 하면 됩니다.

    설치를 하고 나서 파일을 생성할때 확장자를 .properties 로 설정하면 해당 파일을 편집할때 자동으로 이 Properties Editor 를 이용해서 편집이 되고 properties 파일의 아이콘도 에디터 설치 후에는 아래와 같이 녹색 P 모양으로 바뀌게 됩니다.

    image

    물론 파일을 선택하고 팝업메뉴를 띄워서 Open with... 로 파일을 열 수도 있습니다.

    package.properties 파일을 열어보면 이렇게 이쁘게 한글로 표시가 되어 있습니다...만...

    image

    Open with 를 선택하고 일반 텍스트 편집기로 열어보면

    image

    native2ascii 를 돌린 것처럼 이쁘게 변환되어 있습니다. 전체 프로젝트의 인코딩이 UTF-8로 지정되어 있어도 해당 .properties 파일은 ISO-8859-1 로 맞춰지기 때문에 별도로 파일 인코딩을 바꾸어주지 않아도 되니 편리하군요.

    posted by 좋은느낌/원철
    2008. 9. 2. 03:13 개발/Java
    [ 날짜 연산법 ]


    가. 이해 및 유틸

    - 시스템 시간에 대한 이해
    - 날짜 계산 종합 유틸리티

    나. 응용팁

    시스템의 밀리초 구하기.(국제표준시각(UTC, GMT) 1970/1/1/0/0/0 으로부터 경과한 시각)
    ------------------------------------------------------------------
    // 밀리초 단위(*1000은 1초), 음수이면 이전 시각
    long time = System.currentTimeMillis ( );
    System.out.println ( time.toString ( ) );
    ------------------------------------------------------------------

    현재 시각을 가져오기.
    ------------------------------------------------------------------
    Date today = new Date ();
    System.out.println ( today );

    결과 : Sat Jul 12 16:03:00 GMT+01:00 2000
    ------------------------------------------------------------------

    경과시간(초) 구하기
    ------------------------------------------------------------------
    long time1 = System.currentTimeMillis ();
    long time2 = System.currentTimeMillis ();
    system.out.println ( ( time2 - time1 ) / 1000.0 );
    ------------------------------------------------------------------

    Date를 Calendar로 맵핑시키기
    ------------------------------------------------------------------
    Date d = new Date ( );
    Calendar c = Calendar.getInstance ( );
    c.setTime ( d );
    ------------------------------------------------------------------

    날짜(년/월/일/시/분/초) 구하기
    ------------------------------------------------------------------
    import java.util.*;
    import java.text.*;

    SimpleDateFormat formatter = new SimpleDateFormat ( "yyyy.MM.dd HH:mm:ss", Locale.KOREA );
    Date currentTime = new Date ( );
    String dTime = formatter.format ( currentTime );
    System.out.println ( dTime );
    ------------------------------------------------------------------

    날짜(년/월/일/시/분/초) 구하기2
    ------------------------------------------------------------------
    GregorianCalendar today = new GregorianCalendar ( );

    int year = today.get ( today.YEAR );
    int month = today.get ( today.MONTH ) + 1;
    int yoil = today.get ( today.DAY_OF_MONTH );

    GregorianCalendar gc = new GregorianCalendar ( );

    System.out.println ( gc.get ( Calendar.YEAR ) );
    System.out.println ( String.valueOf ( gc.get ( Calendar.MONTH ) + 1 ) );
    System.out.println ( gc.get ( Calendar.DATE ) );
    System.out.println ( gc.get ( DAY_OF_MONTH ) );
    ------------------------------------------------------------------

    날짜(년/월/일/시/분/초) 구하기3
    ------------------------------------------------------------------
    DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.KOREA);
    Calendar cal = Calendar.getInstance(Locale.KOREA);
    nal = df.format(cal.getTime());
    ------------------------------------------------------------------

    - 표준시간대를 지정하고 날짜를 가져오기.
    ------------------------------------------------------------------
    TimeZone jst = TimeZone.getTimeZone ("JST");
    Calendar cal = Calendar.getInstance ( jst ); // 주어진 시간대에 맞게 현재 시각으로 초기화된 GregorianCalender 객체를 반환.// 또는 Calendar now = Calendar.getInstance(Locale.KOREA);
    System.out.println ( cal.get ( Calendar.YEAR ) + "년 " + ( cal.get ( Calendar.MONTH ) + 1 ) + "월 " + cal.get ( Calendar.DATE ) + "일 " + cal.get ( Calendar.HOUR_OF_DAY ) + "시 " +cal.get ( Calendar.MINUTE ) + "분 " + cal.get ( Calendar.SECOND ) + "초 " );

    결과 : 2000년 8월 5일 16시 16분 47초
    ------------------------------------------------------------------

    영어로된 날짜를 숫자로 바꾸기
    ------------------------------------------------------------------
    Date myDate = new Date ( "Sun,5 Dec 1999 00:07:21" );
    System.out.println ( myDate.getYear ( ) + "-" + myDate.getMonth ( ) + "-" + myDate.getDay ( ) );
    ------------------------------------------------------------------

    "Sun, 5 Dec 1999 00:07:21"를 "1999-12-05"로 바꾸기
    ------------------------------------------------------------------
    SimpleDateFormat formatter_one = new SimpleDateFormat ( "EEE, dd MMM yyyy hh:mm:ss",Locale.ENGLISH );
    SimpleDateFormat formatter_two = new SimpleDateFormat ( "yyyy-MM-dd" );

    String inString = "Sun, 5 Dec 1999 00:07:21";

    ParsePosition pos = new ParsePosition ( 0 );
    Date frmTime = formatter_one.parse ( inString, pos );
    String outString = formatter_two.format ( frmTime );

    System.out.println ( outString );
    ------------------------------------------------------------------

    숫자 12자리를, 다시 날짜로 변환하기
    ------------------------------------------------------------------
    Date conFromDate = new Date();
    long ttl = conFromDate.parse ( "Dec 25, 1997 10:10:10" );
    System.out.println ( ttl ); //예 938291839221

    Date today = new Date ( ttl );
    DateFormat format = DateFormat.getDateInstance ( DateFormat.FULL,Locale.US );
    String formatted = format.format ( today );
    System.out.println ( formatted );
    ------------------------------------------------------------------

    특정일로부터 n일 만큼 이동한 날짜 구하기
    ------------------------------------------------------------------
    특정일의 시간을 long형으로 읽어온다음..
    날짜*24*60*60*1000 을 계산하여.
    long형에 더해줍니다.
    그리고 나서 Date클래스와 Calender클래스를 이용해서 날짜와 시간을 구하면 됩니다
    ------------------------------------------------------------------

    특정일에서 일정 기간후의 날짜 구하기2
    ------------------------------------------------------------------
    //iDay 에 입력하신 만큼 빼거나 더한 날짜를 반환 합니다.
    import java.util.*;

    public String getDate ( int iDay )
    {
    Calendar temp=Calendar.getInstance ( );
    StringBuffer sbDate=new StringBuffer ( );

    temp.add ( Calendar.DAY_OF_MONTH, iDay );

    int nYear = temp.get ( Calendar.YEAR );
    int nMonth = temp.get ( Calendar.MONTH ) + 1;
    int nDay = temp.get ( Calendar.DAY_OF_MONTH );

    sbDate.append ( nYear );
    if ( nMonth < 10 )
    sbDate.append ( "0" );
    sbDate.append ( nMonth );
    if ( nDay < 10 )
    sbDate.append ( "0" );
    sbDate.append ( nDay );

    return sbDate.toString ( );
    }
    ------------------------------------------------------------------

    현재날짜에서 2달전의 날짜를 구하기
    ------------------------------------------------------------------
    Calendar cal = Calendar.getInstance ( );//오늘 날짜를 기준으루..
    cal.add ( cal.MONTH, -2 ); //2개월 전....
    System.out.println ( cal.get ( cal.YEAR ) );
    System.out.println ( cal.get ( cal.MONTH ) + 1 );
    System.out.println ( cal.get ( cal.DATE ) );
    ------------------------------------------------------------------

    달에 마지막 날짜 구하기
    ------------------------------------------------------------------
    for ( int month = 1; month <= 12; month++ )
    {
    GregorianCalendar cld = new GregorianCalendar ( 2001, month - 1, 1 );
    System.out.println ( month + "/" + cld.getActualMaximum ( Calendar.DAY_OF_MONTH ) );
    }
    ------------------------------------------------------------------

    해당하는 달의 마지막 일 구하기
    ------------------------------------------------------------------
    GregorianCalendar today = new GregorianCalendar ( );
    int maxday = today.getActualMaximum ( ( today.DAY_OF_MONTH ) );
    System.out.println ( maxday );
    ------------------------------------------------------------------

    특정일을 입력받아 해당 월의 마지막 날짜를 구하는 간단한 예제.(달은 -1 해준다.)...윤달 30일 31일 알아오기.
    ------------------------------------------------------------------
    Calendar cal = Calendar.getInstance ( );
    cal.set ( Integer.parseInt ( args[0] ), Integer.parseInt ( args [1] ) - 1, Integer.parseInt ( args [2] ) );
    SimpleDateFormat dFormat = new SimpleDateFormat ( "yyyy-MM-dd" );
    System.out.println ( "입력 날짜 " + dFormat.format ( cal.getTime ( ) ) );
    System.out.println ( "해당 월의 마지막 일자 : " + cal.getActualMaximum ( Calendar.DATE ) );
    ------------------------------------------------------------------

    해당월의 실제 날짜수 구하기 ( 1999년 1월달의 실제 날짜수를 구하기 )
    ------------------------------------------------------------------
    Calendar calendar = Calendar.getInstance ( );
    calendar.set ( 1999, 0, 1 );
    int maxDays = calendar.getActualMaximum ( Calendar.DAY_OF_MONTH );
    ------------------------------------------------------------------

    어제 날짜 구하기
    ------------------------------------------------------------------
    오늘날짜를 초단위로 구해서 하루분을 빼주고 다시
    셋팅해주면 쉽게 구할수 있죠..
    setTime((기준일부터 오늘까지의 초를 구함) - 24*60*60)해주면 되겠죠..
    ------------------------------------------------------------------

    어제 날짜 구하기2
    ------------------------------------------------------------------
    import java.util.*;

    public static Date getYesterday ( Date today )
    {
    if ( today == null )
    throw new IllegalStateException ( "today is null" );
    Date yesterday = new Date ( );
    yesterday.setTime ( today.getTime ( ) - ( (long) 1000 * 60 * 60 * 24 ) );

    return yesterday;
    }
    ------------------------------------------------------------------

    내일 날짜 구하기
    ------------------------------------------------------------------
    Date today = new Date ( );
    Date tomorrow = new Date ( today.getTime ( ) + (long) ( 1000 * 60 * 60 * 24 ) );
    ------------------------------------------------------------------

    내일 날짜 구하기2
    ------------------------------------------------------------------
    Calendar today = Calendar.getInstance ( );
    today.add ( Calendar.DATE, 1 );
    Date tomorrow = today.getTime ( );
    ------------------------------------------------------------------

    오늘날짜에서 5일 이후 날짜를 구하기
    ------------------------------------------------------------------
    Calendar cCal = Calendar.getInstance();
    c.add(Calendar.DATE, 5);
    ------------------------------------------------------------------

    날짜에 해당하는 요일 구하기
    ------------------------------------------------------------------
    //DAY_OF_WEEK리턴값이 일요일(1), 월요일(2), 화요일(3) ~~ 토요일(7)을 반환합니다.
    //아래 소스는 JSP일부입니다.
    import java.util.*;

    Calendar cal= Calendar.getInstance ( );
    int day_of_week = cal.get ( Calendar.DAY_OF_WEEK );
    if ( day_of_week == 1 )
    m_week="일요일";
    else if ( day_of_week == 2 )
    m_week="월요일";
    else if ( day_of_week == 3 )
    m_week="화요일";
    else if ( day_of_week == 4 )
    m_week="수요일";
    else if ( day_of_week == 5 )
    m_week="목요일";
    else if ( day_of_week == 6 )
    m_week="금요일";
    else if ( day_of_week == 7 )
    m_week="토요일";

    오늘은 : 입니다.
    ------------------------------------------------------------------

    콤보박스로 선택된 날짜(예:20001023)를 통해 요일을 영문으로 가져오기
    ------------------------------------------------------------------
    //gc.get(gc.DAY_OF_WEEK); 하면 일요일=1, 월요일=2, ..., 토요일=7이 나오니까,
    //요일을 배열로 만들어서 뽑아내면 되겠죠.
    GregorianCalendar gc=new GregorianCalendar ( 2000, 10 - 1 , 23 );
    String [] dayOfWeek = { "", "Sun", "Mon", .... , "Sat" };
    String yo_il = dayOfWeek ( gc.get ( gc.DAY_OF_WEEK ) );
    ------------------------------------------------------------------

    두 날짜의 차이를 일수로 구하기
    ------------------------------------------------------------------
    각각의 날짜를 Date형으로 만들어서 getTime()하면
    long으로 값이 나오거든요(1970년 1월 1일 이후-맞던가?- 1/1000 초 단위로..)
    그러면 이값의 차를 구해서요. (1000*60*60*24)로 나누어 보면 되겠죠.
    ------------------------------------------------------------------

    두 날짜의 차이를 일수로 구하기2
    ------------------------------------------------------------------
    import java.io.*;
    import java.util.*;

    Date today = new Date ( );
    Calendar cal = Calendar.getInstance ( );
    cal.setTime ( today );// 오늘로 설정.

    Calendar cal2 = Calendar.getInstance ( );
    cal2.set ( 2000, 3, 12 ); // 기준일로 설정. month의 경우 해당월수-1을 해줍니다.

    int count = 0;
    while ( !cal2.after ( cal ) )
    {
    count++;
    cal2.add ( Calendar.DATE, 1 ); // 다음날로 바뀜

    System.out.println ( cal2.get ( Calendar.YEAR ) + "년 " + ( cal2.get ( Calendar.MONTH ) + 1 ) + "월 " + cal2.get ( Calendar.DATE ) + "일" );
    }

    System.out.println ( "기준일로부터 " + count + "일이 지났습니다." );
    ------------------------------------------------------------------

    두 날짜의 차이를 일수로 구하기3
    ------------------------------------------------------------------
    import java.io.*;
    import java.util.*;

    public class DateDiff
    {
    public static int GetDifferenceOfDate ( int nYear1, int nMonth1, int nDate1, int nYear2, int nMonth2, int nDate2 )
    {
    Calendar cal = Calendar.getInstance ( );
    int nTotalDate1 = 0, nTotalDate2 = 0, nDiffOfYear = 0, nDiffOfDay = 0;

    if ( nYear1 > nYear2 )
    {
    for ( int i = nYear2; i < nYear1; i++ )
    {
    cal.set ( i, 12, 0 );
    nDiffOfYear += cal.get ( Calendar.DAY_OF_YEAR );
    }
    nTotalDate1 += nDiffOfYear;
    }
    else if ( nYear1 < nYear2 )
    {
    for ( int i = nYear1; i < nYear2; i++ )
    {
    cal.set ( i, 12, 0 );
    nDiffOfYear += cal.get ( Calendar.DAY_OF_YEAR );
    }
    nTotalDate2 += nDiffOfYear;
    }

    cal.set ( nYear1, nMonth1-1, nDate1 );
    nDiffOfDay = cal.get ( Calendar.DAY_OF_YEAR );
    nTotalDate1 += nDiffOfDay;

    cal.set ( nYear2, nMonth2-1, nDate2 );
    nDiffOfDay = cal.get ( Calendar.DAY_OF_YEAR );
    nTotalDate2 += nDiffOfDay;

    return nTotalDate1-nTotalDate2;
    }

    public static void main ( String args[] )
    {
    System.out.println ( "" + GetDifferenceOfDate (2000, 6, 15, 1999, 8, 23 ) );
    }
    }
    ------------------------------------------------------------------

    파일에서 날짜정보를 가져오기
    ------------------------------------------------------------------
    File f = new File ( directory, file );

    Date date = new Date ( f.lastModified ( ) );
    Calendar cal = Calendar.getInstance ( );
    cal.setTime ( date );

    System.out.println("Year : " + cal.get(Calendar.YEAR));
    System.out.println("Month : " + (cal.get(Calendar.MONTH) + 1));
    System.out.println("Day : " + cal.get(Calendar.DAY_OF_MONTH));
    System.out.println("Hours : " + cal.get(Calendar.HOUR_OF_DAY));
    System.out.println("Minutes : " + cal.get(Calendar.MINUTE));
    System.out.println("Second : " + cal.get(Calendar.SECOND));
    ------------------------------------------------------------------

    날짜형식으로 2000-01-03으로 처음에 인식을 시킨후
    7일씩 증가해서 1년정도의 날짜를 출력해 주고 싶은데요.
    ------------------------------------------------------------------
    SimpleDateFormat sdf = new SimpleDateFormat ( "yyyy-mm-dd" );
    Calendar c = Calendar.getInstance ( );

    for ( int i = 0; i < 48; i++ )
    {
    c.clear ( );
    c.set ( 2000, 1, 3 - ( i * 7 ) );
    java.util.Date d = c.getTime ( );
    String thedate = sdf.format ( d );
    System.out.println ( thedate );
    }
    ------------------------------------------------------------------

    쓰레드에서 날짜 바꾸면 죽는 문제
    ------------------------------------------------------------------
    Main화면에 날짜와시간이Display되는 JPanel이 있습니다.
    date로 날짜와 시간을 변경하면 Main화면의 날짜와 시간이 Display되는 Panel에
    변경된 날짜가 Display되지 않고 Main화면이 종료되어 버립니다.

    문제소스:
    public void run ( )
    {
    while ( true )
    {
    try{
    timer.sleep ( 60000 );
    }
    catch ( InterruptedException ex ) { }

    lblTimeDate.setText ( fGetDateTime ( ) );
    repaint ( );
    }
    }

    public String fGetDateTime ( )
    {
    final int millisPerHour = 60 * 60 * 1000;
    String DATE_FORMAT = "yyyy / MM / dd HH:mm";
    SimpleDateFormat sdf = new SimpleDateFormat ( DATE_FORMAT );
    SimpleTimeZone timeZone = new SimpleTimeZone ( 9 * millisPerHour, "KST" );
    sdf.setTimeZone ( timeZone );

    long time = System.currentTimeMillis ( );
    Date date = new Date ( time );
    return sdf.format ( date );
    }

    해답:
    // 날짜와 요일 구한다. timezone 으로 날짜를 다시 셋팅하시면 됨니다.
    public String getDate ( )
    {
    Date now = new Date ( );
    SimpleDateFormat sdf4 = new SimpleDateFormat ( "yyyy/MM/dd HH:mm EE" );
    sdf4.setTimeZone ( TimeZone.getTimeZone ( "Asia/Seoul" ) );

    return sdf4.format ( now );
    }
    ------------------------------------------------------------------

    날짜와 시간이 유효한지 검사하려면...?
    ------------------------------------------------------------------
    import java.util.*;
    import java.text.*;

    public class DateCheck
    {
    boolean dateValidity = true;

    DateCheck ( String dt )
    {
    try
    {
    DateFormat df = DateFormat.getDateInstance ( DateFormat.SHORT );
    df.setLenient ( false );
    Date dt2 = df.parse ( dt );
    }
    catch ( ParseException e ) { this.dateValidity = false; }
    catch ( IllegalArgumentException e ) { this.dateValidity = false; }
    }

    public boolean datevalid ( )
    {
    return dateValidity;
    }

    public static void main ( String args [] )
    {
    DateCheck dc = new DateCheck ( "2001-02-28" );
    System.out.println ( " 유효한 날짜 : " + dc.datevalid ( ) );
    }
    }
    ------------------------------------------------------------------

    두 날짜 비교하기(아래보다 정확)
    ------------------------------------------------------------------
    그냥 날짜 두개를 long(밀리 세컨드)형으로 비교하시면 됩니다...

    이전의 데이타가 date형으로 되어 있다면, 이걸 long형으로 변환하고.
    현재 날짜(시간)은 System.currentTimeMillis()메소드로 읽어들이고,
    두수(long형)를 연산하여 그 결과 값으로 비교를 하시면 됩니다.

    만약 그 결과값이 몇시간 혹은 며칠차이가 있는지를 계산할려면,
    결과값을 Calender의 setTimeInMillis(long millis) 메소드를 이용해
    설정한다음 각각의 날짜나 시간을 읽어오시면 됩니다
    ------------------------------------------------------------------

    두 날짜 비교하기2
    ------------------------------------------------------------------
    //Calendar를 쓸 경우 데이타의 원본을 고치기 때문에 clone()을 사용하여
    //복사한 후에 그 복사본을 가지고 비교한다
    import java.util.*;
    import java.util.Calendar.*;
    import java.text.SimpleDateFormat;

    public class DayComparisonTest
    {
    public static void main(String args[])
    {
    Calendar cal = Calendar.getInstance();
    SimpleDateFormat dateForm = new SimpleDateFormat("yyyy-MM-dd");

    Calendar aDate = Calendar.getInstance(); // 비교하고자 하는 임의의 날짜
    aDate.set(2001, 0, 1);

    Calendar bDate = Calendar.getInstance(); // 이것이 시스템의 날짜

    // 여기에 시,분,초를 0으로 세팅해야 before, after를 제대로 비교함
    aDate.set( Calendar.HOUR_OF_DAY, 0 );
    aDate.set( Calendar.MINUTE, 0 );
    aDate.set( Calendar.SECOND, 0 );
    aDate.set( Calendar.MILLISECOND, 0 );

    bDate.set( Calendar.HOUR_OF_DAY, 0 );
    bDate.set( Calendar.MINUTE, 0 );
    bDate.set( Calendar.SECOND, 0 );
    bDate.set( Calendar.MILLISECOND, 0 );


    if (aDate.after(bDate)) // aDate가 bDate보다 클 경우 출력
    System.out.println("시스템 날짜보다 뒤일 경우 aDate = " + dateForm.format(aDate.getTime()));
    else if (aDate.before(bDate)) // aDate가 bDate보다 작을 경우 출력
    System.out.println("시스템 날짜보다 앞일 경우 aDate = " + dateForm.format(aDate.getTime()));
    else // aDate = bDate인 경우
    System.out.println("같은 날이구만");
    }
    }
    posted by 좋은느낌/원철
    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: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. 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 좋은느낌/원철
    prev 1 2 next