본문 바로가기
SpringFramework | SpringBoot

Spring에서 서버 재기동없이 mybatis의 XML파일을 바로 적용하는 법

by Lcoding 2023. 12. 26.
반응형

 

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파일을 바로 적용하는 법에 대해서 알아보았습니다.

반응형

loading