- redirect를 사용하여 파라미터 담아서 넘겨주기 -


웹 개발 진행 중에 redirect를 사용하여 파라미터를 넘겨줘야 하는 경우가 있으니 그 방법에 대해 알아보자.

 

우선 알아둬야할것은

 

RedirectAttributes 인터페이스를 통해서 전달하게 되는데

 

파라미터를 담아서 넘겨줄 때 사용하는 함수는 총 3개로 각각

  • addAttribute
  • addAllAttributes
  • addFlashAttribute

이렇게 존재하는데 이 게시글에서는 "addAttribute"와 "addFlashAttribute"에 대해서만 다루고자 한다.

 

우선 예제를 한번 보고 그다음 둘의 차이와 특징에 대해 설명해보자.

 

▶예제 1)

@RequestMapping(value = "/mine.do", method = RequestMethod.POST)
public String mine(RedirectAttributes redirect) {

	redirect.addAttribute("mine", "record");
	redirect.addFlashAttribute("msg", "나만의 기록들");
    
	return "redirect:/record.do";
}
    

@RequestMapping(value="/record.do")
public String record(@RequestParam("mine") String mine, MineVO mineVO, Model model){
	
    ...
    
    return "mine";
}

 

위에서 설명한 것처럼 RedirectAttributes 인터페이스를 사용하여 파라미터를 담아서 넘겨주는데

addAttribute와 addFlashAttribute의 차이점은 뭘까?

 

addAttribute는 GET 방식이며 페이지를 새로고침 한다 해도 값이 유지된다.
addFlashAttribute는 POST 방식이며 이름처럼 일회성 데이터라 새로고침 하면 값이 사라진다.

 

추가적으로 redirect를 사용하다 보면 알겠지만

값을 받는 방식은 @RequestParam을 사용해도 좋고, 대부분 VO를 통해서 받고는 한다.

 

addAttribute와 addFlashAttribute는 둘 다

당연히 Obejct를 전송할 수 있으므로 map 이라던가 VO를 그대로 전송해도 된다.

(테스트를 해보니 VO를 전송하면 VO로 받을 수 있더라.)

반응형

- Open Resource 파일 검색 시 target 폴더 제외하기 -


이클립스 기반의 웹 개발을 할 때 개인적인 생각으로 가장 많이 사용한다 생각하는

Open Resource 파일 검색(ctrl + shift + r) 기능인데

 

이 기능을 사용할 때 maven build 시 생성되는 target 폴더 역시 검색 대상이기 때문에 사용하기 불편하고 아무생각없이 target에 있는 파일을 수정하게되는 실수가 자주 발생한다.

 

그래서 Open Resource 검색 시 target 폴더를 제외하는 두가지 방법에 대해 알아보자.


1. target resource derived

이 방법은 설정하기가 매우 간편하지만, maven 멀티 모듈 프로젝트를 사용하는 곳에서는 추천하지 않는다. 일이 두배가 되기 때문이다. 그 외는 사용해도 상관없다.

 

우선 target 폴더에 마우스 우클릭을 하여 Properties를 들어가주자.



그리고 나서 Resource 화면에서 Derived를 체크해주고 난 뒤 Apply를 해주자.



이런식으로만 설정해주면 Open Resource 검색에서 위와같이 설정해준 target은 제외가 될것이다.

 

근데 앞에서 말한 maven 멀티 모듈에서는 사용하지 말라는 것은 maven project 안에 각 프로젝트 모듈별로 target이 또 들어가기때문에 거기에도 설정해줘야하는 번거로움이 있어서 그렇다.


2. project resource filter

이 방법은 프로젝트 설정에서 resource filter를 지정해주는것인데, 1번 방법에 비해 번거롭지만 maven 멀티 모듈을 사용하는 중 이라면 추천하는 방법이다.

 

[Project] - [Properties] 로 들어가 주자.



그리고 [Resource] - [Resource Filters] 메뉴로 들어가서 [Add Filter...] 버튼을 클릭해주자.



이제 target 을 막기 위한 설정을 해보자.

Filter type : Exclude all

Applies to : Folders & All children (recursive)

Filter Details : "target"

아래 이미지 처럼 설정해주고 OK 버튼을 누른다.



마지막으로 아래 화면처럼 나오면 Apply 버튼을 눌러주고 OK 버튼을 눌러주어 끝내자.



이렇게 두가지 방법에 대해 정리하였는데

귀찮다고 넘어가지 말고 이런거 설정 한번 해놓고 편하게 개발하자.

반응형

- May be locked by another process 해결 -


[May be locked by another process]

라는 에러가 발생하면 다음과 같이 조치를 해주면 된다.

 

아래 모든 것들을 할 필요는 없고 나는 1번 톰캣 서버 클린 만으로 해결 됐다.그것만으로 안될 경우에는 전부 해주면 좋다 언제나 좋은 해결책인 클린


1) Tomcat Server Clean (톰캣 서버 클린)


