Query Service

쿼리문이나 객체의 입력만으로 DB 데이터 조작을 가능하게 하는 서비스이다. Query 서비스는 JDBC(Java Database Connectivity)를 이용한 데이터 액세스 수행 부분을 추상화함으로써 간편한 데이터 액세스 방법을 제공하고, JDBC 사용 시 발생할 수 있는 공통적인 에러를 줄여주게 된다. Query 서비스는 내부적으로 DataSource 서비스를 이용하고 있으므로, DataSource 서비스와 같이 배포되어야 함에 유의하도록 한다.
Query 서비스에 대한 기본 구현체는 1가지이며, 다음은 각 구현체별 사용법이다. 또한 매뉴얼 통해 제공되는 다음의 상세 가이드를 참조하면 Query Service를 보다 쉽게 이용할 수 있다.

QueryServiceImpl

다음은 Query 서비스를 사용하기 위해 필요한 설정 정보들이다.
Property Name
Description
Required
Default Value
jdbcTemplate DataSource 서비스를 이용하여 해당하는 DB에 접근하고, 정의된 쿼리문을 실행시키는 역할을 수행하는 PagingJdbcTemplate의 Bean Id를 정의한다.
Y
N/A
sqlRepository 테이블 매핑 정보 및 쿼리문을 정의한 매핑 XML 파일들을 처리하는 역할을 수행하는 SQLLoader의 Bean Id를 정의한다.
Y
N/A
pagingSQLGenerator DB에 특화된 형태의 페이징 처리를 위한 SQL을 구성하지 않더라도, 해당 DB에 따라 페이징 처리를 위해 알맞은 SQL을 생성할 수 있도록 도와주는 PagingSQLGenerator의 Bean Id를 정의한다.
N
N/A
lobHandler LOB 유형의 데이터를 다루어야 하는 경우 알맞은 LobHandler의 Bean Id를 정의한다.
N
N/A
velocityPropsFilename Dynamic SQL 문을 다루어야 하는 경우 Velocity에 의해 남겨지는 Log 파일의 경로를 변경하고자 할 때 정의한다. Velocity Log 파일의 경로는 다음과 같이 절대/상대 경로(file:...)나 클래스패스(classpath:...)를 이용하여 정의 가능하다.
  • file:./testvelocity/velocity.log
  • classpath:/anyframe/core/query/log/velocity.log
값을 정의하지 않았을 경우에는 Velocity Log는 남기지 않는다.

* 해당하는 경로에 지정된 로그 파일이 생성되어 있어야 함에 유의하도록 한다.
N
Log를 남기지 않음.

다음에서는 Query 서비스가 필요로 하는 설정 정보 중, jdbcTemplate, sqlRepository, pagingSQLGenerator, lobHandler에 대해 자세히 짚어보기로 한다.

[jdbcTemplate] Query 서비스에서는 DataSource 서비스를 이용하여 해당하는 DB에 접근하고, 정의된 쿼리문을 실행시키는 역할을 수행하기 위해 Spring의 JdbcTemplate을 확장한 anyframe.core.query.impl.jdbc.PagingJdbcTemplate와 anyframe.core.query.impl.jdbc.PagingNamedParamJdbcTemplate를 제공하고 있다.
  • PagingJdbcTemplate : 내부 ResultSetExtractor를 이용하여 조회 결과에 대한 매핑 처리 및 페이징 처리를 수행한다.
  • PagingNamedParamJdbcTemplate : 별도 설정없이 QueryServiceImpl 클래스에서 내부적으로 사용하는 클래스로 Named Parameter를 가진 Dynamic SQL 처리를 수행한다.
