Forked from sbcoba/RefreshableSqlSessionFactoryBean.java
Last active
July 24, 2019 01:45
-
-
Save composite/27de4a82397fdb03b38c22a6ffaa64a5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 sbcoba | |
*/ | |
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.util; | |
import java.io.IOException; | |
import java.lang.reflect.Field; | |
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.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Properties; | |
import java.util.Set; | |
import java.util.Timer; | |
import java.util.TimerTask; | |
import java.util.concurrent.locks.Lock; | |
import java.util.concurrent.locks.ReentrantReadWriteLock; | |
import org.apache.commons.logging.Log; | |
import org.apache.commons.logging.LogFactory; | |
import org.apache.ibatis.builder.xml.XMLConfigBuilder; | |
import org.apache.ibatis.executor.ErrorContext; | |
import org.apache.ibatis.session.Configuration; | |
import org.apache.ibatis.session.SqlSessionFactory; | |
import org.mybatis.spring.SqlSessionFactoryBean; | |
import org.springframework.beans.factory.DisposableBean; | |
import org.springframework.core.io.ClassPathResource; | |
import org.springframework.core.io.Resource; | |
/** | |
* mybatis mapper 자동 감지 후 자동으로 서버 재시작이 필요 없이 반영 | |
* | |
* [참고] 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 | |
*/ | |
public class RefreshableSqlSessionFactoryBean2 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 configLocation; | |
private Resource[] mapperLocations; | |
private Properties configurationProperties; | |
/** | |
* Set optional properties to be passed into the SqlSession configuration, as alternative to a | |
* {@code &lt;properties&gt;} tag in the configuration xml file. This will be used to | |
* resolve placeholders in the config file. | |
*/ | |
public void setConfigurationProperties(Properties sqlSessionFactoryProperties) { | |
super.setConfigurationProperties(sqlSessionFactoryProperties); | |
this.configurationProperties = sqlSessionFactoryProperties; | |
} | |
/** | |
* 파일 감시 쓰레드가 실행중인지 여부. | |
*/ | |
private boolean running = false; | |
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); | |
private final Lock r = rwl.readLock(); | |
private final Lock w = rwl.writeLock(); | |
public void setConfigLocation(Resource configLocation) | |
{ | |
super.setConfigLocation(configLocation); | |
this.configLocation = configLocation; | |
} | |
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 (log.isInfoEnabled()) | |
{ | |
log.info("refreshing SqlSessionFactory."); | |
} | |
w.lock(); | |
try | |
{ | |
super.afterPropertiesSet(); | |
} finally | |
{ | |
w.unlock(); | |
} | |
} | |
/** | |
* | |
* 싱글톤 멤버로 SqlSessionFactory 원본 대신 프록시로 설정하도록 오버라이드. | |
*/ | |
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); | |
if( retVal ) | |
break; | |
} | |
} | |
else if( configLocation != null ) | |
{ | |
Configuration configuration = null; | |
XMLConfigBuilder xmlConfigBuilder = null; | |
try | |
{ | |
xmlConfigBuilder = new XMLConfigBuilder(configLocation.getInputStream(), null, configurationProperties); | |
configuration = xmlConfigBuilder.getConfiguration(); | |
} catch (IOException e) | |
{ | |
e.printStackTrace(); | |
} | |
if (xmlConfigBuilder != null) { | |
try { | |
xmlConfigBuilder.parse(); | |
// Configuration 클래스의 protected member field 인 loadedResources 를 얻기 위해 reflection 을 사용함. | |
Field loadedResourcesField = Configuration.class.getDeclaredField("loadedResources"); | |
loadedResourcesField.setAccessible(true); | |
@SuppressWarnings("unchecked") | |
Set<String> loadedResources = (Set<String>) loadedResourcesField.get(configuration); | |
for (Iterator<String> iterator = loadedResources.iterator(); iterator.hasNext();) | |
{ | |
String resourceStr = (String) iterator.next(); | |
if( resourceStr.endsWith(".xml") ) { | |
Resource mappingLocation = new ClassPathResource(resourceStr); | |
retVal |= findModifiedResource(mappingLocation); | |
if( retVal ) { | |
break; | |
} | |
} | |
} | |
} catch (Exception ex) { | |
throw new RuntimeException("Failed to parse config resource: " + configLocation, ex); | |
} finally { | |
ErrorContext.instance().reset(); | |
} | |
} | |
} | |
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(); | |
} | |
} |
RefreshableSqlSessionFactoryBean2
세팅
이 버전은 mapperLocations
속성이 없을 때 (주로 mapper config 내 mapper 정의)를 대응한 버전.
mappconfig 파일이 아래와 같을 때,
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="defaultStatementTimeout" value="25000"/>
</settings>
<mappers>
<!-- Apps Mapper File -->
<mapper resource="com/example/dao/apps/AppsMapper.xml"/>
<!-- Common Mapper File -->
<mapper resource="com/example/dao/common/CommonMapper.xml"/>
<!-- User Mapper File -->
<mapper resource="com/example/dao/user/UserMapper.xml"/>
</mappers>
</configuration>
config는 아래와 같이 설정할 수 있다.
<bean id="sqlSessionFactory" class="패키지경로.RefreshableSqlSessionFactoryBean2"
p:configLocation="classpath:/MapperConfig.xml"
p:dataSource-ref="dataSource"
p:interval="1000" />
<!-- scan for mappers and let them be autowired -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.dao" />
</bean>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
RefreshableSqlSessionFactoryBean
세팅class="org.mybatis.spring.SqlSessionFactoryBean"
부분을class="패키지경로.RefreshableSqlSessionFactoryBean"
로 바꾸면 끝.파일 모니터리 간격을 바꿀 때
interval="(밀리초 숫자)"
설정.출처: https://sbcoba.tistory.com/16