spring로 검색한 결과 :: 시소커뮤니티[SSISO Community]
 
SSISO 카페 SSISO Source SSISO 구직 SSISO 쇼핑몰 SSISO 맛집
추천검색어 : JUnit   Log4j   ajax   spring   struts   struts-config.xml   Synchronized   책정보   Ajax 마스터하기   우측부분

회원가입 I 비밀번호 찾기


SSISO Community검색
SSISO Community메뉴
[카페목록보기]
[블로그등록하기]  
[블로그리스트]  
SSISO Community카페
블로그 카테고리
정치 경제
문화 칼럼
비디오게임 스포츠
핫이슈 TV
포토 온라인게임
PC게임 에뮬게임
라이프 사람들
유머 만화애니
방송 1
1 1
1 1
1 1
1 1
1

spring로 검색한 결과
등록일:2008-06-11 10:39:26
작성자:
제목:Spring Framework를 이용한 Oracle JDBC 액세스의 개선


spring Framework를 이용한 Oracle JDBC 액세스의 개선
저자 Dustin Marx

spring Framework를 이용하여 Oracle JDBC 액세스를 개선하는 방법을 알아봅니다.

아티클 관련 다운로드:
Oracle Database 10g
Oracle JDBC Drivers
spring Framework

JDBC는 관계형 데이터베이스 접근을 위해 널리 사용되는 표준 데이터 액세스 프로토콜입니다. JDBC의 가장 중요한 장점의 하나로 표준화된 API를 꼽을 수 있습니다. 표준화된 API를 이용하면 Java 기반 데이터 접근 애플리케이션의 이식성을 보장할 수 있기 때문입니다. JDBC는 표준 Java (J2SE) 및 엔터프라이즈 Java (J2EE) 환경을 위한 핵심 기술로써 Java의 초창기 시절부터 사용되어 왔습니다.

JDBC의 다양한 장점 덕분에, JDBC는 JSSE 및 J2EE 애플리케이션에서 매우 중요한 역할을 담당하고 있습니다. 하지만 우리가 기대 수준에 못 미치는 몇 가지 특성도 존재하며, 이 때문에 JDBC 기반의 개발작업에서 반복적이고, 때로는 짜증스럽기까지 한 상황이 발생하기도 합니다. 이러한 이유로 SQLExecutor, Apache Jakarta Commons DBUtils와 같은 JDBC 추상화 프레임워크(JDBC abstraction framework)가 발표되고, 기업 내부적으로 다양한 JDBC 애플리케이션 프레임워크가 개발되고 있기도 합니다. spring Framework는 그 중에서도 가장 널리 알려진 JDBC 추상화 프레임워크입니다.

spring Framework는 Apache 라이센스를 통해 공개된 Java/J2EE 애플리케이션 프레임워크로 J2EE 애플리케이션의 다양한 계층을 지원하고 있습니다. spring Framework의 중요한 장점의 하나로 JDBC 데이터 액세스에 관련하여 뛰어난 유지보수성과 안정성을 지원한다는 점을 들 수 있습니다. 이 문서에서는 spring Framework와 Oracle TopLink 오브젝트/관계형(O/R) 매핑 툴을 이용하여 JDBC 코드를 작성하는데 수반되는 반복적 요소와 리스크를 줄이는 방법을 설명합니다. 개발자는 spring Framework를 이용하여 오라클 데이터베이스 기반 환경에서 보다 깔끔하고, 유연하고, 에러 가능성이 적은 JDBC 코드를 작성할 수 있습니다.

데이터베이스 리소스에 대한 연결의 해제

JDBC 코딩 과정에서 연결을 정상적으로 해제하지 않는 실수가 종종 발생하곤 합니다. 그 결과로 데이터베이스 리소스의 할당에 문제가 발생할 수 있습니다. 마찬가지로 결과 셋과 구문에 대한 해제(close) 작업 역시 필요하며, 이는 일반적으로 권장되는 관행이기도 합니다. 코드의 실행이 비정상적으로 종료되는 상황에서도 이러한 해제 작업이 정상적으로 이루어지도록 하기 위한 코딩의 예를 아래 Listing 1의 finally 블록에서 확인하실 수 있습니다.

Listing 1

try
{
  // JDBC Connection/Statement/Result Set
}
catch (SQLException sqlEx)
{
  // Handle the exception
}
finally
{
  try
  { // Closing connection *should* close statement and result set
   if (stmt != null) stmt.close();
   if (conn != null) conn.close();
  }
  catch (SQLException sqlEx)
  {
   System.err.println("SQLException NOT handled");
  }
}
위 예제에서 finally 블록은 데이터베이스 연결 및 구문이 정상적으로 해제되었음을 보장합니다. 이와 같은 메커니즘을 이용하여 연결 해제를 관리하는 경우, 코드가 길어지고 반복되는 현상이 나타날 수 밖에 없습니다. spring Framework는 연결 관리 및 리소스 관리를 개발자의 관점에서 추상화하고, 보다 일관적인 리소스 관리 및 코드 가독성의 향상을 가능하게 합니다.

spring 코드 예제 – 첫 번째

Listing 2의 JDBC 코드는 여러분이 잘 알고 계신 scott/tiger 스키마에서 직원의 커미션을 조회하는 예제입니다. 이 경우에도, 실제로 데이터베이스를 쿼리하기 위한 코드 이외에도 많은 양의 “반복적인” 코드가 함께 작성되어야 합니다.

Listing 2

List commissions = new ArrayList();
Statement stmt = null;
ResultSet rs = null;
try
{
  stmt = this.myConnection.createStatement();
  rs = stmt.executeQuery("SELECT comm FROM emp"); 
  while ( rs.next() )
  {
   Integer commission = new Integer( rs.getInt("COMM") );
   if ( rs.wasNull() )
   {
     // By assigning the commission to null, this effectively
     // represents a null in the database as a Java null.
     System.out.println( "\tCommission seen as " + commission +
               " is really null");
     commission = null;
   }
   commissions.add( commission );
  }
}
catch (SQLException sqlEx)  // checked
{
  System.err.println( "Message: " + sqlEx.getMessage() );
  System.err.println( "Error Code: " + sqlEx.getErrorCode() );
  System.err.println( "SQL State: " + sqlEx.getSQLState() );
}
finally
{
  try
  {
   if ( rs != null ) { rs.close(); }
   if ( stmt != null ) { stmt.close(); }
  }
  catch (SQLException sqlEx) // checked
  {
   System.err.println( sqlEx.getMessage() );
  }
}
Listing 3은 spring Framework를 이용하여 Listing 2와 유사한 기능을 구현한 예를 보여주고 있습니다.

