JCF TEAM BLOG

관리자 글쓰기
블로그 »
블로그 »
Though iBATIS is one of a very popular ORM framework, it has some irritatating problems that every developers have suffered for a long time. Whenever sqlMap file is modified, one should undergo the tedious process of restarting the application so that the modifications actually take effect.
So I made a little extension to Spring framework's SqlMapClientFactory class in order to monitor file modification and re-initialize sqlMapClient accordingly. Basic implementation ideas are :
1. don't change original classes of Spring Framework nor iBATIS library.
2. should run with iBATIS 2.3.0 for jdk 1.4 and iBATIS 2.3.2 or later for jdk 1.5 or higher.
 (with the nice feature of Spring 2.5.5+ SqlMapClientFactoryBean's wildcard matching while preserving compatibility with old libraries.)
3. assuming default singleton scoped beans, SqlMapClientFactoryBean creates new SqlMapClient instance only when Spring initializes the applicationContext. The object (sqlMapclient) injected to other beans cannot be replaced while the application is up. It should be replaced with a proxy. The proxy, sitting in as the product of the factory, deligates all the received messages to the real sqlMapClient object, which can be replaced whenever the mapping files are modified.
4. make list of files to monitor from SqlMapClientFactoryBean's properties and sqlMapConfig files.

Limitations
The list of files to monitor is determined on startup. It doesn't detect added files while running, and emits warning messages for removed files. For example, with a sqlMap file registered to a sqlMapConfig file, it is not monitored but those changes work when refreshed.

Requirements
iBATIS sqlmap 2.3.0, Java 1.4, Spring 2.5+
or
iBATIS sqlmap 2.3.2+, Java 1.5+, Spring 2.5.5+

Installation
1. replace SqlMapClientFactoryBean with newly created RefreshableSqlMapClientFactoryBean in applicationContext configuration file
2. specify modification detection check interval in milliseconds.
3. add backport-util-concurrent-3.1.jar to classpath.

configuration example
<bean id="sqlMapClient" class="jcf.dao.ibatis.sqlmap.RefreshableSqlMapClientFactoryBean"> 
   <property name="configLocation" value="classpath:jcf/dao/ibatis/sqlmap/sqlmap-config.xml" /> 
   <property name="dataSource" ref="dataSource" />
 
   <!-- Java 1.5 or higher and iBATIS 2.3.2 or higher REQUIRED --> 
   <property name="mappingLocations" value="jcf/dao/\*\*\/T\*.xml" /> 
   <!-- <property name="mappingLocations" value="file:///D:/Type.xml" />-->
  <property name="checkInterval" value="1000" /> 
</bean>

크리에이티브 커먼즈 라이센스
Creative Commons License
Writer profile
author image
setq
2009/06/22 18:38 2009/06/22 18:38

(go to top)

블로그 »

정말 오래간만의 포스팅입니다. 오랜 동안의 공백기간 이후 이 사이트도 또 잠깐이나마 정상화할 수 있지 않을까 하는 기대를 해보면서 올려봅니다.

오랜 기간 숙원사업(?) 이었던 스프링과 iBATIS sqlMap을 이용해서 애플리케이션 개발/운영할 때 변경사항이 발생할 때마다 서버를 올렸다 내렸다하는 번거로움을 해소하기 위한 방법을 알려드리겠습니다.

일단 JCF 3.5 릴리즈에는 들어갈 것 같고요, 이미 여기저기서 아는 분들은 각자 만들어서 사용하고 계실만한 간단한 방법이긴 합니다만 그래도 정식 배포본에 들어가게 되었으니 언급은 하고 넘어가야겠지요?

- 착안
1. iBATIS 자체를 뜯어고치지는 않는다. 대신 스프링의 SqlMapFactoryBean을 교체 (IoC 컨테이너라 편리)
2. 실행 중 sqlMapClient를 교체하기 - 프록시 처리 및 수행 성능을 고려한 concurrency 처리
3. sqlMap 또는 sqlMapConfig의 xml이 변경되면 자동으로 리로드하도록 파일 감시.
4. Java 1.4, iBATIS 2.3.0, Spring 2.x 조합과 Java 1.5+, iBATIS 2.3.2+, Spring 2.5.5+ 조합간의 차이 처리


나머지는 Javadoc으로 대신합니다. (가만 보니 수동 리로딩에 대한 언급은 빠져있군요.)


 
RefreshableSqlMapClientFactoryBean
iBATIS sqlmap 클라이언트의 sqlMap 및 sqlMapConfig 파일의 변경을 감지, 실시간 적용하는 팩토리 빈.

개요
iBATIS + Spring 개발시 쿼리 매핑 파일이 변경되면 웹애플리케이션 서버를 재기동해야 적용이 됐었다. 이러한 불편을 없애기 위해 매핑 파일 변경을 실시간으로 감시, 적용하는 모듈을 제공한다.
감시 대상 이 모듈은 iBATIS sqlmap 클라이언트의 sqlMap 및 sqlMapConfig 파일의 변경을 감지, 실시간 적용해준다.

제약사항
감시 대상 파일들은 스타트업 당시에 결정된다. 그러므로 추가된 파일들에 대해서는 감지가 되지 않고, 삭제된 파일들에 대해서는 경고 메시지가 나온다. 예를 들어, sqlMapConfig에 sqlMap 파일이 추가되거나 하면 해당 맵이 적용되기는 하지만, 실시간 변경 감지 대상으로 추가되지는 않는다.

요구사항
iBATIS sqlmap 2.3.0, Java 1.4, Spring 2.5 이상 또는 iBATIS sqlmap 2.3.2 이상,
Java 1.5 이상, Spring 2.5.5 이상

적용 순서
1. Spring의 applicationContext 설정 파일 중 sqlMapClient를 얻기 위한 SqlMapClientFactory 빈을 신규 클래스로 교체한다.
2. 변경 감지 시간 간격 (1000분의 1초 단위)를 지정한다.

 <bean id="sqlMapClient" class="jcf.dao.ibatis.sqlmap.RefreshableSqlMapClientFactoryBean">
     <!-- <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">-->
     <property name="configLocation" value="classpath:jcf/dao/ibatis/sqlmap/sqlmap-config.xml" />
     <property name="dataSource" ref="dataSource" />
 
     <!-- Java 1.5 or higher and iBATIS 2.3.2 or higher REQUIRED -->
     <property name="mappingLocations" value="jcf/dao/\*\*\/T\*.xml" />
     <!-- <property name="mappingLocations" value="file:///D:/Type.xml" />-->
 
     <property name="checkInterval" value="1000" />
 </bean>
 
3. 라이브러리 추가
backport-util-concurrent-3.1.jar
테스트 기존의 applicationContext에 등록되어 있던 sqlMapClient에 해당하는 mappingFile들을 편집하거나, sqlMapConfig 파일 또는 그 파일에 등록된 mappingFile(sqlMap 파일)들을 편집하면 주어진 변경감지 시간 후 변경사항이 적용된다.


Author:
setq

크리에이티브 커먼즈 라이센스
Creative Commons License
Writer profile
author image
setq
2009/04/23 15:37 2009/04/23 15:37

(go to top)

블로그 »
서브모듈 별로 분리하여 시스템을 개발하려면 설정파일을 잘 분리해야 한다.
iBATIS 인 경우는 sqlmap-config 파일에 모든 mapping 파일을 등록해야 하므로
사실상 설정 파일을 모듈별로 분리하기는 어렵다.
이 부분에 대하여 고민하고 있던 중에 Spring 프레임워크가 구원투수로 나섰다.

Spring 버전 2.5.3을 이용하면 이 문제를 깔끔(?)하게 처리할 수 있다.

새롭게 추가된 SqlMapClientFactoryBean의 mappingLocations 프로퍼티를 통해
sqlmap mapping 파일을 일일히 지정하지 않고 패턴을 이용하여 일괄적으로 지정할 수 있게 되었다.

<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
    <property name="configLocation" value="classpath:sqlmap-config.xml" />
    <property name="mappingLocations" value="classpath:**/sqlmap/*.xml"/>
    <property name="dataSource" ref="dataSource" />
</bean>    


위와 같이 설정하면 sqlmap-config 파일은 아래와 같이 단순해 진다.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN" "http://dev.daewoobrenic.co.kr/dtd/sql-map-config-2.dtd">

<sqlMapConfig>    
    <settings useStatementNamespaces="true" cacheModelsEnabled="false"/>
</sqlMapConfig>


주의해야 할 것은  iBATIS 2.3.2 이상의 버전을 사용해야 하며
DTD 파일에서 sqlMap 을 필수에서 옵션으로 변경해 주어야 한다.
우선 테스트를 위해서 src 디렉토리에 com.ibatis.sqlmap.engine.builder.xml 팩키지를
만들고 기존의 sql-map-config-2.dtd  복사하여 아래와 같이 변경을 하였다.

<!ELEMENT sqlMapConfig (properties?, settings?, resultObjectFactory?, typeAlias*, typeHandler*, transactionManager?, sqlMap*)+>


아직까지는 DTD를 수정해야 하는 번거러움이 있지만
개발자 분들의 노가다를 줄이는데 조금이라도 도움이 되었으면 좋겠다^^






 

크리에이티브 커먼즈 라이센스
Creative Commons License
Writer profile
그래도 꿈이 있어서 행복하다^^
2008/07/14 14:01 2008/07/14 14:01

(go to top)

블로그 »

현재까지 iBatis에서는 JDBC 메타데이터를 가져올 수 있는 API 를 제공하지 않고 있습니다.
문제는 Gauce나 MiPlatform 같은 X인터넷 솔루션을 사용할 경우는 데이타 컬럼 크기를 알아야만 합니다.
이럴 경우 AOP 구현기술인 AspectJ를 활용할 수 있습니다.

가장 중요한 것은 Aspect를 적용해야 하는 소스상의 위치(Pointcut) 찾아내는 것입니다.
손쉬운 방법 중에 하나는 Stacktrace를 이용하는 것입니다.
아래 Stacktrace를 보면 Resutset 객체를 참조할 수 있는 SqlExecutor.handleResults(RequestScope, ResultSet, int, int, RowHandlerCallback) 를 Pointcut으로 해야 함을 알 수 있습니다.

Thread [main] (Suspended (breakpoint at line 28 in DefaultRowHandler))
 DefaultRowHandler.handleRow(Object) line: 28
 RowHandlerCallback.handleResultObject(RequestScope, Object[], ResultSet) line: 76
 SqlExecutor.handleResults(RequestScope, ResultSet, int, int, RowHandlerCallback) line: 395
 
SqlExecutor.executeQuery(RequestScope, Connection, String, Object[], int, int, RowHandlerCallback) line: 185
 SelectStatement(GeneralStatement).sqlExecuteQuery(RequestScope, Connection, String, Object[], int, int, RowHandlerCallback) line: 205
 SelectStatement(GeneralStatement).executeQueryWithCallback(RequestScope, Connection, Object, Object, RowHandler, int, int) line: 173
 SelectStatement(GeneralStatement).executeQueryForList(RequestScope, Transaction, Object, int, int) line: 123
 SqlMapExecutorDelegate.queryForList(SessionScope, String, Object, int, int) line: 610
 SqlMapExecutorDelegate.queryForList(SessionScope, String, Object) line: 584
 SqlMapSessionImpl.queryForList(String, Object) line: 101
 SqlMapClientTemplate$3.doInSqlMapClient(SqlMapExecutor) line: 255
 SqlMapClientTemplate.execute(SqlMapClientCallback) line: 188
 SqlMapClientTemplate.executeWithListResult(SqlMapClientCallback) line: 214
 SqlMapClientTemplate.queryForList(String, Object) line: 253
 LookupDaoiBatis.getRoles() line: 22



다음 작업으로는 Pointcut에 적용될 Aspect를 만듭니다.

public aspect IBatisJDBCMetadata {
 public pointcut sqlExecutorCall() :
         execution(* SqlExecutor.executeQuery(..)) ;
 public pointcut handleResults() :
           execution(* SqlExecutor.handleResults(..)) ;
 public pointcut executeQueryForList() :
         execution(* com.ibatis.sqlmap.engine.mapping.statement.GeneralStatement.executeQueryForList(..)) ;
 before(RequestScope request, ResultSet rs, int skipResults, int maxResults, RowHandlerCallback callback) :
     handleResults() && args(request, rs, skipResults, maxResults, callback) {
 
   SessionScope session = request.getSession();
   // rs를 이용하여 메타데이터 정보가져오기
    session.setAttribute("metadata", 메타정보);
  }
 after(RequestScope request, Transaction trans, Object parameterObject,
   int skipResults, int maxResults) returning(List list):
  executeQueryForList() && args(request, trans, parameterObject, skipResults, maxResults) {
  SessionScope session = request.getSession();
  //메타정보 = session.getAttribute("metadata");
  //메타정보를 조회된 리스트와 함께 반환

 }
}



사용자 삽입 이미지



 
사용자 삽입 이미지

크리에이티브 커먼즈 라이센스
Creative Commons License
Writer profile
그래도 꿈이 있어서 행복하다^^
2008/01/10 16:53 2008/01/10 16:53

(go to top)