반응형

Commons-Digester


I. Digester ?

digester는 XML파일로 저장된 정보를 java 객체에 매핑해 주는 API입니다. 하지만 그 반대기능은 되지 않습니다.

보통은 XML파일을 parsing할때 SAX와 DOM을 이용하였지만 DOM은 성능이 좀 느리고, SAX는 DOM보다는 빠르지만 코드가 난잡해 집니다. digester는 SAX기반이지만 pattern 매칭으로 아주 쉽고 빠르게 XML 파일을 parsing합니다


원래 digester는 struts의 struts-config.xml 정보를 로딩하기위해 개발되어 struts의 한 부분이었는데 독립적으로 commons project로 분리되었습니다.


II. 준비물

digester를 사용하기 위해서는 다음 4가지 준비물이 필요합니다

commons-digester http://jakarta.apache.org/site/downloads/downloads_commons-digester.cgi

commons-beanutils http://jakarta.apache.org/site/downloads/downloads_commons-beanutils.cgi

commons-collections http://jakarta.apache.org/site/downloads/downloads_commons-collections.cgi

commons-logging http://jakarta.apache.org/site/downloads/downloads_commons-logging.cgi

만약 digester는 SAX API를 사용하기 때문에 jdk1.4 이상의 버젼이 아니라면 Xerces같은 XML parser가 필요합니다


아래예제를 실행하려면 dbcp 관련 파일도 필요하며 이는 Commons-dbcp 편을 참조하세요~


참고 사이트

commons-digester API http://jakarta.apache.org/commons/digester/commons-digester-1.6/docs/api/index.html

commons-digester Guide http://jakarta.apache.org/commons/digester/apidocs/org/apache/commons/digester/package-summary.html


III. 시작하기 전에..

보통 digester는 여러 설정값들을 xml에 정의해 놓고 이를 어플리케이션에서 로드하는 방식으로 많이 이용됩니다. 이 강좌에서도 데이터베이스 정보를 xml에 정의해 놓고 이를 로딩하여 데이터베이스에 연결하는 예제를 강의할 것입니다.

시작하기 전에 XML에 대한 어느정도 기본 지식이 필요합니다.


주요함수

다른 함수들도 많이만 가장 많이 사용되는 다음 4가지만 딱 눈으로 익히고 갑시다


-. addObjectCreate(element 경로, 자바빈크래스) : 어떤 element 경로를 자바빈클래스로 매칭?

-. addSetProperties(element 속성명, 자바빈프로퍼티명) : 어떤 element 속성을 자바빈 변수에 설정?

-. addBeanPropertySetter(element 경로, 자바빈프로퍼티명) : 어떤 element 경로를 자바빈 변수에 설정?

-. addSetNext(element 경로, 자바빈함수) : 어떤 element 경로를 자바빈 함수에?

대강은 이런 뜻으로 알고 넘어 갑쉬다~!

그럼 element 경로가 먼가요? 다음에 나옵니다


Element Matching Pattern

XML에 element들의 path를 pattern으로 인식하는 방법만 익힙시다.


<a>                 -- Matches pattern "a"
    <b>             -- Matches pattern "a/b"
        <c/>        -- Matches pattern "a/b/c"
        <c/>        -- Matches pattern "a/b/c"
    </b>
    <b>             -- Matches pattern "a/b"
        <c/>        -- Matches pattern "a/b/c"
        <c/>        -- Matches pattern "a/b/c"
        <c/>        -- Matches pattern "a/b/c"
    </b>
</a>


위 XML을 보면 element a가 최상위 루트 element 입니다.

이것은 "a"로 매칭되며 그다음 a의 서브 element b는 "a/b" 로 매칭합니다

그다음은 .. "a/b/c".. 

쉽죠?

즉 최상위만 "/"가 붙지 않으며 그 이하는 트리구조처럼 "/"를 붙여주면 됩니다


자 그럼 좀전에 보았던 함수들과 연관지어 보면..

...

digester.addObjectCreate("a/b", B.class);

digester.addBeanPropertySetter("a/b/c", "c");

...

요렇게 쓰입니다.


IV. Digester를 이용하여 데이터베이스 커넥션 정보를 DBCP로 멀티 설정하여 웹에서 사용해 보자!

자 이제 실질적인 예제를 봅시다~


무엇을 하려고 하려면 mysql과 oracle jdbc정보를 xml 파일에 기록해 두고 이를 딱 한번만 읽어서 이정보를 데이터베이스 커넥션풀인 dbcp에 설정할 것입니다


 XML 파일

다음과 같은 XML 파일이 있습니다. 이 파일은 mysql과 oracle을 연결하는 커넥션 정보를 가지고 있습니다

이 파일이름은 C:\Tomcat 5.0\webapps\ROOT\WEB-INF\classes\config.xml입니다