Listing 3

List commissions = new ArrayList();
try
{
  JdbcTemplate jt = new JdbcTemplate(this.myDataSource);
  List commList = jt.queryForList( "SELECT comm FROM emp");
  
  Iterator commIter = commList.iterator();
  while ( commIter.hasNext() )
  {
   Number comm = (Number) ((Map) commIter.next()).get("COMM");
   if (comm != null)
     commissions.add( new Integer(comm.intValue()) );
   else commissions.add( null );  } 
}
catch ( DataAccessException ex )  // unchecked exception
{
  System.err.println( ex.getMessage() );
}
위 예제를 통해, spring Framework를 사용하는 경우 순수 JDBC 코딩에 비해 훨씬 적은 양의 코딩만으로도 동일한 기능을 구현할 수 있음을 확인할 수 있습니다. Listing 3에서 볼 수 있듯, (연결, 구문, 결과 셋 등의) 리소스 관리를 위한 코드를 별도로 작성하고 유지할 필요가 없습니다. Listing 3 예제에 포함된 소량의 예외 처리 코드도 반드시 필요한 것은 아닙니다. DataAccessException는 체크되지 않는 예외사항(unchecked exception)입니다. 커미션 조회 결과를 반환하는 과정에서 Number 타입이 사용되기 때문에 ResultSet의 wasNull 메소드를 명시적으로 호출할 필요가 없습니다. 실제로, Listing 3에서는 ResultSet 신택스가 전혀 사용되고 있지도 않습니다.

Listing 3에는 또 spring Framework의 JDBC 지원을 위해 사용되는 가장 기본적인 클래스의 하나인 JdbcTemplate이 사용되고 있습니다. 예제에서 JdbcTemplate 클래스의 인스턴스는 데이터 소스와 함께 인스턴스화(instantiate) 되며, 그런 다음 overriden된 queryForList 메소드가 제공된 SQL 구문과 함께 템플릿 클래스에서 호출됩니다. queryForList 메소드는 HashMap의 ArrayList를 반환합니다. 여기서 ArrayList의 각 엘리먼트는 반환된 데이터 로우로 구성되며, 어레이 리스트 엘리먼트의 각 맵 엔트리는 해당 로우의 컬럼 값을 의미합니다.

JdbcTemplate이 제공하는 다양한 overriden된 버전의 queryForList 메소드를 사용하면 다수의 로우에 대한 쿼리가 가능합니다. 또 JdbcTemplate 클래스는 queryForInt (하나의 integer를 반환), queryForLong (return a single long), query, update, 등의 다양한 메소드 버전을 제공합니다. spring Framework가 제공하는 Javadoc 기반 API 문서의 "Method Detail" 부분을 참고하면 overridden 메소드에 대한 자세한 정보를 얻을 수 있습니다. 각각의 메소드는 사용하는 구문의 유형 (Statement,PreparedStatement 등), 지원되는 기능 면에서 차이를 갖습니다. JdbcTemplate은 그 밖에도 (JDBC에 관련한 일정 수준의 지식이 요구되는) 메소드들을 제공하며, 이 메소드를 이용하여 보다 높은 유연성을 확보하는 것이 가능합니다. (이러한 유형의 메소드에 대해서는 뒷부분에서 설명합니다.)

JDBC Exception 핸들링

다시 Listing 1으로 돌아가 봅시다. 예제 코드에서 명시적으로 처리되는 exception은 java.sql.SQLException이 유일함을 확인할 수 있습니다. SQLException은 데이터베이스 및 SQL에 관련한 다양한 예외 상황을 처리하기 위한 exception입니다. Javadoc은 SQLException 인스턴스로부터 가져올 수 있는 기본적인 정보에 대해 설명하고 있습니다. 이 정보에는 에러의 내용을 기술하는 문자열[getMessage()], 표준화된 SQLState exception String [getSQLState()], 벤더별 정수 에러 코드 [getErrorCode()]등이 포함됩니다. 이 세 가지 정보는 Listing 1에서 모두 사용되고 있습니다.

SQLException은 (java.lang.Exception을 직접적으로 확장한) checked exception입니다. Java의 checked exception은 Java 커뮤니티에서 열띤 논란의 주제가 되고 있으며, exception이 애플리케이션 내에서 처리될 수 있는 경우에만 checked exception을 사용해야 한다는 것이 일반적인 관례로 굳어지고 있습니다. 애플리케이션에서 의미 있는 형태로 처리하기 어려운 exception이라면 unchecked로 처리되는 것이 바람직합니다. SQLException은 checked exception이며, 따라서 애플리케이션 내에서 예외 처리를 수행하거나, 호출 코드로 명시적으로 전달하는 방법을 통해 애플리케이션 코드에서 직접 처리해야 합니다.

SQLException은 SQL 구문을 사용하는 관계형 데이터 소스의 종류에 따라 달라질 수 있습니다. 따라서 데이터 저장소 유형과 언어로부터 독립적인, 진정한 의미의 Data Access Objects (DAO) 호환성이 보장되지 않는다는 문제가 있습니다.

spring Framework의 SQLException 처리 방식은 JDBC 개발/유지보수의 관점에서 볼 때 매우 유용한 기능으로 꼽힙니다. spring Framework는 SQLException를 추상화하고, DAO 친화적인 unchecked exception의 계위(hierarchy)를 제공합니다.

(Oracle JDeveloper 10g의 UML 모델링 툴을 통해 생성한) 그림 1은 JDBC 및 DAO를 위한 spring Framework의 exception 클래스 중 중요한 몇 가지를 보여주고 있습니다. 그림 1에서 보여지는 모든 클래스는 org.springframework 패키지 내의 서브패키지에 포함되어 있습니다. JDBC 관련 exception 핸들링 클래스는 jdbc 서브패키지에, DAO 관련 exception 핸들링 클래스는 dao 서브패키지에 포함됩니다.

