01.JAVA/Java2008. 11. 12. 17:10
반응형

많은 사람들이 알고 있는 것처럼, JDBC는 자바 프로그램으로부터 관련된 데이터베이스에 이식가능한 액세스를 할 수 있는 메커니즘이다. 관련된 데이터베이스에 액세스하기 위해 사용자의 J2EE 애플리케이션의 JDBC를 사용할 수 있다. 이번 테크팁은 JDBC를 사용하는 간단한 애플리케이션을 보여주고 이식성과 유연성을 위한 몇 가지 유용한 JDBC 실습법을 소개한다.

JDBC의 소개

JDBC를 이용해서 데이터에 액세스하는 절차는 간단하다.

  1. 드라이버를 로드한다. 대부분의 경우 클래스이름으로 되어있다.

  2. 데이터베이스에 Connection 접속한다. 이름으로 DataSource 를 위한 JNDI를 호출하고 DataSource 로부터 Connection을 얻어서 접속할 수 있다. 이 절차는 동일한 서블릿 메소드인 getConnection 에서 실제적으로 볼 수 있을 것이다.

  3. Connection로부터 Statement 객체를 받는다. 각각의 Statement는 단일 SQL 커맨드를 나타낸다. StatementStatement의 출처가 되는 Connection에 묶여 있기 때문에 다른 Connection을 위해 재사용될 수가 없다.

  4. updateQuery(데이터를 업데이트하지만 결과는 리턴하지 않음)나 executeQuery(선정된 데이터의 ResultSet를 리턴함) 와 같은 Statement 메소드를 이용해 비즈니스 기능을 수행해 보자.

  5. ResultSet를 받았다면 다른 비즈니스 기능을 수행하기 위해 쿼리의 결과를 반복해서 실행해 보자.

이 팁의 샘플 코드에서는 jdbcservlet라고 불리는 서블릿을 사용하고 있다. 이 서블릿은 데이터베이스에 접속하여 임의의 SQL 구를 실행하도록 한다. 그리고 리턴되는 결과를 테이블의 형태로 보여주고, SAVEDQUERIES라고 불리는 테이블 안에 지명된 쿼리들을 저장할 수 있게 한다.

데이터베이스 생성하기

설명을 계속하기 전에 이 애플리케이션에 번들된 데이터 로딩 스크립트가 연결하고자 하는 데이터베이스 내의 테이블을 드롭하고 생성한다는 것을 기억해야 한다. 필요한 데이터를 포함하고 있는 데이터베이스에 연결하고 있지 않다는 것을 확실히 해야 한다.

안전한 데이터베이스에 연결하고 있다는 것을 확실히 확인할 수 있는 가장 좋은 방법은 작업이 끝났을 때 삭제해 버릴 수 있는 것을 하나 생성해 놓는 것이다. 이 팁은 썬으로부터 배포된 표준 J2EE 1.4에 탑재된 Java technology relational database management system (RDBMS)인 PointBase 를 사용한다. 이는 통합된 PointBase 드라이버에 부수되어 배포되기 때문에 가장 사용하기 쉬운 방법이다. 원한다면 고유의 데이터베이스를 생성하기 위해 제공자가 지정한 툴을 사용할 수도 있다. (이 경우, 배포 시에 몇 가지 사항만 변경하면 된다.) 데이터베이스를 생성하기 위해서는 "샘플 코드 실행하기"의 "팁 2: 데이터베이스 생성하기" 부분을 참고하기 바란다.

쿼리 실행하기

샘플 애플리케이션에 의해 디스플레이되는 메인 페이지에서 SQL 쿼리를 동반하는 텍스트 박스를 볼 수 있다.

이 쿼리를 실행하려면 Execute SQL 버튼을 클릭한다.

쿼리 결과의 일부분만을 편집한 것이다.

결과를 나타내는 폼은 3가지 영역을 갖는다.

  • 왼쪽 윗부분, 쿼리 박스
  • 오른쪽 윗부분, 저장된 쿼리의 리스트
  • 이하부분, 쿼리를 실행했을 때 나타나는 결과를 나타내는 테이블

데이터베이스의 데이터 출처는 퍼블릭 도메인 소스이다. 이는 이세상에 존재하는 나라들과 그곳에 사는 사람들에 관한 기초적인 통계를 보여준다.

Name 박스에 이름을 타이핑하고 Save Query 버튼을 클릭하여 흥미있는 쿼리를 저장할 수 있다. 이 쿼리는 오른쪽 Saved 쿼리 박스에 나타날 것이다. 링크를 클릭하여 저장된 쿼리를 실행해보자.