따라서 QueryServiceImpl을 구현체로 정의한 경우 PagingJdbcTemplate을 참조하도록 한다. 다음은 PagingJdbcTemplate을 위해 필요한 주요 설정 정보들이다. (이외 설정 정보에 대해서는 org.springframework.jdbc.core.JdbcTemplate의 설정 정보를 참고하도록 한다.)
Property Name
Description
Required
Default Value
dataSource 참조할 dataSource service의 Bean Id를 정의한다.
Y
N/A
exceptionTranslator ExceptionTranslator의 Bean Id를 정의한다.
ExceptionTranslator는 DB 데이터 조작시 SQLException이 발생한 경우 별도 Exception 객체에 해당하는 SQL Error Code와 Error Message 정보를 셋팅하여 throw하도록 하는 역할을 수행하며, Query 서비스에서는 anyframe.core.query.impl.util.RawSQLExceptionTranslator를 제공하고 있다. 따라서, ExceptionTranslator를 별도 셋팅하면, 데이터 조작으로 인한 오류가 발생한 경우 Query 서비스에서 throw한 QueryServiceException으로부터 SQL Error Code와 Error Message 정보를 추출할 수 있게 된다.
N
N/A

[sqlRepository] Query 서비스에서는 테이블 매핑 정보 및 쿼리문을 정의한 매핑 XML 파일들을 처리하는 역할을 수행하기 위해 anyframe.core.query.impl.config.loader.SQLLoader를 제공하고 있다. 다음은 SQLLoader를 위해 필요한 주요 설정 정보들이다.
Tag Name
Attrubute Name
Description
Required
Default Value
Child Tag
config:configuration filename properties
filename 테이블 매핑 정보와 사용할 쿼리문을 정의하고 있는 파일명을 지정하는 요소로 복수 지정 가능하다. filename 요소에 대한 지정은 Spring Configuration 파일 경로 지정 방식과 동일하므로, 절대/상대적인 파일 경로 지정(file:...)과 클래스패스를 이용한 지정(classpath:...)이 가능하다. *를 활용한 Pattern Matching 역시 적용 가능하다.
Y
N/A
nullcheck 해당 DB Column의 값이 없는 경우, null value가 리턴되었을 때, 지정한 값으로 변환시켜준다. 현재, CHAR, VARCHAR, LONGVARCHAR 타입의 칼럼에 대해서만 지원된다.
N
N/A
sqlload dynamic 매핑 XML 파일에 대한 동적 Reload 설정여부를 정의한다.
N
N
frequency Reload 주기를 세팅한다.(miliseconds 단위) 10미만 입력시 -> 10으로 인식하며, 10이상 입력시 -> 입력값으로 인식한다.
N
skiperror 매핑 XML 파일을 읽어들이면서, error가 발생한 경우 skip 여부를 셋팅한다.
N

[pagingSQLGenerator] Query 서비스에서는 DB에 특화된 형태의 페이징 처리를 위한 SQL을 구성하지 않더라도, 해당 DB에 따라 페이징 처리를 위해 알맞은 SQL을 생성할 수 있도록 도와주는 역할을 수행하기 위해 다음과 같은 PagingSQLGenerator를 제공한다.
DB 종류 PagingSQLGenerator Class
Oracle anyframe.core.query.impl.jdbc.generator.OraclePagingSQLGenerator
DB2 anyframe.core.query.impl.jdbc.generator.DB2PagingSQLGenerator
HSQLDB anyframe.core.query.impl.jdbc.generator.HSQLPagingSQLGenerator
이 외, PagingSQLGenerator가 필요한 경우에는 anyframe.core.query.impl.jdbc.generator.AbstractPagingSQLGenerator를 확장하여 신규 PagingSQLGenerator를 생성하고, getPaginationSQL() 메소드를 구현해주면 된다. getPaginationSQL() 메소드에는 입력받은 SQL을 기반으로 페이징 처리를 위한 SQL을 생성하여 전달하는 로직을 정의하도록 한다.

[lobHandler] Query 서비스에서는 Spring에서 제공하는 LobHandler를 사용하여 LOB 유형의 데이터를 다루도록 권장한다. 다음은 Spring에서 제공하는 LobHandler 목록이다.
  • Oracle(9i이상) : org.springframework.jdbc.support.lob.OracleLobHandler
  • the Others : org.springframework.jdbc.support.lob.DefaultLobHandler