figure 1

벤더별 에러 코드의 처리

위에서 설명한 것처럼, 표준 SQLException 은 표준적인 정보 (SQLState)와 벤더별 에러 정보 (ErrorCode)를 모두 포함하고 있습니다. 다른 대부분의 데이터베이스와 JDBC 드라이버와 마찬가지로, 오라클 데이터베이스와 오라클 JDBC 드라이버는 SQLException 표준적인 SQLState 컴포넌트에 비해 훨씬 자세한 벤더별 에러 정보를 제공하고 있습니다.

그 한 가지 예로 SQLState 코드 42000 (일반적인 신택스 에러 또는 액세스 문제를 의미)를 들 수 있습니다. SQLException의 42000 SQLState 코드값에 대응되는, Oracle JDBC 드라이버의 에러 코드는 900 ("invalid SQL statement"), 903 ("invalid table name"), 904 ("invalid identifier"), 911 ("invalid character"), 936 ("missing expression") 등 매우 다양합니다. 뿐만 아니라 (오라클 데이터베이스가 아닌) Oracle JDBC 드라이버 내부에서 발생하는 에러의 경우에는 아예 대응되는 SQLState 코드가 존재하지 않습니다.

Oracle Database에서 발생되는 에러와 Oracle JDBC 드라이버에서 발생되는 에러를 구분하는 것이 중요한 경우가 있습니다. 예를 들어, 903 ("invalid table name") 에러 코드는 오라클 데이터베이스 에러 코드 ORA-00903에 대응됩니다. 또 17003 ("invalid column index") 코드는 Oracle JDBC 드라이버 에러코드 ORA-17003에 대응됩니다. “ORA-”라는 접두어에서 미루어 짐작할 수 있듯, 이 두 가지 유형(데이터베이스, JDBC 드라이버)의 에러 코드는 오라클 환경에서만 사용됩니다. Oracle JDBC 드라이버에서 발생하는 에러에 대해서는 대응되는 SQLState 코드가 존재하지 않기 때문에, JDBC 드라이버가 원인이 되는 에러에 대처하기 위해서는 오라클 에러 코드가 반드시 필요합니다.

오라클 데이터베이스에 액세스하는 JDBC 드라이버 관련 에러의 대표적인 예가 표 1에 정리되어 있습니다. "Example Code and/or Comment" 컬럼은 에러의 원인이 되는 SQL 구문의 예와 추가적인 설명을 포함하고 있습니다.

Table 1

Error Label Oracle Error SQLState Example Code and/or Comment

SQL-related errors based on variations of statement: SELECT ename FROM emp

"unique constraint" 1 2300 예: 프라이머리 키 제약조건 위반

"resource busy and acquire with NOWAIT specified"

54 61000 NOWAIT이 명시된 경우에만 발생

"invalid SQL statement"

900 42000

ename FROM emp

"invalid table name"

903 42000

SELECT ename FROM

"invalid identifier"

904 42000

SELECT empname FROM emp

"invalid character"

911 42000

SELECT ename FROM emp;

"missing column"

917 42000

예: INSERT 구문의 컬럼 구분을 위한 쉼표 기호가 누락된 경우.

"FROM keyword not found where expected"

923 42000

SELECT ename emp

"missing expression"

936 42000

SELECT FROM emp

"table or view does not exist"

942 42000

SELECT ename FROM empp

"cannot insert null into"

1400 23000 NOT NULL 제약조건을 갖는 컬럼에 NULL 값을 삽입하려 시도하는 경우

"value larger than specified precision allows for this column"

1438 22003 컬럼이 허용하는 정밀도(precision)을 초과하는 수를 삽입하려 시도하는 경우

"invalid number"

1722 42000 문자에 대해 numeric 함수를 적용하려 시도한 경우

"integrity constraint failed"

2291 23000 기존 프라이머리 키와 일치하지 않는 외래 키를 삽입하려 시도하는 경우

"value too large for column"

12899 72000 컬럼이 허용하는 것보다 큰 값(예: 너무 많은 수의 문자)을 삽입하려 시도하는 경우

"Io exception"

17002 none Oracle JDBC 드라이버에서 발생한 에러에 대응되는 SQLState 코드가 존재하지 않는 경우

"Invalid column index"

17003 none


"Invalid column name"

17006 none


"Numeric Overflow"

17026 none


본 문서의 마지막 부분의 “온라인 리소스” 섹션에는 사용자가 마주칠 수 있는 다양한 오라클 데이터베이스 exception에 대한 상세한 정보를 제공하는 웹 사이트 링크가 소개되어 있습니다. Oracle JDBC 드라이버 에러 코드는 Oracle JDBC Developer's Guide and Reference의 Appendix B 에서 참고하실 수 있으며 Oracle Database Error Messages 문서는 오라클 데이터베이스 에러 코드에 대한 일반적인 정보를 담고 있습니다.

spring Framework는 표준 기반 SQLState와 벤더별 에러 코드를 모두 지원합니다. spring Framework의 벤더별 에러 코드 지원은, 각 데이터베이스와의 느슨한 커플링(loose coupling)을 통해 구현됩니다. spring Framework는 XML 구성 파일을 이용하여 JDBC에서 자주 발생하는 벤더별 에러에 대한 링크를 제공합니다. spring Framework의 sql-error-codes.xml 구성 파일에 포함된 오라클 데이터베이스 관련 설정이 Listing 4와 같습니다 (다른 데이터베이스에 관련된 설정 항목은 Listing 4에 포함되어 있지 않습니다.)

Listing 4

<bean id="Oracle”
   class="org.springframework.jdbc.support.SQLErrorCodes">
 <property name="badSqlGrammarCodes">
  <value>900,903,904,917,936,942,17006</value>
 </property>
 <property name="invalidResultSetAccessCodes">
  <value>17003</value>
 </property>
 <property name="dataAccessResourceFailureCodes">
  <value>17002</value>
 </property>
 <property name="dataIntegrityViolationCodes">
  <value>1,1400,1722,2291</value>
 </property>
 <property name="cannotAcquireLockCodes">
  <value>54</value>
 </property>