샘플 코드

이하의 샘플은 보기에는 복잡하지만 사실은 매우 간단하다. 이 코드의 핵심을 간추려보았다.

1. 드라이버 로드하기

드라이버를 로드하는 코드는 서블릿을 위한 init 메소드 내에 있다.

    public void init(ServletConfig config)
       throws ServletException {
       _config = config;

       // Load driver. If this fails, the rest won't work.
       try {
          String driver =
                   _config.getInitParameter("driver");
          Class.forName(driver).newInstance();
       } catch (Exception e) {
          throw new ServletException(e);
       }
    }

드라이버 클래스 이름은 서블릿 초기화 파라미터에서 왔다는 것을 기억하자. 이 파라미터는 웹 애플리케이션 배포 디스크립터인 web.xml에 설정되어 있다.

   <servlet>
     <description>Sample servlet</description>
     <display-name>Apr2004Servlet</display-name>
     <servlet-name>Apr2004Servlet</servlet-name>
     <servlet-class>
       com.elucify.tips.apr2004.Apr2004Servlet
     </servlet-class>
     <init-param>
       <description>
         This is the name of the JDBC driver
         used to connect to the database.
       </description>
       <param-name>driver</param-name>
       <param-value>
         com.pointbase.jdbc.jdbcUniversalDriver
       </param-value>
     </init-param>
     ...

드라이버 클래스의 이름을 배포 정보에 삽입하는 것은 좋은 습관이다. 드라이버 클래스를 변경해야 한다면(가령 다른 데이터베이스를 사용하고 있기 때문에), 코드를 변경하고 그것을 다시 컴파일하는 것 대신에 이를 배포 툴에서 해결할 수 있다.

데이터베이스 이름, 사용자, 패스워드의 경우 모두 동일하다.

2. 데이터베이스에 접속한다.

다음 메소드는 데이터베이스에 대한 Connection을 얻기 위해 사용된다.

    protected Connection getConnection() throws Exception {
       String dbname = _config.getInitParameter("dbname");
       String user = _config.getInitParameter("user");
       String pass = _config.getInitParameter("pass");

       InitialContext ic = new InitialContext();
       DataSource ds = (DataSource)ic.lookup(dbname);
       Connection conn = ds.getConnection(user, pass);

       return conn;
    }

Init 파라미터를 정의하고 지정하기

이와 같은 init 파라미터를 세팅하는 배포 디스크립터는 다음과 같다.

     <init-param>
       <description>
         This is the JDBC name of the database.
       </description>
       <param-name>dbname</param-name>
       <param-value>jdbc/PointBase</param-value>
     </init-param>

    <init-param>
      <description>
        The name of the database user.
      </description>
      <param-name>user</param-name>
      <param-value>PBPUBLIC</param-value>
    </init-param>

    <init-param>
      <description>
        The password for the user named above.
      </description>
      <param-name>pass</param-name>
      <param-value>PBPUBLIC</param-value>
    </init-param>

또 다시, 이러한 정보를 배포 디스크립터에 삽입하면 컴파일링을 다시 하지 않고서도 정보를 변경할 수 있게 해준다.

일반적으로 제작과정에서는 배포 디스크립터 파일에 패스워드를 저장하지 않는다. 인증을 잘 관리하기 위해서는 조직의 보안 책임자와 상담하는 것이 대부분이다. 위의 "jdbc/PointBase" 스트링값은 데이터베이스의 이름이다. 이 JDBC리소스는 Sun One 애플리케이션 서버 내에 미리 형성되어서 존재한다. 만약 다른 애플리케이션 서버를 사용한다면 데이터베이스의 이름을 찾기 위해 서버 다큐먼테이션을 보면 된다.(서버 다큐먼트를 직접 생성해야 할 수도 있다.)

3. 데이터베이스를 로드한다.

