gnusraun

Egov RefreshableSqlSessionFactoryBean 서버 재시작 없이 XML 반영 본문

Backend/Egov

Egov RefreshableSqlSessionFactoryBean 서버 재시작 없이 XML 반영

gnusraun 2023. 5. 14. 08:05
728x90

TOMCAT 서버 기준 XML 쿼리를 수정하면 서버를 재시작해야 한다.

이러한 번거로운 작업을 줄이기 위해 서버 재시작 없이도 XML 쿼리를 반영하는 RefreshableSqlSessionFactoryBean 사용한다.

 

 

:: RefreshableSqlSessionFactoryBean.java

package egovframework.example.cmmn.config;

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.ibatis.session.SqlSessionFactory; 
import org.mybatis.spring.SqlSessionFactoryBean; 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean; 
import org.springframework.core.io.Resource;

import java.util.concurrent.locks.Lock; 
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class RefreshableSqlSessionFactoryBean extends SqlSessionFactoryBean implements DisposableBean {
	private static final Logger logger = LoggerFactory.getLogger(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;
	} 
	
	public void refresh() throws Exception {
		if(logger.isInfoEnabled()) {			
			logger.info("> Refresh SqlMapper");
			logger.info("======================================================================================");
		}
		
		w.lock();
		
		try {
			super.afterPropertiesSet();
		} finally { 
			w.unlock(); 
		} 
	} 
	
	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) { 
						logger.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());	// 전체경로 
							modifiedResources.add(resource.getFilename());		// 파일명							
							
							retVal = true;
						} 
					} else { 
						map.put(resource, new Long(modified));
					} 
				} catch (IOException e) { 
					logger.error("caught exception", e); 
				}
				
				if(retVal) {
					if(logger.isInfoEnabled()) {						
						logger.info("======================================================================================");
						logger.info("> Update File name : " + 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();
	}
}

 

 

:: context-mapper.xml

<!-- class 경로 추가 -->
<bean id="sqlSession" class="egovframework.example.cmmn.config.RefreshableSqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="classpath:/egovframework/sqlmap/oracle/sql-mapper-config.xml" />
    <property name="mapperLocations" value="classpath:/egovframework/sqlmap/oracle/mappers/**/*.xml" />
    <property name="interval" value="1000" />
</bean>

 

 

출처 - https://shxrecord.tistory.com/196

728x90