요즘 ORM으로는 하이버네이트, JPA등 많이 사용하고 있으나, 역시 SI 쪽은 mybatis(ibatis)를 많이 사용된다.

문제는 mybatis는 xml로 sql을 관리하고 있는데 보통 조금 바꿀때 마다 서버를 재구동 시켜야 되는 문제가 된다.

시스템이 클 경우 재시작시 오랜 시간 걸리고 session 사용시 또 로그인을 해야 하는듯 개발의 흐름이 끊어지는 문제가 많이 발생한다.

예전에 ibatis를 사용 했을시에도 그런 부분이 많이 불편했었는데, 예전 대우정보시스템의 JCF 프레임워크에서 사용된다고 Refresh 되는 클래스 소스가 한번 공개 된적이 있었다. ( 몇년전인지 기억은 안나지만, 당시 인터넷 검색으로 찾았었다. )

그것이 버전이 문제인지 바로 사용이 안되어서 커스터마이징하고 사용을 잘사용 했었다.

그런데 지금 프로젝트가 mybatis로 진행하기 때문에 예전과 같은 불편함이 또 생기게 되었는데, 이 번에는 그 소스를 mybatis에 맞도로 커스터마이징 하기로 했다.  

일단 사전 조건은 

JDK 1.5 이상, Spring, mybatis, spring-mybatis 라이브러리가 설치되어 있는 환경에서만 된다.


일단 기존 Spring 에서 mybatis 설정을 보겠다.

보통 sqlSessionFactory를 이렇게 설정 한다. 

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" p:mapperLocations="classpath*:패키지경로/**/mapper.xml" p:configLocation="classpath:/MapperConfig.xml" p:dataSource-ref="dataSource"/>

classpath*:패키지경로/**/mapper.xml  <- 이부분이 재로딩될 xml 파일 경로

이부분에서 굵게 표시한 class 부분만 새로 만든 클래스로 바꾸면 모든게 해결된다.

<bean id="sqlSessionFactory" class="패키지경로.RefreshableSqlSessionFactoryBean" p:mapperLocations="classpath*:패키지경로/**/mapper.xml" p:configLocation="classpath:/MapperConfig.xml" p:dataSource-ref="dataSource" />

RefreshableSqlSessionFactoryBean.java

import java.io.IOException;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import java.util.Timer;

import java.util.TimerTask;


import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import org.apache.ibatis.session.SqlSessionFactory;

import org.mybatis.spring.SqlSessionFactoryBean;

import org.springframework.beans.factory.DisposableBean;

import org.springframework.core.io.Resource;


import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantReadWriteLock;


/**

 * mybatis mapper 자동 감지 후 자동으로 서버 재시작이 필요 없이 반영

 *

 * @author

 *

 */

public class RefreshableSqlSessionFactoryBean extends SqlSessionFactoryBean implements DisposableBean {


private static final Log log = LogFactory .getLog(RefreshableSqlSessionFactoryBean.class);


private SqlSessionFactory proxy;

private int interval = 500;


private Timer timer;

private TimerTask task;


private Resource[] mapperLocations;


/**

 * 파일 감시 쓰레드가 실행중인지 여부.

 */

private boolean running = false;


private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

private final Lock r = rwl.readLock();

private final Lock w = rwl.writeLock();


public void setMapperLocations(Resource[] mapperLocations) {

super.setMapperLocations(mapperLocations);

this.mapperLocations = mapperLocations;

}


public void setInterval(int interval) {

this.interval = interval;

}


/**

 *

 * @throws Exception

 */

public void refresh() throws Exception {

if (log.isInfoEnabled()) {

log.info("refreshing sqlMapClient.");

}

w.lock();

try {

super.afterPropertiesSet();


} finally {

w.unlock();

}

}


/**

 * 싱글톤 멤버로 SqlMapClient 원본 대신 프록시로 설정하도록 오버라이드.

 */

public void afterPropertiesSet() throws Exception {

super.afterPropertiesSet();


setRefreshable();

}


private void setRefreshable() {

proxy = (SqlSessionFactory) Proxy.newProxyInstance(

SqlSessionFactory.class.getClassLoader(),

new Class[] { SqlSessionFactory.class },

new InvocationHandler() {

public Object invoke(Object proxy, Method method,

Object[] args) throws Throwable {

// log.debug("method.getName() : " + method.getName());

return method.invoke(getParentObject(), args);

}

});


task = new TimerTask() {

private Map<Resource, Long> map = new HashMap<Resource, Long>();


public void run() {

if (isModified()) {

try {

refresh();

} catch (Exception e) {

log.error("caught exception", e);

}

}

}


private boolean isModified() {

boolean retVal = false;


if (mapperLocations != null) {

for (int i = 0; i < mapperLocations.length; i++) {

Resource mappingLocation = mapperLocations[i];

retVal |= findModifiedResource(mappingLocation);

}

}


return retVal;

}


private boolean findModifiedResource(Resource resource) {

boolean retVal = false;

List<String> modifiedResources = new ArrayList<String>();


try {

long modified = resource.lastModified();


if (map.containsKey(resource)) {

long lastModified = ((Long) map.get(resource))

.longValue();


if (lastModified != modified) {

map.put(resource, new Long(modified));

modifiedResources.add(resource.getDescription());

retVal = true;

}

} else {

map.put(resource, new Long(modified));

}

} catch (IOException e) {

log.error("caught exception", e);

}

if (retVal) {

if (log.isInfoEnabled()) {

log.info("modified files : " + modifiedResources);

}

}

return retVal;

}

};


timer = new Timer(true);

resetInterval();


}


private Object getParentObject() throws Exception {

r.lock();

try {

return super.getObject();


} finally {

r.unlock();

}

}


public SqlSessionFactory getObject() {

return this.proxy;

}


public Class<? extends SqlSessionFactory> getObjectType() {

return (this.proxy != null ? this.proxy.getClass()

: SqlSessionFactory.class);

}


public boolean isSingleton() {

return true;

}


public void setCheckInterval(int ms) {

interval = ms;


if (timer != null) {

resetInterval();

}

}


private void resetInterval() {

if (running) {

timer.cancel();

running = false;

}

if (interval > 0) {

timer.schedule(task, 0, interval);

running = true;

}

}


public void destroy() throws Exception {

timer.cancel();

}

}