</bean>
sql-error-codes.xml에서 쉼표로 구분된 정수들은 앞에서 설명한 벤더별 에러 코드의 숫자를 의미합니다. 특히 "badSqlGrammarCodes" 카테고리에 표시된 숫자의 대부분이 표 1에서 설명되고 있습니다. 17006은 “invalid column name”을 의미하는 JDBC 드라이버 에러 코드입니다. Listing 4의 property 태그에서 name 속성은 각각의 에러 코드를 처리하기 위해 사용되는 exception의 타입을 지정하는 용도로 사용됩니다. 예를 들어, 917 (ORA-00917) 에러가 발생한 경우 spring Framework는 unchecked BadSqlGrammarException을 발생시키게 됩니다. 이러한 설정이 코드 외부에 XML 파일의 형태로 존재하기 때문에, 특정 벤더 에러 코드에 대해 대응되는 JDBC exception을 발생시키도록 설정을 변경하기가 매우 용이하다는 장점이 있습니다.

데이터베이스의 에러 코드 별로 대응되는 exception을 설정하는 것이 바람직한 이유에는 여러 가지가 있습니다. 먼저, (어차피 런타임에서 코드를 처리하는 것에는 한계가 있기 때문에) SQLException이 발생하는 상황 중 일부에 대해서만 exception 처리를 수행하기를 원할 수 있습니다. spring Framework는 데이터베이스 개발자를 위한 정교한 exception hierarchy와 데이터베이스 및 exception 간에 느슨하게 커플링된 연결된 관계를 제공함으로써 특정 exception만을 처리하고, 런타임에 처리가 불가능한 unchecked exception을 무시할 수 있습니다.

BadSqlGrammarExceptionspring이 JDBC 환경을 위해 지원하는 유용한 exception 클래스의 하나입니다. 이 exception 클래스가 제공하는 getSql() 메소드는 exception이 발생한 시점에 호출된 SQL 구문을 반환합니다. 이 클래스가 (일반적인 DAO 클래스와 달리) SQL 구문에 관련한 상세 정보를 갖고 있기 때문에 getSQLException() 메소드를 통한 표준 SQLException 으로의 핸들로써 이용될 수도 있습니다.

Bsql-error-codes.xml파일에 오라클 에러 코드를 추가하고 기존 spring exception 클래스로 매핑하는 대신, 커스텀 exception 핸들링 클래스를 생성하는 방법을 사용할 수도 있습니다. 그런 다음, 커스텀 SQLExceptionTranslator 클래스를 이용하여 생성된 커스텀 exception 핸들링 클래스와 Oracle 에러 코드를 연결하게 됩니다.

spring에서 PreparedStatement 사용하기

Listing 3spring 코드 예제는 실행된 SQL의 Statement에 대한 spring의 래핑(wrapping) 기능에 의존하고 있습니다. 하지만, 데이터베이스에 대해 SQL을 실행하는 과정에서 Statement 대신 PreparedStatement가 사용될 수도 있습니다. spring JdbcTemplate 클래스는 StatementPreparedStatement를 모두 지원하는 다양한 메소드를 제공하고 있으며, 사용자는 자신이 사용하고자 하는 JDBC 구문의 유형을 선택할 수 있습니다.

spring의 Javadoc 기반 API 문서는 각 메소드의 Statement 또는 PreparedStatement 지원 여부를 명시하고 있습니다. 또 SQL 매개변수의 전달 여부에 따라 JdbcTemplate에서 사용되는 구문의 유형을 구분하는 것도 가능합니다. SQL 구문의 문자열만이 전달되는 경우, 이 메소드는 Statement를 사용하는 것으로 간주될 수 있습니다. 메소드가 SQL 구문과 별개로 매개변수를 사용하는 경우, 이 메소드는 PreparedStatement를 사용하는 것으로 간주됩니다. 다음에 소개할 두 개의 예제 코드(Listing 5와 Listing 6)는 PreparedStatement를 이용한 표준 JDBC 액세스와 PreparedStatement를 래핑한 spring 기반 액세스를 각각 보여주고 있습니다.

Listing 5

String updateStr =
  "UPDATE salgrade SET losal = ?, hisal = ? WHERE grade = ?";
PreparedStatement stmt = null;
   
try
{
  stmt = this.myConnection.prepareStatement(updateStr);
  stmt.setInt(1,aLowSal);
  stmt.setInt(2,aHighSal);
  stmt.setInt(3,aSalGrade);
     
  updateStatus = stmt.execute();
  stmt.close();
} // lots of catch and finally code typically follows here

 

Listing 6
String updateStr =
  "UPDATE salgrade SET losal = ?, hisal = ? WHERE grade = ?";

JdbcTemplate jt = new JdbcTemplate(this.myDataSource);
jt.update( updateStr,
      new Object[] { new Integer(aLowSal),
             new Integer(aHighSal),
             new Integer(aSalGrade) } );
// No handling/closing of PreparedStatement or catch/finally needed
Listing 6는 spring Framework를 이용하여 데이터베이스를 업데이트하는 예를 보여주고 있습니다. 코드 내에서 "PreparedStatement"가 직접 명시되지는 않고 있지만,JdbcTemplate's의 update 메소드는 실제로 PreparedStatement를 사용하고 있습니다. JDBC 표준은 발생된 SQLException를 캡처할 것을, 그리고 구문을 블록으로 처리할 것을 요구하고 있습니다. Listing 6에서 확인할 수 있듯, spring 기반 코드에서는 이러한 요구사항이 존재하지 않습니다.

Listing 6에서 PreparedStatement가 명시적으로 사용되지 않고 있음을 참고하시기 바랍니다. JdbcTemplate update 메소드를 사용하는 개발자는 PreparedStatement의 자세한 사용법, API, 또는 SQLException에 대해 알고 있을 필요가 없습니다. 개발자는 “anonymous”한 형태의 inner 클래스를 사용하여 SQL 구문과 함께 JdbcTemplate.update 메소드를 전달하고 있습니다.

Oracle-Specific SQL의 사용

spring Framework는 JDBC 개발자들이 일상적이고 반복적으로 수행하는 코딩 작업에 대해 “래핑(wrapping)” 기능을 제공하지만, 반면 비표준적인 SQL/JDBC가 필요한 경우에는 이를 충분히 활용할 수 있는 환경을 제공합니다. 코드가 완벽하게 표준화된 환경이 바람직할 수도 있겠지만, 경우에 따라서는 특정 벤더가 제공하는 비표준적인 기능이 매우 유용하게 사용될 수도 있습니다.