mysql과 oracle 두개의 jdbc pool을 dbcp로 설정할 것입니다.

이 파일은 제가 임의로 정해서 만든겁니다.


<?xml version="1.0" encoding="EUC-KR"?>


<connection-sources>
   <description>This script is a connection description</description>


   <JDBCConnectionPool name="mysql">
      <description>Mysql Connection Source</description>
      <defaultAutoCommit>true</defaultAutoCommit>
      <defaultReadOnly>false</defaultReadOnly>
      <driverClassName>org.gjt.mm.mysql.Driver</driverClassName>
      <maxActive>10</maxActive>
      <maxIdle>10</maxIdle>
      <maxWait>10000</maxWait>
      <username>unicorn</username>
      <password>iloveyou</password>
      <url>jdbc:mysql://localhost/unicorn</url>
   </JDBCConnectionPool>


   <JDBCConnectionPool name="oracle">
      <description>Oracle Connection Source</description>
      <defaultAutoCommit>true</defaultAutoCommit>
      <defaultReadOnly>false</defaultReadOnly>
      <driverClassName>oracle.jdbc.driver.OracleDriver</driverClassName>
      <maxActive>10</maxActive>
      <maxIdle>10</maxIdle>
      <maxWait>10000</maxWait>
      <username>unicorn</username>
      <password>iloveyoutoo</password>
      <url>jdbc:oracle:thin:@localhost:1521:unicorn</url>
   </JDBCConnectionPool>


</connection-sources>


web.xml

웹 배치파일에 의해 db.ConnectionInitialize.java를 초기 서블릿 컨테이너 로딩시 실행하여 XML정보를 DBCP로 세팅할 것입니다

config.xml 파일경로를 config 파라미터에 설정합니다


<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

  <servlet>
     <servlet-name>connectioninitialize</servlet-name>
     <servlet-class>db.ConnectionInitialize</servlet-class>
     <init-param>
        <param-name>config</param-name>
        <param-value>C:\Tomcat 5.0\webapps\ROOT\WEB-INF\classes\config.xml</param-value>
     </init-param>
     <load-on-startup>1</load-on-startup>
  </servlet>
   
</web-app>


db.jdbc.ConnectionSources.java

XML 파일에서도 보면 알수있듯이 connection-sources 서브요소인 description과 JDBCConnectionPool을 저장하는 객체입니다. 변수를 눈여겨 봅시다


package db.jdbc;

import java.util.HashMap;

public class ConnectionSources {

	private String description;
	private HashMap source = new HashMap();
	
	public void setDescription(String description) {
		this.description = description;
	}
	
	public void addSource(JDBCConnectionPool source) {
		this.source.put(source.getName(), source);
	}
	
	public String getDescription() {
		return this.description;
	}
	
	public JDBCConnectionPool getSource(String name) {
		return (JDBCConnectionPool)this.source.get(name);
	}
}

addSource 함수는 여러 JDBCConnectionPool 정보를 ConnectionSourcec 의 source에 저장합니다. 이 함수는 밑에서 다시 나오니 눈여겨 봅시다

 

db.jdbc.JDBCConnectionPool.java

변수에 대해 단순히 setter, getter로 이루어져 있습니다.

XML 파일을 보면JDBCConnectionPool 의 서브 element들을 저장하는 객체이며 XML파일과  이 java 변수명들과 매칭되는것을 알수 있을겁니다.

대강 감이 오나요? ㅡ.ㅡ? 감좌봐쓰~?


package db.jdbc;

public class JDBCConnectionPool {