만약에 재로딩 되는 시간을 바꾸고 싶으면 

<bean id="sqlSessionFactory" class="패키지경로.RefreshableSqlSessionFactoryBean" p:mapperLocations="classpath*:kr/web/**/mapper.xml" :configLocation="classpath:/MapperConfig.xml" p:dataSource-ref="dataSource" p:interval="1000" />

 p:interval="1000" 이부분을 수치를 정해주면된다. ( 디폴트는 500, 단위 ms )


이제 설정을 서버를 시작해서 위에 로케이션 해당하는 mapper.xml ( sql이 있는 xml , 설정에 따라 다름)에서

sql을 바꿔 보자 그리고 클라이언트에서 바뀐지 확인해보자

큰 문제가 없다면 반영될것이라고 생각된다. 


아 단 운영 시스템에 사용은 보장 못합니다~ 개발시에서만 사용하세요~


이 소스는 예전에 인터넷에서 나돌던 RefreshableSqlMapClientFactoryBean 소스를 커스터마이징한 소스인데 문제가 있다면 연락주시길바랍니다.



원문:http://sbcoba.tistory.com/entry/Spring-mybats-%EC%82%AC%EC%9A%A9%EC%8B%9C-%EC%9E%AC%EC%8B%9C%EC%9E%91-%EC%97%86%EC%9D%B4-%EC%84%9C%EB%B2%84-%EB%B0%98%EC%98%81

Posted by gofly

댓글을 달아 주세요

요즘 구상중인 Application이다.

내용은 이렇다 DB를 앞으로 Data 표준이될 XML을 이용하여 Source로 삼고 이것을 조회하거나 할 때, 개발자들이 흔히 이용하는 SQL로 조회하여 보여주고, DML로 데이터를 가공하는 Application이다.

이점은
1.Smart phone과 같은 소형 기기에서 간략하게 사용할 수 있는 소형 DB를 구현할 수 있음
2.Data를 Import시키는 것이 자유롭다.

특징은
1.특정 공간에 두고 App을 구동하며 Data(XML file)를 자동으로 읽어와 조회할 수 있게 해준다.
2.하나의 XML은 하나의 Table이다.
3.SQL언어로 데이터를 조회하기 때문에 별도의 교육이 필요하지 않다.

다음은 mind map으로 구성해본 요소들이다.

XaSQL Program Mind map image

XaSQL Mind map


1. Data Access Method는 Data를 import하거나, create했을 때, 이것을 읽어 오는 기술을 제공합니다.
    ㄱ.Import 방식의 데이터 추가:XML을 복사해서 입력하는 것이다.
    ㄴ.Create 방식의 데이터 추가: DDL언어를 이용하여 XML파일을 만들고, 컬럼은 DTD파일로 저장하여 새로 만들        어진 XML          Data의 컬럼을 정의 합니다.

2. Data Manufacture Method는 Data를 조회하거나 가공하는 기술을 제공합니다.
   ㄱ.SQL형식의 언어와 같은 방식의 언어로 XML 데이터를 조회한다.
   ㄴ.SQL형식의 언어와 같이 데이터를 조작(Insert, Update, Delete)을 한다.

3. Data View Method는 DMM(Data Manufacture Method)에서 가공 또는 조회되는 데이터를 보여주는 기술 제공합니다.
  ㄱ.View All Column: 서로 상이할 수 있는 데이터의 Entity를 모두 컬럼화 하여 해당하는 데이터를 Row의 Column에 보여줍니다.
  ㄴ.View Average Column: 평균적 즉, 일반적으로 보여줘야 할 데이터당 Entity를 조회하여 컬럼을 만들어 Row의 Column에 보       여줍니다.

예시

Text.xml

<Test>
  <Data>
    <name>홍길동</name>
    <age>18</age>
    <height>183</height>
    <weight>73</weight>
    <sex>Male</sex>
  </Data>
  <Data>
    <name>김부자</name>
    <age>56</age>
    <height>153</height>
    <weight>73</weight>
    <sex>Female</sex>
  </Data>
</Test>

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

*명령어 입력-표준 SQL형식을 따른다*

SELECT * FROM Text /*<--Text는 Text.xml을 말함*/;

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

*실행결과*

   | name | age | height | weight | sex |              /*<==각 데이터의 Entity를 column화 함*/
1   홍길동   18       183       73       Male            /*<==각 데이터를 Column에 맞게 나열함
2   김부자   56       153       73      Female

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

이렇게 동작하는 프로그램이다.

'Idea Story' 카테고리의 다른 글

요즘 구상 중인 XML as Sequence Query Language : XaSQL  (0) 2010.05.15
Posted by gofly

댓글을 달아 주세요