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 = 100;
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, modified);
modifiedResources.add(resource.getDescription());
retVal = true;
}
} else {
map.put(resource, 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();
}
}
2.MyBatisConfig class를 생성한다.
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import javax.annotation.Resource;
import javax.sql.DataSource;
/**
* Mybatis 의 XML에서 쿼리 변경 시 was 재기동 없이 xml을 reload 하는 class
*/
@Configuration
@MapperScan(basePackages = "com.kona.framework.dao") // Mapper 인터페이스가 있는 패키지 경로 설정
public class MyBatisConfig {
@Resource
ApplicationContext applicationContext;
/**
* local 프로필에서만 사용하는 xml 파일 변경시 was 재기동 없이 refresh 하는 메서드
* @param dataSource
* @return
* @throws Exception
*/
@Profile("local")
@Bean
public SqlSessionFactory refreshableSqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new RefreshableSqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setVfs(SpringBootVFS.class);
factoryBean.setConfigLocation(applicationContext.getResource("classpath:mybatis-config.xml"));
factoryBean.setMapperLocations(applicationContext.getResources("classpath:mybatis/mapper/mariadb/*.xml"));
factoryBean.setTypeAliasesPackage("com.kona.framework.domain.entity");
((RefreshableSqlSessionFactoryBean) factoryBean).setInterval(100);
return factoryBean.getObject();
}
/**
* xml 파일 reload는 local에서 개발용으로만 쓰고 그외는 일반적인 sqlsessionFactory를 사용한다.
* @param dataSource
* @return
* @throws Exception
*/
@Bean
@Profile("!local")
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setConfigLocation(applicationContext.getResource("classpath:mybatis-config.xml"));
bean.setMapperLocations(applicationContext.getResources("classpath:mybatis/mapper/mariadb/*.xml"));
bean.setTypeAliasesPackage("com.kona.framework.domain.entity");
return bean.getObject();
}
}
3.devtools 적용
pom.xml
<!--hot reload devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
application-local.yml에 devtools 적용
spring:
devtools:
livereload:
enabled: true # resource 수정시 재기동 옵션
restart:
enabled: false # classpath 에 속한 파일 Ex. class 수정시 재기동 옵션
4.intellij auto compile 저굥
4.1 Compiler > Build project automatically

4.2 Advanced Settings > Allow auto-make to start even if developed application is currently running

