Spring에서 서버 재기동없이 mybatis의 XML파일을 바로 적용하는 법에 대해서 알아보겠습니다.
아래의 설명은 mybatis의 설정을 xml 파일 방식이 아닌 java config 방식으로 설정한 경우입니다.
1. 첫번째로 적당한 위치에 RefreshableSqlSessionFactoryBean.java 파일을 만들어줍니다.
저는 util 디렉토리를 생성하여 그안에 넣어 주었습니다.
2. 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 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();
}
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() throws Exception {
if (this.proxy == null) {
setRefreshable();
}
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();
}
}
3. mybatis의 설정을 xml 파일 방식이 아닌 java config 방식으로 설정하였다면, config.java파일이 프로젝트 존재할 것 입니다.
필자는 MyBatisConfig.java라는 이름으로 생성하였습니다. 그 파일에 아래와 같이 작성해줍니다.
import com.prepot.util.RefreshableSqlSessionFactoryBean;
import lombok.RequiredArgsConstructor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
@RequiredArgsConstructor
@Lazy
@EnableTransactionManagement
@MapperScan(basePackages = "com.prepot.repository.mybatis")
public class MyBatisConfig {
private final ApplicationContext context;
private final DataSource dataSource;
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactory = new RefreshableSqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setMapperLocations(
context.getResources(
"classpath:/mappers/*.xml"
));
sessionFactory.setTypeAliasesPackage( "com.prepot.domain" );
((RefreshableSqlSessionFactoryBean) sessionFactory).setInterval(1000);
return sessionFactory.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception {
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
sqlSessionTemplate.getConfiguration().setMapUnderscoreToCamelCase(true);
return sqlSessionTemplate;
}
@Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource);
}
}
위의 작성내용에서 ((RefreshableSqlSessionFactoryBean) sessionFactory).setInterval(1000); 부분이 실제 리프레쉬되는 시간을 설정한 부분입니다.
ms 단위이므로 알맞게 적절히 조정하여 사용하시면 됩니다.
이상으로 Spring에서 서버 재기동없이 mybatis의 XML파일을 바로 적용하는 법에 대해서 알아보았습니다.
'SpringFramework | SpringBoot' 카테고리의 다른 글
톰캣(Tomcat) 포트 충돌시 죽이는 법 (1) | 2024.04.27 |
---|---|
<![CDATA[ ]]>의사용 (0) | 2023.12.27 |
Spring에서 실제 동작한 쿼리를 로그에 찍는법 (0) | 2023.12.25 |
Spring에서 정적 리소스 서버 재기동 없이 바로 적용하는법 (0) | 2023.12.22 |
ThreadLocal 과 동시성 문제 (0) | 2023.12.12 |