dataload 메소드는 WAR 아카이브 내의 파일(data/data.sql)로부터 sql 문을 읽어 들인다. 그리고 파일이 처리될 때까지 항목들을 하나씩 차례대로 실행한다. 이 메소드 코드의 대부분은 단순한 스트링 처리이다. 이러한 스티링 처리의 결과는 sqlcmd 라고 불리는 스트링 변수이다. sqlcmd StringBuffer 로 구축되었다. 이 메소드는 Connection 을 생성하고 Connection 으로부터 Statement를 받아서 sqlcmd 쿼리 스트링을 구축한다. 그리고나서 완전한 커맨드를 갖으면 sqlcmd.executeUpdate 에 그 스트링 값을 넘겨주게 된다.

   StringBuffer sbcmd = new StringBuffer();
   String line = "";
   Connection conn = null;

   try {
      // Get connection and statement
      conn = getConnection();
      Statement stmt = conn.createStatement();

      while ((line = in.readLine()) != null) {
         linenumber++;

         // ... line splitting, cleaning, joining, etc.
         // ... result is in sbcmd

          String sqlcmd = new String(sbcmd);
         
          // ... more housekeeping

          stmt.executeUpdate(sqlcmd);

   } catch (Exception e) {
      // ... blah blah
   } finally {
      if (conn != null) {
         try {
            conn.close();
         } catch (SQLException exc) {
            // Not much we can do about it here except...
            System.err.println
              ("Failed closing connection in dataload");
         }
      }
   }

dataload 메소드의 코드는 파일로부터 읽어 들인 SQL 구를 구축하고 Statement.executeUpdate 가 실행될 수 있도록 이를 넘겨준다. 이 메소드는 executeQuery 대신에 executeUpdate 를 사용하는데, 이유는 executeUpdate가 결과를 리턴하지 않고 데이터 로드 스크립트는 SELECT 구를 포함할 수 없기 때문이다.

메소드 dataload의 코드(method dataload code)내의 try/finally블록을 주의 깊게 살펴보자. 이 코드 블록은 중요한 연습방법을 따르고 있는데, 그것은 사용자가 커넥션에 관한 작업을 마쳤을 때 언제나 커넥션들이 닫혀져 있어야 한다는 것이다. 이렇게 하지 않았을 때 심한 리소스 누출이 생길 수 있다. 이런 경우, 서버의 커넥션 풀 안의 모든 커넥션은 닫혀있지 않은 커넥션에 의해 계속된다. 때문에 항상 try/finally블록에 Connection객체의 사용을 enclose하고 필요할 시 커넥션을 닫아야 한다.

서버-사이드 커넥션 관리에 있어 또 다른 주의 사항은, 만약 서버가 커넥션을 갖지 않는다는 사실을 파악하지 못했다면 커넥션 풀을 구현하려는 시도를 하지 말아야 한다는 것이다. 서버가 이미 커넥션을 갖고 있는 상태라면 커넥션 풀을 구현하려는 것은 시간 낭비일 수 있기 때문이다. 또한 서버 커넥션 풀과 애플리케이션 레벨의 풀 간에 경합은 애플리케이션의 퍼포먼스를 떨어뜨릴 수 있다. 말하자면 커넥션 풀링은 애플리케이션 프로그래머가 아닌 서버 프로그래머의 일이라는 것이다.

웹 애플리케이션에서 대부분의 경우처럼 서버가 커넥션 풀을 갖고 있다면 HTML 호출을 리턴하기 전에 커넥션을 항상 해제해야 한다. 만약 서버가 이미 공유되는 커넥션을 효율적으로 관리하고 있다면 이 공유 커넥션에 시간을 투자할 이유는 없다. 또한 서블릿이 불필요하게 데이터베이스 커넥션에 매달려있다면 애플리케이션의 속도를 늦출 수 있다.

4. SQL 문 실행하기

브라우저로부터 받은 SQL문은 데이터를 리턴하는 SELECT 문이거나 데이터를 리턴하지 않는 다른 문장일 수 있다. evalprint 메소드는 이후의 SQL이 SELECT 문이라면 Statement.executeQuery를 사용하고 그렇지 않으면 executeUpdate를 사용한다. 입력 서식에서 받은 사용자의 SQL을 실행시키는 코드는 다음과 같다.

   try {
      // Get connection and statement
      conn = getConnection();
      Statement stmt = conn.createStatement();
      ResultSet rs;

      // Execute posted sqlcmd
      if (sqlcmd.trim().toUpperCase().indexOf("SELECT ") == 0) {
         rs = stmt.executeQuery(sqlcmd);
         pw.println("<div class=\"results\">");
         pw.println("<h2>Query Results</h2>");
         printResultSet(pw, rs);
         pw.println("</div>");
      } else {
         stmt.executeUpdate(sqlcmd);
      }

5. 출력 결과

printResultSet 메소드는 HTML 테이블 형식으로 ResultSet객체를 출력한다. ResultSet객체를 위한 데이터 액세스 인터페이스는 SQL 결과를 다루기 쉽게 만든다.

   protected void printResultSet(PrintWriter pw, 
           ResultSet rs)
      throws SQLException {
      ResultSetMetaData rsmd = rs.getMetaData();
      int cols = rsmd.getColumnCount();
      int nrows = 0;

      pw.println("<TABLE class=\"tbl\">");
      pw.println("<TR class=\"hdrtr\">");
      for (int n = 1; n <= cols; n++) {
         pw.print
           ("<TD class=\"hdrcol hdrcol" + n + "\">");
         pw.print(rsmd.getColumnLabel(n));
         pw.println("</TD>");
      }
      pw.println("</TR>");

      while (rs.next()) {
         ++nrows;
         pw.println
           ("<TR class=\"datatr datatr" + nrows + "\">");
         for (int i = 1; i <= cols; i++) {
            pw.print("<TD class=\"datacol datacol" + 
                     i + "\">");
            pw.print(rs.getObject(i).toString());
            pw.println("</TD>");
         }
         pw.println("</TR>");
      }
      pw.println("</TABLE>");

      pw.println("<div class=\"rowcount\">");
      pw.println(nrows + " rows returned.");
      pw.println("</div>");
   }

JDBC에 관한 더 많은 정보는 자바 튜토리얼의 "JDBC Basics" 과 Fisher, Ellis, Bruce 의 "JDBC API Tutorial and Reference, Third Edition" 를 참고하기 바란다.

샘플코드 실행하기

주의: 2번째 팁의 샘플 코드를 실행하기 위해서는 J2EE서버를 시작하기 전에 PointBase 데이터베이스 서버를 먼저 시작해야 한다.

이 팁을 위한 샘플 아카이브를 다운로드 받자. 애플리케이션의 컨텍스트 루트는 ttapr2004이다. 다운로드된 ear 파일은 샘플을 위한 완성된 코드를 포함한다. deploytool 프로그램이나 어드민 콘솔을 이용해서 J2EE 1.4 Application Server에 애플리케이션 아카이브(ttapr2004.ear) 를 배포할 수 있다. 혹은 다음과 같은 asadmin 커맨드로 이를 배포할 수도 있다.

   asadmin deploy install_dir/ttapr2004.ear

install_dir를 war 파일을 인스톨한 디렉토리로 대체하자.

http://localhost:8000/ttapr2004에서 애플리케이션에 액세스할 수 있다.

Application Server이외의 J2EE 1.4에 호환되는 구현을 원한다면 플랫폼에 애플리케이션을 배포할 때 J2EE 제품의 배포툴을 이용하면 된다.

애플리케이션의 실행을 위한 설명은 index.jsp welcome 파일을 참고하기 바란다.

팁 1: JAR의 다운로드와 압축출기

팁 1의 샘플 코드는 apr2004-tip1.jar라고 불리는 JAR 파일 내의 애플리케이션 아카이브에 있다. apr2004-tip1.jar파일을 다운로드 받는 가장 쉬운 방법은 위에서 언급한 애플리케이션 EAR 파일을 배포하고 메인 페이지를 방문하여 Web 페이지의 첫번째 부분에 있는 다운로드 링크를 클릭하는 것이다.

또 다른 방법은 ttapr2004.ear 에서apr2004.war 를 처음 추출해낼 때 JAR 유틸리티를 이용하는 것이다. 그리고나서 apr2004.war 에서 apr2004-tip1.jar를 추출해낸다.

jar파일을 확보했다면 다음을 이용해서 내용물들을 모두 추출하자.

   $ jar xvf apr2004.jar

결과는 "apr2004"라고 불리는 디렉토리이고 코드와 컴파일된 클래스 지원 데이터 파일등과 함께 소스 파일을 포함하고 있다.

팁 2: 데이터베이스 생성하기

프로그램을 처음으로 실행할 때 데이터를 데이터베이스안에 로드해야 한다. 데이터베이스 로드 스크립트는 애플리케이션 아카이브 내에 있다. 데이터를 로드하기 위해 페이지에서 적절한 링크를 클릭하자. 데이터베이스가 6000줄이 넘기 때문에 시간이 좀 걸릴 수 있다.

데이터가 로드되었다면 텍스트 입력란에 SQL문을 입력하고 Execute SQL 버튼을 이용해서 그것을 실행한다.

이 글 뿐만 아니라 썬에서 제공하는 다른 샘플코드로부터 샘플 테이터를 볼 수 있다.

리스트에 저장된 쿼리를 클릭하면 이 쿼리를 위한 SQL문과 쿼리를 실행한 결과를 보게 된다.

가령, CountryBorders 를 클릭하면 다음과 같은 결과를 보게 된다.

"Java EE" 카테고리의 다른 글

Posted by 1010