	private String name;
	private String description;
	private boolean defaultAutoCommit;
	private boolean defaultReadOnly;
	private String driverClassName;
	private int maxActive;
	private int maxIdle;
	private int maxWait;
	private String username;
	private String password;
	private String url;
	
//for debug public void print() { String toString = "name : "+name+"\n"+ "description : "+description+"\n"+ "defaultAutoCommit : "+defaultAutoCommit+"\n"+ "defaultReadOnly : "+defaultReadOnly+"\n"+ "driverClassName : "+driverClassName+"\n"+ "maxActive : "+maxActive+"\n"+ "maxIdle : "+maxIdle+"\n"+ "maxWait : "+maxWait+"\n"+ "username : "+username+"\n"+ "password : "+password+"\n"+ "url : "+url; System.out.println(toString); } public void setName(String name) { this.name = name; } public void setDescription(String description) { this.description = description; } public void setDefaultAutoCommit(boolean defaultAutoCommit) { this.defaultAutoCommit = defaultAutoCommit; } public void setDefaultReadOnly(boolean defaultReadOnly) { this.defaultReadOnly = defaultReadOnly; } public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; } public void setMaxActive(int maxActive) { this.maxActive = maxActive; } public void setMaxIdle(int maxIdle) { this.maxIdle = maxIdle; } public void setMaxWait(int maxWait) { this.maxWait = maxWait; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public void setUrl(String url) { this.url = url; } public String getName() { return this.name; } public String getDescription() { return this.description; } public boolean getDefaultAutoCommit() { return this.defaultAutoCommit; } public boolean getDefaultReadOnly() { return this.defaultReadOnly; } public String getDriverClassName() { return this.driverClassName; } public int getMaxActive() { return this.maxActive; } public int getMaxIdle() { return this.maxIdle; } public int getMaxWait() { return this.maxWait; } public String getUsername() { return this.username; } public String getPassword() { return this.password; } public String getUrl() { return this.url; } }

db.ConnectionInitialize.java

자 이제 여기가 핵심 클래스입니다. 위부분이 모두이해가 되었으면 다음 소스코드를 살펴봅시다

web.xml에서 정의한 config.xml을 로딩하여 파싱하고 그 정보를 DBCP에 설정합니다

mysql과 oracle 두개의 jdbc를 설정하도록 xml에 정의하였었습니다.


package db;

import java.sql.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.commons.dbcp.ConnectionFactory;
import org.apache.commons.dbcp.PoolingDriver;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;

import org.apache.commons.digester.Digester;
import java.io.*;
import org.xml.sax.SAXException;

import db.jdbc.ConnectionSources;
import db.jdbc.JDBCConnectionPool;

public class ConnectionInitialize extends HttpServlet {

	public void init() throws ServletException {

		String config = null;
		// web.xml의 배치스크립트의 파라미터이름을 가져온다
		Enumeration names = getServletConfig().getInitParameterNames();
		do {
			if(!names.hasMoreElements())
			break;
			String name = (String)names.nextElement();
			String value = getServletConfig().getInitParameter(name).trim();

			System.out.println(name+" : "+value);

			// config에 정의된 XML 파일을 가져온다
			if (name.startsWith("config"))
 				config = value;
		} while(true);

		//Digester를 생성하고
		Digester digester = new Digester();
		//XML 유효성을 검증할것인가?
		digester.setValidating(false);

		//connection-sources 요소를 ConnectionSources.class 객체에 저장하겠다
		digester.addObjectCreate("connection-sources",
		                          ConnectionSources.class);

		//connection-sources/description 요소를
		//ConnectionSources.class의 description 변수에 저장하겠다
		digester.addBeanPropertySetter("connection-sources/description",
						"description");

		//connection-sources/JDBCConnectionPool 요소를 JDBCConnectionPool.class 객체에저장하겠다
		digester.addObjectCreate("connection-sources/JDBCConnectionPool",
		                         db.jdbc.JDBCConnectionPool.class);

		//connection-sources/JDBCConnectionPool 요소의 name 속성을
		//JDBCConnectionPool.class의 name 변수에 저장하겠다
		digester.addSetProperties("connection-sources/JDBCConnectionPool", "name", "name");

		//connection-sources/JDBCConnectionPool/description 을
		//JDBCConnectionPool.class 객에의 description 변수에 저장하겠다
		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/description",
                                       "description");

		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/defaultAutoCommit",
                                       "defaultAutoCommit");
		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/defaultReadOnly",
                                       "defaultReadOnly");
		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/driverClassName",
                                       "driverClassName");
		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/maxActive",
                                       "maxActive");
		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/maxIdle",
                                       "maxIdle");
		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/maxWait",
                                       "maxWait");
		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/username",
                                       "username");
		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/password",
                                       "password");
		digester.addBeanPropertySetter("connection-sources/JDBCConnectionPool/url", "url");

		//connection-sources/JDBCConnectionPool을
		//ConnectionResources.class의 addSource를 이용하여 반복적으로 추가하겠다
		digester.addSetNext("connection-sources/JDBCConnectionPool", "addSource");

		try {
			File file = new File(config);
			//digester 파싱 및 결과 리턴
			ConnectionSources connectionSources =
                                                             (ConnectionSources)digester.parse(file);

			System.out.println(connectionSources.getDescription());
			JDBCConnectionPool mysql = connectionSources.getSource("mysql");
			mysql.print(); //디버깅
			JDBCConnectionPool oracle = connectionSources.getSource("oracle");
			oracle.print(); //디버깅

			//이부분 이하는 Commons-dbcp 부분을 참조하기 바란다
			setupDriver(mysql.getName(),
				mysql.getDriverClassName(),
				mysql.getUrl(),
				mysql.getUsername(),
				mysql.getPassword(),
				mysql.getDefaultAutoCommit(),
				mysql.getDefaultReadOnly(),
				mysql.getMaxActive(),
				mysql.getMaxIdle(),
				mysql.getMaxWait());

			setupDriver(oracle.getName(),
				oracle.getDriverClassName(),
				oracle.getUrl(),
				oracle.getUsername(),
				oracle.getPassword(),
				oracle.getDefaultAutoCommit(),
				oracle.getDefaultReadOnly(),
				oracle.getMaxActive(),
				oracle.getMaxIdle(),
				oracle.getMaxWait());

		} catch (FileNotFoundException filenotfoundexception) {
			System.out.println("Config file not found");
			filenotfoundexception.printStackTrace();
		} catch (Exception exception) {
			exception.printStackTrace();
		}
	}

	public void setupDriver(String poolName,
			String driverClassName,
			String url,
			String username,
			String password,
			boolean defaultAutoCommit,
			boolean defaultReadOnly,
			int maxActive,
			int maxIdle,
			long maxWait) throws Exception {
		try {
		    Class.forName(driverClassName);
		} catch (ClassNotFoundException classnotfoundexception) {
			System.out.println(driverClassName+" is not found");
		    classnotfoundexception.printStackTrace();
		    throw classnotfoundexception;
		}
		
		GenericObjectPool connectionPool = new GenericObjectPool(null);
		connectionPool.setMaxActive(maxActive);
		connectionPool.setMaxIdle(maxIdle);
		connectionPool.setMaxWait(maxWait);
		
		ConnectionFactory connectionFactory
			= new DriverManagerConnectionFactory(url, username, password);
		
		PoolableConnectionFactory poolableConnectionFactory
			= new PoolableConnectionFactory(connectionFactory,
		                                   connectionPool,
		                                   null,
		                                   null,
		                                   defaultReadOnly,
		                                   defaultAutoCommit);
		
		Class.forName("org.apache.commons.dbcp.PoolingDriver");
		PoolingDriver driver = (PoolingDriver) DriverManager.getDriver("jdbc:apache:commons:dbcp:");
		
		driver.registerPool(poolName,connectionPool);    
	}
}