단, Spring에서 제공하는 OracleLobHandler의 경우 Oracle 9i 이상에서만 사용 가능하므로 Oracle 8i 사용자를 위해 anyframe.core.query.impl.jdbc.lob.AnyframeOracle8iLobHandler를 추가로 제공하고 있다. OracleLobHandler나 AnyframeOracle8iLobHandler의 경우 다음과 같은 설정 정보가 필요하다.
Property Name
Description
Required
Default Value
nativeJdbcExtractor 사용중인 Connection Pool에 맞게 Wrapping되어 있는 Connection 객체로부터 본래의 JDBC Connection 객체를 추출하는 역할을 수행하는 NativeJdbcExtractor의 Bean Id를 정의한다. (해당 lobHandler에서 nativeJdbcExtractor를 필요로 하는 경우에만 정의)
다음은 Spring에서 제공하는 주요 JdbcExtractor 클래스들이다.
  • Common DBCP :

  • org.springframework.jdbc.support.nativejdbc.CommonsDbcpNativeJdbcExtractor
  • C3P0 :

  • org.springframework.jdbc.support.nativejdbc.C3P0NativeJdbcExtractor
  • WebLogic :

  • org.springframework.jdbc.support.nativejdbc.WebLogicNativeJdbcExtractor
  • WebSphere :

  • org.springframework.jdbc.support.nativejdbc.WebSphereNativeJdbcExtractor

    오픈소스 프로젝트인 Commons DBCP를 Connection Pool로 채택한 경우 CommonsDbcpNativeJdbcExtractor를 사용할 수 있다.
    N
    N/A

    Samples

    • Configuration
    • 다음은 Query 서비스에서 사용할 속성을 정의한 applicationContext-query-oracle.xml 의 일부이다.
      <bean id="oracle_queryservice" class="anyframe.core.query.impl.QueryServiceImpl">
      	<property name="jdbcTemplate" ref="jdbcTemplate"/>
      	<property name="sqlRepository" ref="sqlLoader"/>
      	<property name="pagingSQLGenerator" ref="pagingSQLGenerator"/>
      	<property name="lobHandler" ref="lobHandler"/>		
      	<!-- if you don't define velocityPropsFilename, 
      	queryservice doesn't make a velocity log file. -->
      	<property name="velocityPropsFilename" value="file:./testvelocity/velocity.log"/>
      </bean>
      
      <bean id="oracle_jdbcTemplate" class="anyframe.core.query.impl.jdbc.PagingJdbcTemplate">
        <property name="dataSource" ref="oracle_datasource" />
      </bean>
      
      <bean id="pagingSQLGenerator" 
            class="anyframe.core.query.impl.jdbc.generator.OraclePagingSQLGenerator"/>
      
      <bean id="nativeJdbcExtractor" 
      	class="org.springframework.jdbc.support.nativejdbc.CommonsDbcpNativeJdbcExtractor" 
      	lazy-init="true"/>
      	
      <bean id="lobHandler" class="org.springframework.jdbc.support.lob.OracleLobHandler"
      	lazy-init="true">
      	<property name="nativeJdbcExtractor" ref="nativeJdbcExtractor"/>
      </bean>	
      
      또한, sqlLoader의 속성은 applicationContext-query-sqlloader.xml 에서와 같이 정의할 수 있다.
      <bean id="sqlLoader" class="anyframe.core.query.impl.config.loader.SQLLoader">
        <config:configuration>
          <!-- xml files in folder -->
          <!--filename>file:./testmappings/**/testcase-*.xml</filename-->
          <!-- xml files in classpath -->
          <filename>
            classpath:/services/query/mappings/mapping-general-query.xml
          </filename>
          <!-- ... -->
          <nullcheck type="VARCHAR" default-value="" />
          <sqlload dynamic="true" frequency="5" />
          <skiperror>true</skiperror>
        </config:configuration>
      </bean>
    • TestCase
    • 다음은 Query 서비스를 사용하여 해당하는 DB에 샘플 데이터를 INSERT, SELECT, UPDATE, DELETE하는 QueryServiceTestGeneral.java 코드의 일부이다.
      다음은 INSERT 예제이다.
      public void testInsertQuery() throws Exception{
          IQueryService queryService = (IQueryService) context.getBean("queryService");
          //create() : XML mapping파일에 정의되어 있는 SQL query를 이용하여 INSERT를 실행한다.
          int rs = queryService.create("create", new Object[] { "1234567890123", "AAAAA" , "seoul"});
          if ( rs == -1 ){
             throw new Exception("Insert query failed");
          }
      }
      다음은 SELECT 예제이다.
      public void testSelectQuery() throws Exception{
      	IQueryService queryService = (IQueryService) context.getBean("queryService");
      	//find() : XML mapping파일에 정의되어 있는 SQL query를 이용하여 SELECT를 실행한다.
      	
      	//일반적인 경우(table과 class를 mapping하지 않은 경우)
      	ArrayList rsquery  = (ArrayList) queryService
      	                        .find("selectGeneral", new Object[] { "%12345%" });
      	Map hsRsquery = new HashMap();
      	for( int i = 0 ; i < rsquery.size() ; i ++ ){
      		hsRsquery = (Map) rsquery.get(i);
      		String name = (String) hsRsquery.get("name");
      	}
      	/*매핑 XML에 해당 클래스와 매핑되는 테이블이 존재하지 않을 경우,
      	* 쿼리 수행 결과에 대해 하나의 Row별로 칼럼명,
      	* 해당값을 쌍으로 org.apache.commons.collections.map.ListOrderedMap에 put하고 
      	* ListOrderedMap들을 ArrayList에 담은 형태로 결과값을 리턴하게 된다.
      	*/
      	
      	//Table - Class mapping을 사용한 경우
      	Collection rsqueryNotUsingResultMapping = queryService
      	               .find("selectNotUsingResultMapping", new Object[] { "%12345%" });
      	Iterator rsqueryItr = rsqueryNotUsingResultMapping.iterator();
      	while (rsqueryItr.hasNext()) {
      		CustomerVO customer = (CustomerVO) rsqueryItr.next();
      		String name =  customer.getNm();
      	}
      
      	//result-mapping을 사용 한 경우
      	Collection rsqueryUsingResultMapping = queryService
      	                .find("selectUsingResultMapping", new Object[] { "%12345%" });
      	Iterator rsqueryItr_01 = rsqueryUsingResultMapping.iterator();
      	while (rsqueryItr_01.hasNext()) {
      		CompositionCustomerVO compositionCustomer 
      		                                 = (CompositionCustomerVO) rsqueryItr_01.next();
      		String name =  compositionCustomer.getCompositionName();
      	}
      	
      	if( rsquery.size() != 1 ){
      		throw new Exception("Select query failed");
      	}
      	if( rsqueryNotUsingResultMapping.size() != 1 ){
      		throw new Exception("Select query failed");
      	}
      	if( rsqueryUsingResultMapping.size() != 1 ){
      		throw new Exception("Select query failed");
      	}
      }
      다음은 UPDATE 예제이다.
      public void testUpdateQuery() throws Exception {
      	IQueryService queryService = (IQueryService) context
      	.getBean("queryService");
      	//update() : XML mapping파일에 정의되어 있는 SQL query를 이용하여 UPDATE를 실행한다.
      	int rs = queryService.update("update"
      	                , new Object[] { "9999999999999", "AAAAA" , "busan" , "1234567890123"});
      	if ( rs == -1 ){
      		throw new Exception("Update query failed");
      	}
      }
      다음은 DELETE 예제이다.
      public void testDeleteQuery() throws Exception {
      	IQueryService queryService = (IQueryService) context
      	.getBean("queryService");
      	//remove() : XML mapping파일에 정의되어 있는 SQL query를 이용하여 DELETE를  실행한다.
      	int rs = queryService.remove("delete", new Object[] { "9999999999999" });
      	if ( rs == -1 ){
      		throw new Exception("Delete query failed");
      	}
      }
      앞서 소개된 샘플 테스트 코드를 포함하여 QueryService 소개 페이지에서 제공하는 모든 샘플 테스트 코드는 HSQL DB를 기반으로 실행된다. 단, ※ CallableStatement, LOB의 경우는 Oracle 9i, 10g를 기반으로 함.)

    Resources

    • 다운로드
    • 샘플 테스트 코드를 포함하고 있는 anyframe-querytest-src.zip 파일을 다운받은 후, 테스트 환경 설정 을 참조하여 위에서 제시한 예제 코드를 실행해 볼 수 있다.
      Name
      Download
      anyframe-querytest-src.zip
      Download