오라클 환경의 경우, ROWID를 이용하여 오라클 테이블의 로우(row)를 지정하는 기능을 그 예로 들 수 있습니다. Listing 7과 8은 scott/tiger EMP 테이블에서 employee number를 기준으로 ROWID를 가져오기 위한 고전적 JDBC 코드와 spring 기반 JDBC 코드를 보여주고 있습니다. 두 가지 경우 모두, ROWID는 String의 형태로 반환됩니다.

Listing 7

String queryStr = "SELECT rowid FROM emp WHERE empno = "
         + aEmpNum;  // aEmpNum set previously
String rowId = null;
try
{
  stmt = this.myConnection.createStatement();
  rs = stmt.executeQuery(queryStr);
  while ( rs.next() )
  {
   rowId = rs.getString("ROWID");
  }
} // lots of catch-finally code needed after this

 

Listing 8
String queryStr = "SELECT rowid FROM emp WHERE empno = "
         + aEmpNum;
String rowId = null;
  
try
{
  JdbcTemplate jt = new JdbcTemplate(this.myDataSource);
  oracle.sql.ROWID oraRowId =
   (ROWID) jt.queryForObject(queryStr, ROWID.class);
  rowId = oraRowId.stringValue();
}
catch ( IncorrectResultSizeDataAccessException wrongSizeEx )
{
  // This unchecked exception is thrown in this case if more
  // than one result is returned from the query.
  // Explicitly printing out the results of this exception's
  // methods getExpectedSize() and getActualSize() is really not
  // necessary in this case because this exception's getMessage()
  // returns this same information in sentence form. 
  System.err.println( wrongSizeEx.getMessage() );
  System.err.print( "Expected " + wrongSizeEx.getExpectedSize()
          + " results, but actually got back "
          + wrongSizeEx.getActualSize() + " results.");
}
Listing 8를 통해 spring Framework이 오라클이 사용하는 비표준적 키워드를 유연하게 지원하고 있음을 확인할 수 있습니다. 또 Listing 8에서는, spring의 DAO exception이 활용되고 있습니다. queryStr이 전체 ROWID를 한꺼번에 반환하는 경우 IncorrectResultSizeDataAccessException이 발생됩니다.

Oracle에서 사용되는 비표준적 SQL의 가장 잘 알려진 예로 “SELECT sysdate FROM dual”을 들 수 있을 것입니다 (이 쿼리는 ANSI 표준에는 포함되어 있지 않습니다). Listing 9은 spring Framework에서 이 쿼리를 사용하는 방법을 보여주고 있습니다.

Listing 9

String queryStr = "SELECT sysdate FROM dual";
Date date = null;
  
try
{
  JdbcTemplate jt = new JdbcTemplate(this.myDataSource);
  date = (Date) jt.queryForObject(queryStr, Date.class);
}
catch ( BadSqlGrammarException badSqlEx ) // unchecked
{
  System.err.println( badSqlEx.getMessage() );
  System.err.println( "Bad SQL: " + badSqlEx.getSql() );
}
spring 및 JDBC 기반의 DDL 구문 실행

위의 코드 예제들은 모두 spring Framework를 이용하여 DML 구문을 처리하는 방법을 보여주고 있습니다. spring Framework는 매우 간단한 신택스를 통해 DDL 구문을 지원합니다. Listing 10과 11은 오라클 데이터베이스에서 테이블을 drop/purge하기 위한 표준 JDBC 코드와 spring으로 래핑된 JDBC 코드를 예시하고 있습니다.

Listing 10

Statement stmt = null;

try
{
  stmt = this.myConnection.createStatement();
  stmt.executeUpdate("DROP TABLE salgrade PURGE");
}
catch ( SQLException sqlEx )
{
  System.err.println("Message: " + sqlEx.getMessage());
  System.err.println("Error Code: " + sqlEx.getErrorCode());
  System.err.println("SQL State: " + sqlEx.getSQLState());
}
finally
{
  try
  {
   if (stmt != null)
   {
     stmt.close();
   }
  }
  catch (SQLException finallySqlEx)  // checked exception
  {
   System.err.println(finallySqlEx.getMessage());
  }
}

 

Listing 11
try
{
  JdbcTemplate jt = new JdbcTemplate(this.myDataSource);
  jt.execute("DROP TABLE salgrade PURGE");
}
catch ( BadSqlGrammarException badSqlEx ) // unchecked
{
  System.err.println( badSqlEx.getMessage() );
  System.err.println( "BadSQL: " + badSqlEx.getSql() );
}
이제 spring 기반 코드가 고전적인 JDBC 코드에 비해 읽기(그리고 작성과 관리)에 훨씬 편리하다는 사실을 분명하게 확인하셨을 것입니다. 위의 경우, 캡처된 exception이 unchecked exception이므로 실제로 작성이 필요한 코드는 단 두 줄에 불과합니다.

spring Framework를 이용한 스토어드 프로시저의 액세스

Listings 13과 14는 (Listing 12에 사용된) 스토어드 프로시저를 일반적인 JDBC와 spring 기반의 JDBC를 이용해 액세스하는 방법을 보여주고 있습니다.

Listing 12

CREATE OR REPLACE PROCEDURE salary_percentile (
  salary IN emp.sal%TYPE,
  low IN salgrade.losal%TYPE,
  high IN salgrade.hisal%TYPE,
  percentile OUT NUMBER )
AS
BEGIN
 IF salary < 0 THEN
  percentile := null;
 ELSE
  percentile := (salary - low) / (high - low);
 END IF;
END;

 

Listing 13
String escapeString = "{call salary_percentile (?,?,?,?)}";
CallableStatement cStmt = null;
double percentile = -1.0;
final int PERCENTILE_INDEX = 4;
   