2) Project Clean (프로젝트 클린)


3) Clean Tomcat Work Directory (디렉토리 클린)


해당 에러는 좀 흔한 에러로 구글에 검색해보면 대부분 같은 조치를 취하라고 되어 있을 것이다.

애초에 대부분의 에러는 클린으로 해결되므로 비슷해도 어쩔수 없는것같다.

반응형

- 이클립스 UI 아이콘 크기 조절하기 -


이클립스를 실행해보면 알겠지만 아래 이미지 처럼 여러 버튼 또는 패키지 등의 아이콘이 존재하는데

 

이러한 아이콘들 또한 크기를 조절할 수 있다.

보통 화면 해상도에 따라 보는게 차이가 있기 때문에 조절 하는 경우가 많다.

 

조절 자체는 이클립스 내부에서 하는게 아니라

제목에 있는것처럼 eclipse.ini 파일을 이용해 조절할 것이다.

 

"mine-it-record.tistory.com/307 (다양한 eclipse.ini 파일 설정)"

 

위 링크 안내대로 eclipse.ini 파일 안에 그냥 맨 마지막 줄에 아래의 옵션들을 추가해 주면된다.

-Dswt.enable.autoScale=true
-Dswt.autoScale=150
-Dswt.autoScale.method=nearest

Dswt.autoScale 옵션을 조절하여 크기 조절이 가능하다.

반응형

- mybatis foreach문 사용하기 -


mybatis 에서 동적쿼리인 foreach문을 사용하는 방법에 대해 알아보자.

코드를 보고 문법 및 사용 태그 옵션을 확인하도록하자.

 

▶예제1)

SELECT *
FROM cm_mine
WHERE 1 = 1
<if test="list != null and list.size != 0">
        AND id IN 
        <foreach item="item" collection="list" open="(" separator="," close=")">
            # <!-- list 단일 값(string,int 등)-->
            <!--list<VO> 일 경우 : #-->
        </foreach>
</if>

 

직접 사용하는 형식에 대해서는 이제 알았으니 자세하게 사용되는 태그 옵션들에 대해 알아보자.

 

▷options

collection : 전달받은 인자/변수 즉, list 같은 배열 형식의 변수
item : 배열에서의 값 하나하나를 의미한다.(script 나 java에서 foreach를 사용해봤으면 item,index를 알것이다.)
index : 현재 for문을 돌고있는 index를 의미한다.(0,1,2,3, ...)
open : foreach문이 시작될때 앞에 삽입될 문자열
separator : foreach문에서 반복되는 값 사이사이에 넣어줄 문자열
close : foreach문이 종료될때 앞에 삽입될 문자열

 

실제로 위 동적쿼리가 어떤식으로 실행되는지를 확인해보자면

List<String> list; //value : 'John', 'Ho', 'Mine'

이렇게 3개의 값을 가진 list변수가 존재하고 위 예제를 통해 다룬 쿼리문을 실행하게 되면

실제 쿼리문을 아래처럼 작성되어 실행된다.

SELECT *
FROM cm_mine
WHERE 1 = 1
    AND id IN ('John', 'Ho', 'Mine')

동적쿼리문은 실제로 많이 사용되기 때문에 까먹지말자.

반응형

- .xml 쿼리문 수정 후 서버 재시작 없이 바로 적용하기 -


서버를 실행중에 쿼리문에서 에러가 발생할 경우

해당 쿼리문을 수정하게되면 서버를 멈추고 다시 시작해야하는 번거로움이 발생하는데

 

서버 재실행 없이 바로 적용시켜 사용하는 방법에 대해 알아보자.


자바 파일 하나를 생성하고 context-mapper.xml을 수정할 것이다.

 

- 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 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;

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();
    }
}

 파일을 만들었으면 이제 xml파일을 수정해보자.

 

- context-mapper.xml

<bean id="sqlSessionFactory" class="com.mine.RefreshableSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:/mybatis/mapper-config.xml" />
<property name="interval" value="500" />
<property name="mapperLocations">
  <list>
 	 <value>classpath:/com/mapper/**/*.xml</value>
  </list>
</property>
</bean>

<!-- scan for mappers and let them be autowired -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<property name="basePackage" value="cbsp" />
</bean>

 

context-mapper.xml 파일에 보면 sqlSessionFactory 부분이 있는데 이 부분을 알맞게 수정해서 사용하면 된다.


이런식으로 설정이 끝났으면

쿼리문을 수정하고 저장하게되면 아래와같이 console창에 뜨면서 수정한 내용이 바로 적용된것이다.



하지만 만약에 아무 콘솔도 뜨지 않는다면 아래 부분을 한번 확인해보자.
[Project] - [Build Automatically]

부분이 체크가 안되어있으면 작동이 안될수 있으니 체크해두자.

 

[참고]

- https://bryan7.tistory.com/117

https://zzang9iu.tistory.com/29

반응형