Tomcat 5 JNDI DataSource를 통한 DB 커넥션 풀 사용
JNDI를 이용한 커넥션 풀에 대해서 정리해봤습니다. 블로그에 쓴글이라 반말이예요... 양해를 ^^ 원문 : http://kr.blog.yahoo.com/kwon37xi/1236540.html Tomcat 5 JNDI DataSource를 통한 DB 커넥션 풀 사용 이미 눈치 채셨겠지만, 요즘 내가 RDBMS 배우기에 열을 올리고 있다. 지금까지는 JSP/Servlet에서 직접 커넥션을 맺거나, 웹 컨텍스트내에 커넥션 풀 라이브러리를 두고 호출에서 사용했는데, 좀 바꿔야겠다. JNDI를 통한 커넥션 풀 사용은 J2EE 표준이고, 현존하는 거의 모든 웹 컨테이너가 지원한다고 한다. JNDI를 서버에 설정하는 방법은 각 WAS 별로 다르지만, 사용하는 것은 모두 동일하므로 호환성에 문제도 없다. 이 글은 Jakarta의 DBCP 커넥션 풀과 Tomcat JNDI 설정을 통해 데이터베이스 커넥션 풀을 사용하는 방법이다. JNDI와 커넥션 풀에 관한 자세한 설명이 JavaServer Pages 3rd ed.에 실려있다. 이 책 너무 좋다. 꼭 읽어보라고 강력하게 권하고 싶다. * 참조 URL : http://jakarta.apache.org/tomcat/tomcat-5.0-doc/jndi-datasource-examples-howto.html 이 글은 사실상 저 참조 URL의 번역에 가깝다. * Tomcat Admin을 이용한 DataSource 설정 : http://www.okjsp.pe.kr/lecture/viewlet/okjsp2005/05_webdev_datasource.html 기본적으로 필요한 라이브러리 * commons-dbcp.jar * commons-collections.jar * commons-pool.jar 예제 JDBC 드라이버 * Oracle 9i classes12.jar JNDI Naming Resource 설정 1. 위 라이브러리들을 $CATALINA_HOME/common/lib 에 복사한다. 그 이외 디렉토리에 두면 안된다. ZIP 파일은 JAR로 확장자를 바꿔야 한다. 톰캣은 JAR파일만 클래스패스에 추가한다. 2. Connection 풀을 이용할 경우에는 ResultSet과 Connection 객체를 필히 직접 닫아 줘야만 한다. 3. $CATALINA_HOME/conf/server.xml 혹은 각 웹 컨텍스트별 XML 파일의 <Context>의 자식 요소로 다음을 추가한다. <Resource name="jdbc/forumDb" auth="Container" type="javax.sql.DataSource"/> <!-- Resource의 name 속성을 이용해서 각 어플리케이션에서 javax.sql.DataSource 객체를 얻어가게 된다. --> <!-- 자세한 파라미터 설정은 http://jakarta.apache.org/tomcat/tomcat-5.0-doc/jndi-datasource-examples-howto.html 참조 --> <ResourceParams name="jdbc/forumDb"> <parameter> <name>factory</name> <value>org.apache.commons.dbcp.BasicDataSourceFactory</value> </parameter> <parameter> <name>maxActive</name> <value>100</value> </parameter> <parameter> <name>maxIdle</name> <value>30</value> </parameter> <parameter> <name>maxWait</name> <value>10000</value> </parameter> <!-- DB 사용자명과 비밀번호 설정 --> <parameter> <name>username</name> <value>dbuser</value> </parameter> <parameter> <name>password</name> <value>dbpasswd</value> </parameter> <!-- JDBC 드라이버 클래스 --> <parameter> <name>driverClassName</name> <value>oracle.jdbc.driver.OracleDriver</value> </parameter> <!-- JDBC 접속 URL --> <parameter> <name>url</name> <value>jdbc:oracle:thin:@dbhost:1521:ORA</value> </parameter> </ResourceParams> 4. 웹 어플리케이션의 web.xml파일에 다음을 추가하여 JNDI 리소스를 사용할 수 있도록 한다. <resource-ref> <description>Forum DB Connection</description> <!-- 다음이 바로 리소스의 이름 --> <res-ref-name>jdbc/forumDb</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> JSP/Servlet 에서 사용하기 이제 다음과 같이 JNDI를 이용해 DataSource 객체를 얻고, 그 객체에서 커넥션을 얻어오면 된다. 다음은 서블릿을 작성하고, 그 서블릿에서 DB커넥션을 얻고, 쿼리를 날린 뒤, 그 결과를 JSP 파일에 포워딩하여 JSTL을 이용해 출력하는 것이다. 1. 예제 테이블과 데이터 SQL - 오라클 계정으로 다음과 같은 데이터를 생성했다고 가정하면 create table test ( num NUMBER NOT NULL, name VARCHAR2(16) NOT NULL ); truncate table test; insert into test values(1,'영희'); insert into test values(2, '철수'); insert into test values(3, '미숙'); commit; 2. test.JndiDataSourceTestServlet 소스 package test; import java.io.IOException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.sql.DataSource; public class JndiDataSourceTestServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Connection conn = null; ResultSet rs = null; Statement stmt = null; try { // 커넥션을 얻기 위한 전초작업. 이 부분을 메소드화 하면 되겠다. ------------ Context initContext = new InitialContext(); Context envContext = (Context)initContext.lookup("java:/comp/env"); DataSource ds = (DataSource)envContext.lookup("jdbc/forumDb"); // 커넥션 얻기 conn = ds.getConnection(); //------------------------------------------------------------------ String sql = "SELECT * from test"; stmt = conn.createStatement(); rs = stmt.executeQuery(sql); List results = new ArrayList(); while (rs.next()) { Map row = new HashMap(); row.put("num", rs.getString("num")); row.put("name", rs.getString("name")); results.add(row); } request.setAttribute("results", results); RequestDispatcher rd = request.getRequestDispatcher("/dbtest.jsp"); rd.forward(request, response); } catch (NamingException e) { throw new ServletException("JNDI 부분 오류", e); } catch (SQLException e) { throw new ServletException("SQL 부분 오류", e); } finally { // 리소스를 필히 반환할 것! if (rs != null) { try { rs.close(); } catch (Exception ignored) {} } if (stmt != null) { try { stmt.close(); } catch (Exception ignored) {} } if (conn != null) { try { conn.close(); } catch (Exception ignored) {} } } } } 3. web.xml에 서블릿 등록 <servlet> <servlet-name>dbtest.svl</servlet-name> <servlet-class>test.JndiDataSourceTestServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>dbtest.svl</servlet-name> <url-pattern>/dbtest.svl</url-pattern> </servlet-mapping> 4. /dbtest.jsp 소스 <%@ page contentType="text/html" pageEncoding="EUC-KR" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> <!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en"> <html> <head> <title>JNDI DataSource Test</title> </head> <body bgcolor="#FFFFFF"> <h2>Results</h2> <c:forEach var="row" items="${results}"> NUM : ${row.num}<br /> Name : ${row.name}<br /> <hr /> </c:forEach> </body> </html> 5. 이제 웹 브라우저에서 "/dbtest.svl" 을 호출해보면 결과를 볼 수 있다. 전역적인 JNDI 리소스 이용 <Resource>와 <ResourceParams> 요소를 server.xml의 <GlobalNamingResources> 의 자식노드로 옮기면 특정 웹 어플리케이션이 아니라, 이 톰캣에 설치된 전체 웹 어플리케이션에서 사용 할 수 있게 된다. 하지만 각 웹 어플리케이션 "<Context>"에 다음과 같은 설정을 해야 한다. <ResourceLink name="jdbc/forumDb" global="jdbc/forumDb" type="javax.sql.DataSource" /> 아니면 server.xml에서 <Host> 요소의 자식으로 다음을 추가하면 각 컨텍스트별 설정 필요없이 전체 웹 어플리케이션 컨텍스트에서 GlobalNamingResources로 지정된 JNDI 리소스를 사용할 수 있다. <DefaultContext> <ResourceLink name="jdbc/forumDb" global="jdbc/forumDb" type="javax.sql.DataSource" /> </DefaultContext> 문제가 생겼어요! 1. DB 커넥션이 간혹 끊어져요. Tomcat이 작동중인 JVM이 가비지 컬렉션을 할 때, 그 시간이 JNDI Resource에 파라미터로 설정한 maxWait보다 길게 갈 경우 DB 커넥션이 끊어질 수 있다. CATALINA_OPTS=-verbose:gc 옵션을 주면 $CATALINA_HOME/logs/catalina.out에 가비지 컬렉션 상황이 기록된다. 거기에 GC 작업에 걸린 시간도 나오니 그것을 참조로 maxWait 파라미터를 늘려주면 된다. 보통 10~15초로 주면 된다. GC 시간은 거의 99% 이상 1초 이내에 끝나야 하나보다.. 2. 무작위로 커넥션이 close 되요. 그건.. Connection 객체를 두 번 이상 close 했기 때문이다. DB Connection Pool은 close()를 호출할 때 정말로 닫는 것이 아니라, 단지 재사용할 수 있다고 표시만 할 뿐이다. 커넥션을 사용하다가 close()하면 다른 쓰레드이서 이 커넥션을 쓸 수 있는데, 이걸 현재 쓰레드에서 계속 잡고 있다가 또 다시 close()를 해버리면, 다른 쓰레드에서 사용중에 close()됐다고 나와 버리게 되는 거다. conn.close(); conn = null; 위와 같이 커넥션을 close()한 뒤에 바로 null 로 설정하여 절대로 다시 사용할 수 없게 만들면 이런 실수는 생기지 않을 것이다. |
검색어(web.xml)제거 |
- 저도 오라클 DB를 JNDI 사용해서 하는데 문제가 있었어 사용을 안하고 있습니다.
기존에 별도의 DB컨넥션 메니저나, 그냥 바로 디비를 연결해서
System.out.println(conn.getClass().getName());을 하면 (커넥션이 인터페이스가 어떤 클래스에서 구현이 되었는지 확인하는 것)
oracle.jdbc.driver.OracleConnection
이렇게 나옵니다.
그러나 JNDI를 이용해서 DBCP를 사용하면
System.out.println(conn.getClass().getName()); 출력했을 경우 아래와 같이 나옵니다.
org.apache.commons.dbcp.PoolingDataSource$PoolGuardConnectionWrapper
즉 오라클 커넥션 객체가 아니란 뜻이죠.
이렇게 되면 일반적인 동작을 다 잘 됩니다. 그런데 Oracle 만의 기능을 쓴다면 예를 들어 CLOB같은걸 때,
아니면 아래와 같이 오라클 클래스를 캐스팅 할때
ocstmt = (OracleCallableStatement)conn.prepareCall("{? = call " + qp.getQuery() + "}");
java.lang.ClassCastException 예외가 발생됩니다.
당연한 결과죠. 커넥션 인터페이스가 오라클(oracle.jdbc.driver.OracleConnection)에서 구현된것이 된것이 아니니..
저 이것때문에 저번 주말 삽질 했습니다.
혹시 이것 해결 할수 있는분 제발 답변좀...
※ 참고할만한 사이트 (http://forum.java.sun.com/thread.jspa?threadID=326031&messageID=1323008)
?F을 영어지만.. 내용을 보면 일반적인 위에 권남님께서 작성해준 코드형태로는 해결을 못하고
DelegatingConnection 객체를 이용해야된는데...... 이러면 oracle 개발해서 mssql, mysql로 넘길때 코드상에서 바꿔져야 한다는 문제점이 발생이 될것 같습니다. - 장정호
- 2005-03-15 22:30:01
- x
- DataSource를 Oracle에서 제공하는 걸로 사용하면 되지 않을까요?
DataSource 객체 설정은 WAS의 JNDI설정에서 하는 것이기 때문에 DataSource 객체를 각 DB 벤더의 것으로 설정하는 것이 클라이언트 사이드에서는 아무런 변경도 일으키지 않습니다. (헌데 정호님의 소스를 보면 오라클 전용 클래스로 캐스팅을 하므로, 어쨌든 다른 DB로 갈 때 소스 수정을 막을 수는 없겠군요)
이러한 기법이 Java Server Pages 3rd Edition의 517쪽 Chapter 24 Database Access Strategies 에 소개되어 있습니다. 매우 간단합니다.
하지만 제 자신이 테스트해보지 않아서 실제 어떤일이 일어날지는 모르겠습니다.
이 책은 eDonkey를 이용해서도 쉽게 구할 수 있습니다. ^^; - 권남
- 2005-03-15 23:24:20
- x
- Oracle 전용 JNDI 설정방법
http://www.microdeveloper.com/html/JNDI_Orcl_Tomcat.html
- 권남
- 2005-03-15 23:48:32
- x
- 감솨 합니다 ^^
- 장정호
- 2005-03-16 10:08:23
- x
- 컨텍스트를 리로드 했을때 별 문제 없나요?
여기 질답게시판에도 올렸는데... 컨텍스트를 리로드 하게되면 전에 사용하던 커넥션은 그냥 방치해둔 채로 새로이 커넥션을 받아오더군요...
이렇게 자꾸 컨텍스트 리로드를 하게되면 나중에는 디비에서 더이상의 커넥션을 제공 할 수 없어 에러가 나네요(이건 당연한현상..)
다른분들은 이런 문제가 없으신지...
- 재학
- 2005-03-16 16:14:54
- x
- org.apache.tomcat.dbcp.dbcp.SQLNestedException: Cannot create JDBC driver of class '' for connect URL 'null'
계속 이에러 나는데 왜 그런가요.. 4.x버전은 잘되는데.. 5.x버전은 도무지 어떻게 하는지 모르겠내요...
<Resource name="jdbc/snmp" auth="Container" type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="10000"
username="snmp" password="snmp" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/snmp?autoReconnect=true"/>이런식으로 하면 되는데...
&useUnicode=true&characterEncoding=euc-kr
&<-요거 쓰면 탐켓구동이 안되내요... 아시는분.... - 음..
- 2005-04-16 21:56:20
- x
- & => a m p;
로 변경해서 해보세요. - tohappy
- 2005-06-08 20:41:54
- x
'IT 세상 > 자바세상' 카테고리의 다른 글
DBCP 를 이용한 db프로그래밍 (0) | 2009.11.04 |
---|---|
jndi (0) | 2009.11.04 |
Io exception: NL Exception was generated (ibatis 설정) (0) | 2009.10.29 |
ibatis sqlMap 설정하기 (0) | 2009.10.27 |
자바 RMI (Remote Method Invovation) (0) | 2009.10.22 |