try
{
  cStmt = this.myConnection.prepareCall(escapeString);
  cStmt.setInt(1, aSalary); // aSalary passed into this method
  cStmt.setInt(2, aLow);   // aLow passed into this method
  cStmt.setInt(3, aHigh);  // aHigh passed into this method
  cStmt.registerOutParameter(PERCENTILE_INDEX, Types.DOUBLE);
  cStmt.execute();
  percentile = cStmt.getDouble(PERCENTILE_INDEX);
}
catch ( SQLException sqlEx )
{
  System.err.println("Message: " + sqlEx.getMessage());
  System.err.println("Error Code: " + sqlEx.getErrorCode());
  System.err.println("SQL State: " + sqlEx.getSQLState());
}
finally
{
  if ( cStmt != null )
  {
   try
   {
     cStmt.close();
   }
   catch (SQLException finallySqlEx)
   {
     System.err.println(finallySqlEx.getMessage());
   }
  }
}

return percentile;
spring 기반의 코드를 통해 스토어드 프로시저에 대한 액세스를 예시하고 있는 Listing 14에는 org.springframework.jdbc.object.StoredProcedure 클래스가 사용되고 있습니다. (spring 패키지의 StoredProcedure 클래스에는 스토어드 프로시저 이외에도 다른 유형의 SQL 구문을 위한 오브젝트가 함께 포함되어 있습니다. 자세한 정보는 spring 기술문서를 참고하시기 바랍니다.)

Listing 14

private class SalaryCalculator extends StoredProcedure
{
  /** Name of procedure in database. */
  public static final String PROC_NAME = "salary_percentile";

  /**
  * Constructor for this StoredProcedure class.
  * @param ds Data Source.
  */
  public SalaryCalculator(DataSource ds)
  {
   setDataSource(ds);
   setSql(PROC_NAME);

   // Parameters should be declared in same order here that
   // they are declared in the stored procedure. 

   declareParameter(new SqlParameter("salary", Types.DOUBLE));
   declareParameter(new SqlParameter("low", Types.INTEGER));
   declareParameter(new SqlParameter("high", Types.INTEGER));
   declareParameter(new SqlOutParameter( "percentile",
                      Types.DOUBLE ) );
   compile();
  }
   
  /**
  * Execute stored procedure.
  * @return Results of running stored procedure.
  */
  public Map executeCalculation( double aSalary,
                 int aLow,
                 int aHigh )
  {
   Map inParameters = new HashMap();
   inParameters.put( "salary", new Double(aSalary) );
   inParameters.put( "low", new Integer(aLow) );
   inParameters.put( "high", new Integer(aHigh) );
   Map out = execute( inParameters ); // Call on parent class
   return out;
   }
  }

// . . .
// Code below is all that is needed to call your Stored Procedure
// created above. 
// . . .

  SalaryCalculator calcPercentile =
   new SalaryCalculator(this.myDataSource);
  Map calcResults =
   calcPercentile.executeCalculation(aSalary, aLow, aHigh);
   
  return ((Double)calcResults.get("percentile")).doubleValue();

// . . .
// remainder of class
// . . .
Listing 14의 코드는 하나의 클래스만을 사용하고 있습니다. 코드의 대부분은 springStoredProcedure 클래스를 확장하는 inner 클래스(SalaryCalculator)로 구성되며, 개발자가 생성한 클래스를 이용해 Listing 12의 스토어드 프로시저를 래핑(wrapping)하는 구조로 구현되어 있습니다. SalaryCalculator 클래스를 호출하기 위한 코드는 단 몇 줄에 불과합니다. 이처럼, SalaryCalculator 클래스는 스토어드 프로시저의 호출에 수반되는 복잡한 과정을 추상화하는 효과를 제공합니다.

개발자는 StoredProcedure를 확장하는 클래스를 작성하는 경우 다양한 효과를 확인할 수 있습니다. 일반적인 checked SQLException 대신 spring이 제공하는 unchecked DAO, JDBC 익스텐션을 사용할 수 있다는 것도 기대할 수 있는 효과의 하나입니다. 또 위 코드에서 확인할 수 있는 것처럼 리소스의 해제를 위한 반복적인 코딩 작업을 생략할 수 있습니다.

0보다 작은 salary가 스토어드 프로시저에 전달되는 경우, Listings 1314 에서 각각 다르게 처리된다는 점을 참고하시기 바랍니다. 일반적인 JDBC 코드(Listing 13)의 경우, 0.0의 값이 반환되며, 결과가 null인지 확인하기 위해서는 wasNull()을 사용해야만 합니다. spring 기반 코드(Listing 14)에서는, Java null이 반환되며 wasNull()의 호출이 불필요합니다.

spring을 이용한 Oracle 오브젝트의 액세스

spring Framework'의 JDBC 추상화 기능을 이용하여 Oracle 오브젝트에 액세스하는 예를 Listing 15에서 확인하실 수 있습니다.

Listing 15

CREATE OR REPLACE TYPE address_type AS OBJECT
(
 STREET VARCHAR2(20),
 CITY  VARCHAR2(15),
 STATE  CHAR(2),
 ZIP   CHAR(5)
);
/

CREATE TABLE emp_address_table
(
 EMP_NUM NUMBER(4),
 ADDRESS ADDRESS_TYPE
);
JDBC를 통해 Oracle 오브젝트에 액세스하는 방법은 크게 두 가지가 있습니다. 그 하나는 표준 JDBC 인터페이스 java.sql.Struct를 Oracle 드라이버를 위한 클래스인 oracle.sql.STRUCT와 함께 사용하는 방법입니다. 두 번째는 Oracle 오브젝트 타입에 매핑되는 Java 클래스를 생성하는 방법입니다. Oracle JDeveloper 10g IDE 와 JPublisher를 이용하면 이러한 작업을 매우 간단하게 마무리할 수 있습니다.

java.sql.Struct를 이용하거나 JPublisher를 이용하는 경우, 오브젝트 내의 데이터에 액세스하는 과정에서 SQLException을 직접 처리해야 한다는 요구사항이 뒤따릅니다. java.sql.Struct를 사용하는 경우, getAttributes() 메소드가 SQLException을 발생시킵니다. 마찬가지로 JDeveloper/JPublisher에 의해 생성된 Java 클래스에 포함된 메소드 역시 SQLException을 발생시킵니다. 이러한 Java 오브젝트에 액세스하는 개발자들은 SQLException을 직접 처리하거나 Listing 16 및 17과 같은 spring 기반 코드를 사용해야 합니다.

Listing 16

String queryStr =  "SELECT address FROM emp_address_table WHERE "
         + "emp_num = " + aEmpNum; // aEmpNum passed in
