spring context support : 어플리케이션 컨텍스트 컨테이너에서 사용되지만 자주 사용되지 않는 부가적인 기능을 분리해 놓은 것으로 캐쉬,메일,스케줄링,UI 등과 관련된 것이 있음 spring-context 를 포함하고 있어 dependency 설정 과정에서
spring context support로 선언 하면 편리하니 참고하세요
----------------- 아래 부터는 가지고 온 것입니다. 문제 시 삭제 하도록 하겠습니다. -------------------
|
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 java.util.concurrent.locks.Lock; |
|
import java.util.concurrent.locks.ReentrantReadWriteLock; |
|
|
|
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; |
|
|
|
public class RefreshableSqlSessionFactoryBean extends SqlSessionFactoryBean implements DisposableBean { |
|
|
|
private static final Logger LOG = LoggerFactory.getLogger(RefreshableSqlSessionFactoryBean.class); |
|
|
|
private SqlSessionFactory proxy; |
|
private int interval = 1000; |
|
|
|
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 { |
|
w.lock(); |
|
try { |
|
super.afterPropertiesSet(); |
|
} finally { |
|
w.unlock(); |
|
} |
|
|
|
LOG.info("sqlMapClient refreshed."); |
|
} |
|
|
|
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 { |
|
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) { |
|
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(); |
|
} |
|
} |
일단은 이 유명한 RefreshableSqlSessionFactoryBean 클래스를 적당한 곳에 생성을 해 놓는다.
그리고 SqlSessionFactory를 설정하는 곳에서 다음과 같이 설정을 해준다.
|
<!--<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">--> |
|
<bean id="sqlSessionFactory" class="com.oingdaddy.RefreshableSqlSessionFactoryBean"> |
|
<property name="dataSource" ref="dataSource" /> |
|
<property name="configLocation" value="classpath:/mybatis-config.xml" /> |
|
<property name="mapperLocations" value="classpath:/mapper/**/*.xml" /> |
|
<property name="interval" value="1000" /> |
|
</bean> |
1 line에 있는 기존의 org.mybatis.spring.SqlSessionFactoryBean 대신 RefreshableSqlSessionFactoryBean을 끼워준다. 그리고 RefreshableSqlSessionFactoryBean 에서 설정 가능한 interval이라는 속성도 넣어준다. Refresh를 해주는 주기이다. (ms) 즉 1초다마 계속 Refresh를 해주겠다는거다. 이정도면 고치고 저장을 하면 거의 실시간으로 변경된 내용이 반영이 된다고 보면 된다.
이렇게 설정까지 만들었다면 WAS가 기동된 상태에서 특정 쿼리를 수정해보자. 그리고 바로 결과가 잘 반영이 되는지 확인을 해보자. RefreshableSqlSessionFactoryBean 관련된 로그가 나오며 update 되었다는 문구가 나오면 성공이다.
Class<SpringJUnit4ClassRunner> cannot be resolved to a type STS 20230926 (0) | 2023.09.26 |
---|---|
Java 버전별 major version 번호. (0) | 2023.09.09 |
Factory Method Pattern(팩토리 메소트 패턴) (0) | 2023.04.10 |
SOLID 원칙 (0) | 2023.04.04 |
AOP 설명 (0) | 2023.03.17 |
댓글 영역