그럼 이렇게 설정한 DBCP는 어떻게 사용할까요?

이전강좌에서 보았던 ConnectionContext.java와 ConnectionResource.java를 다시 사용해봅시다

package db;

public interface ConnectionContext {
	public java.sql.Connection getConnection();
	public void rollback();
	public void commit();
	public void release();
}

ConectionContext를 구현한 ConnectionResource는 풀이름을 받아 처리하는것으로 수정하였습니다

package db;

import java.sql.Connection;
import java.sql.DriverManager;
import javax.sql.DataSource;

public class ConnectionResource implements ConnectionContext {
	private Connection connection = null;
	private boolean transaction = false;

    public ConnectionResource(String poolName) throws Exception {
    	init(false, poolName);	
    }
    
    public ConnectionResource(boolean transaction, String poolName) throws Exception {
    	init(transaction, poolName);
    }
    
    public void init(boolean transaction, String poolName) throws Exception {
    	this.transaction = transaction;
    	connection = DriverManager.getConnection("jdbc:apache:commons:dbcp:"+poolName);
if (transaction) connection.setAutoCommit(false); if (connection == null) throw new Exception("fail to get connection"); } public Connection getConnection() { return connection; } public void rollback() { if (transaction) { if (connection != null) try { connection.rollback(); } catch (Exception e) {} } } public void commit() { if (transaction) { if (connection != null) try { connection.commit(); } catch (Exception e) {} } } public void release() { { if (connection != null) { if (transaction) { try { connection.setAutoCommit(true); } catch (Exception e) {} } try { connection.close(); } catch (Exception e) {} }    } }

jsp 파일

마지막으로 ConnectionContext는 풀이름과 함께 사용하면 되겠네요

<%@ page contentType="text/html;charset=EUC_KR" %>
<%@ page import="java.sql.*,db.*" %>

<%

ConnectionContext mysqlContext = new ConnectionResource("mysql");
// 혹은 ConnectionContext oracleContext = new ConnectionResource("oracle");


Connection connection = null;


try {


    connection = mysqlContext.getConnection();
    out.println(connection);


} catch (Exception exception) {
    exception.printStackTrace();
} finally {
    if (connection != null) try { mysqlContext.release(); } catch (Exception exception) {}
}

%>


DBCP 부분은 이전 강좌인 Commons-dbcp (http://www.jakartaproject.com/article/jakarta/1111890409958 )부분을 참조하세요~


ps. 휴 =3 다썼당 ^_^

     흠.. 쉽게쓴다고 이리 저리 하루종일 썼는데 설명이 많이 부족한것 같네요

     그밖에 사항은 알아서 찾아보기~!

     digester II탄에서는 xml 설정파일로부터 규칙을 읽어 xml을 파싱하는 방법을 알아보겠습니다~


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

본문서는 자유롭게 배포/복사 할수 있지만

이문서의 저자에 대한 언급을 삭제하시면 안됩니다

저자 : GoodBug (unicorn@jakartaproject.com)

최초 : http://www.jakartaproject.com 

Posted by 1010