final List addresses = new ArrayList();
  
JdbcTemplate jt = new JdbcTemplate(this.myDataSource);
jt.query( queryStr,
     new RowCallbackHandler()
     {
       public void processRow(ResultSet rs)
        throws SQLException
       {
        // The Struct and ResultSet methods throw
        // SQLException, so throws above is necessary
        java.sql.Struct address =
         (java.sql.Struct) rs.getObject(1);
        String street =
          address.getAttributes()[0].toString();
        String city =
          address.getAttributes()[1].toString();
        String state =
          address.getAttributes()[2].toString();
        String zipCode =
          address.getAttributes()[3].toString();
        String addressStr = street + ", " + city + ", "
                  + state + " " + zipCode;
        addresses.add( addressStr );
       }
     }
    );

 

Listing 17
String updateStr =  "UPDATE emp_address_table SET address = ? "
          + "WHERE emp_num = ?";

JdbcTemplate jt = new JdbcTemplate( getDataSource() );
jt.update( updateStr,
      new PreparedStatementSetter()
      {
       public void setValues(PreparedStatement ps)
         throws SQLException
       {
         Address address = new Address();
         address.setStreet(aStreet);
         address.setCity(aCity);
         address.setState(aState);
         address.setZip(aZipCode);
          
         ps.setObject(1, address);
         ps.setInt(2, aEmpNum);
       }
      }
);
두 가지 접근방식 모두 JDBC(StructSQLData)를 이용하여 Oracle 오브젝트에 액세스하고 있으며, 반환된 클래스에 액세스하는데 사용되는 메소드는 SQLException을 발생시킵니다. Listing 16, 17은 anonymous inner callback 클래스를 이용하여 SQLException을 unchecked spring Framework exception hierarchy 내부로 “숨기는” 방법을 예시하고 있습니다. 이렇게 발생된 exception은 본 문서의 다른 예제와 동일한 exception 변환 방법을 사용하여 처리됩니다. 위 코드 예제는 spring 기반의 코드를 사용하여 Oracle 오브젝트에 액세스하는 방법을 보여주는 동시에, 다른 JdbcTemplate 메소드의 적용이 불가한 상황에서 anonymous inner callback 클래스를 이용하는 방법을 예시하고 있습니다.

Listing 16에서 spring 기반의 코드에 ResultSet과 SQLException이 처음으로 사용된 것을 확인하실 수 있습니다. 하지만 여기에서도 SQLException은 직접적으로 사용되지 않고, spring Framework가 내부적인 exception 핸들링 메커니즘을 통해 발생된 SQLException을 처리하고 있습니다. 그러므로 개발자는 spring exception의 캡처 및 핸들링에 대해 신경을 쓰지 않아도 됩니다.

Online Reading

JDBC Resources
JDBC/SQLJ Discussion Forum
Core J2EE Design Patterns-Data Access Object (DAO)
"Of Persistence and POJOs: Bridging the Object and Relational Worlds"
Oracle TopLink spring Integration
Java Programming with Oracle JDBC, Chapter 19, "Performance" (Sample Chapter)
Top Ten Oracle JDBC Tips (from OnJava)
"JPublisher: Simplifying Database Access" (from Oracle Magazine)

Listing 17에서는 spring 기반의 코드에 PreparedStatement을 활용하는 방법을 처음으로 예시하고 있으며, SQLException을 처리하는 또 다른 방법을 보여주고 있습니다. Listing 16에서와 마찬가지로, SQLExceptionspring Framework의 JdbcTemplate 클래스를 위한 참조로서만 활용되며, JdbcTemplate 클래스는 발생되는 모든 exception을 unchecked spring exception으로 처리합니다.

또 Listing 16과 17은 springRowCallbackHandler, PreparedStatementSetter 콜백 인터페이스를 사용하는 방법을 예시하고 있습니다. 이 인터페이스들은 anonymous inner 클래스를 이용해 구현됩니다. inner 클래스를 사용하는 경우, (이전의 단순한 JdbcTemplate 활용 방법에 비해) ResultSet, PreparedStatement과 API에 대해 더 깊이 이해하고 있어야 한다는 요구사항이 붙기는 하지만, (JdbcTemplate이 모든 exception을 처리하므로) 여전히 SQLException의 처리에 대해 신경 쓸 필요가 없다는 장점이 있습니다.

앞서 설명한 spring 기반 코드 예제(Listings 3, 6 JdbcTemplat 활용 예제)에서는 ResultSet, Statement, PreparedStatement, SQLException 등이 아예 언급조차 되지 않았다는 사실을 상기하시기 바랍니다. 이처럼 고도로 추상화된 접근방식은 JDBC의 세부에 관여하기를 원하지 않는 개발자들에게 특히 유용합니다. 하지만 이처럼 극도로 단순화된 접근방식은 Listings 1617에 예시된 inner-class 방식의 접근법에 비해 유연성이 떨어지는 것이 사실입니다. 개발자는 JDBC API에 대한 매우 기본적인 지식만을 활용하여 Listing 16, 17과 같은 유연한 코드를 작성할 수 있습니다. 어떠한 경우든 exception은 spring exception hierarchy를 통해 일관성 있게 처리되며, 개발자는 SQLException에 대해 신경을 쓸 필요가 없습니다.

그 밖의 장점

spring Framework가 JDBC 환경에서 제공하는 효과는 그 밖에도 여러 가지가 있습니다. 위에서 언급되지 않은 몇 가지 중요한 장점이 아래와 같습니다:
  • springJdbcTemplate 클래스는 위에서 설명된 것과 유사한 여러 가지 spring 기반 인터페이스를 제공합니다. 한 예로, ResultSetExtractorPreparedStatementCreatorRowCallbackHandlerPreparedStatementSetter와 유사한 기능을 제공합니다. JdbcTemplate이 지원하는 유용한 인터페이스의 또 다른 예로 BatchPreparedStatementSetter를 들 수 있습니다.
  • spring 애플리케이션 컨텍스트에서 spring의 JDBC 추상화 기능을 반드시 사용해야 하는 것은 아니지만, 그 적용효과는 분명합니다. 개발자는 코드에 직접 커플링하는 대신, spring Framework의 설정 파일을 이용하여 비즈니스 오브젝트를 데이터 액세스 오브젝트와 매핑할 수 있습니다.
  • org.springframework.jdbc.object 패키지는 재활용 가능한 오브젝트의 형태로 (DML 작업 및 스토어드 프로시저를 포함하는) 관계형 데이터베이스 작업을 지원합니다. 개발자는 쓰레드 조작의 안정성을 보장해주는 “threadsafe” 오브젝트를 이용하여 보다 높은 수준의 관계형 데이터베이스 추상화 환경을 구현할 수 있습니다. 필자는 개인적으로 JdbcTemplate이 제공하는 추상화 수준에 매우 만족하고 있으며, exception, 결과 셋, 리소스 관리 등의 측면에서 개발자가 원하는 수준의 처리 기능이 제공되고 있다고 평가합니다. 하지만, (TopLink, Hibernate와 같은) O/R(object-relational) 매핑 테크놀로지를 사용하지 않으면서 한층 더 높은 수준의 관계형 데이터베이스 개념의 추상화를 구현하고자 하는 경우라면, org.springframework.jdbc.object 패키지를 고려해 볼 것을 추천합니다 Listing 12의 스토어드 프로시저 예제에 이 패키지의 활용 방법이 간략하게 예시되고 있습니다.
  • spring Framework는 컨테이너 외부에서 사용될 수 있는 단순한 형태의 DataSource를 제공하며, 필요 시 override가 가능한 추상화된 datasource 클래스를 지원합니다 (spring Reference Documentation의 Chapter 10 참고).
  • spring Framework는 JDBC 이외에도 Hibernate, iBatis, Java Data Objects (JDO)와 같은 O/R 매핑 테크놀로지를 지원합니다. (spring Reference Documentation의 Chapter 11 참고). 본 문서의 “온라인 리소스” 글상자에서 spring과 Oracle TopLink를 함께 활용하는 방법을 소개한 문서의 링크를 참조하실 수 있습니다.
  • spring Framework는 테크놀로지 독립적(technology-agnostic)한 DAO 인터페이스와 공통 exception hierarchy를 공유하는 개발 환경을 지원합니다. spring Reference Documentation의 Chapter 9 참고). 이 주제는 본 문서의 여러 부분에서 언급되고 있습니다.
  • spring Framework는 SQLWarnings을 exception(SQLWarningException)의 형태로 캡처하는 기능을 지원합니다. 이 기능은 거의 모든 상황에서 SQLException을 발생시키는 오라클 데이터베이스, JDBC 드라이버 기반 환경에서는 그리 유용하지 않습니다. 이 기능은 개발자가 경고(warning) 내용에 대해 보다 깊이 이해하고자 하는 경우, 또는 SQLWarning을 빈번하게 발생시키는 데이터베이스를 사용하는 경우에 주로 사용됩니다. 이 경우, JDBC만을 사용하는 개발자는 SQLWarning이 발생했는지 확인하는 코드를 일일이 작성해야 합니다. spring Framework는 보다 쉽고 일관성 있는 경고 처리를 가능하게 합니다.
  • spring Framework는 org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor 인터페이스와 이 인터페이스의 몇 가지 implementation(예: SimpleNativeJdbcExtractor)을 지원합니다. 이 인터페이스는 데이터베이스 연결이 다른DataSource를 통해 래핑(wrapping)되어 있거나, 연결 풀(connection pool)이 사용되는 환경에서 오라클 연결 또는 ResultSet을 통해 오라클의 기능에 접근하기 위한 방법으로 유용하게 활용됩니다.
  • spring Framework는 oracle.sql.BLOB(binary large object)과 oracle.sql.CLOB(character large object)의 인스턴스 생성을 위한 클래스로 org.springframework.jdbc.support.lob.OracleLobHandler를 제공합니다r.
  • spring Framework의 OracleSequenceMaxValueIncrementer 클래스는 Oracle sequence의 다음 값을 확인하는 용도로 사용됩니다. 이 클래스는 “select someSequence.nextval from dual” (someSequence는 오라클 데이터베이스의 sequence 이름)을 커맨드 상에서 실행한 것과 동일한 결과를 제공합니다. 이 클래스를 사용하는 경우, 오라클 환경에 긴밀하게 커플링(thigh coupling)되지 않은 환경에서 DataFieldMaxValueIncrementer를 DAO hierarchy 내에서 활용할 수 있다는 이점이 있습니다.
spring Framework의 다른 활용 방안

본 문서는 spring Framework를 이용해서 유지보수가 간편하고 에러의 가능성이 적은 JDBC 코드를 작성하는 방법에 초점을 맞추고 있습니다. spring Framework가 제공하는 혜택은 여기에 그치지 않습니다. spring Framework는 JDBC 환경에서O/R(object-relational) 매핑 테크놀로지가 제공하는 것과 유사한 기능을 제공할 뿐 아니라, 엔터프라이즈 애플리케이션의 다른 여러 가지 계층을 위한 기능을 함께 제공하고 있습니다. 라이트웨이트 IoC(Inversion of Control) 컨테이너 지원, 웹 프레임워크 지원, J2EE 애플리케이션 지원 기능 등이 그 예입니다.

spring Framework는 기존 환경에 대한 변경작업을 최소화하도록 설계되었습니다. spring Framework 환경을 위한 별도의 포팅 작업을 거치지 않고도 spring Framework가 제공하는 다양한 혜택을 누리는 것이 가능합니다. spring은 오픈 소스이며, 애플리케이션 설계 원칙을 제시하기 위한 툴로써 활용될 수도 있습니다. 지금 당장 spring Framework를 애플리케이션에 적용할 수 없는 환경이라 하더라도, 표준적인 설계 및 코딩 원칙을 제시하기 위한 용도로 spring Framework를 이용하는 것은 충분히 가능합니다. spring의 JDBC 지원 기능은 spring의 다른 기능과는 독립적으로 제공되므로, spring Framework에 의존적이지 않은 환경에서 spring을 처음으로 사용해보고자 하는 사용자에게 매우 유용하게 활용됩니다.


Dustin Marx는 Raytheon Co.의 선임 소프트웨어 엔지니어 겸 설계담당자입니다.

출처 : Tong - 반이오타님의 JAVA Framework통