파워포인트로 도형이나 모형을 그릴 경우 아주 유용한 툴 중에 하나이다.
처음 접했던 7년전이다.
그 때 프로젝트 산출물 작업을 할 때 아주 유용하게 활용을 했던 기억이 생생하다.
과거에 사용했을땐 파워포인트 2010버젼에서 사용을 했었는데..
시간이 많이 흐른 지금은 과거의 라이센스로는 사용이 불가능해서 새롭게 다시 소개글을 작성하는 중이다.
PPT에서 화면을 설계할 때 자주쓰는 도형을 미리 만들어 두고 필요할 때 마다 찾아서 사용하면 된다.
이 것(PowerMocker)을 이용하게 되면 웹이나, 모바일의 기본포멧은 물론 BI, 커서, 이미지 등 다양한 것들을
많이 제공해 주는 것을 확인할 수 있다. 이로인해 처음부터 하나 하나 그려오던 것을
일부만 바꾸면 되기 때문에 굉장히 업무효율이 올라간다.
=파워목업 트라이얼버젼=
위의 링크로 접속을 하면 트라이얼버젼을 다운로드할 수 있으며
, 직접 구매하게 될 경우 59.99불, 한화기준 약 7만원에 라이센스를 구매할 수 있다.
(개인이용시, 기업에서 이용할 시 비용은 달라짐)
=실행화면=
이 편리한 툴의 제작자, 울프 소프트에서는 무료로 라이센스를 제공하고 있다.
블로거, 기자, 마이크로소프트 MVP에게 이 기회를 제공한다.
과거에 한 번 받았던 경험이 있기때문에 이 포스트로 무료라이센스를 또 다시 요청해보려고 한다.
[무료라이센스받는방법]
1. 평가판을 다운로드해서 사용
2. 정기적으로 게시글이 업로드 되고, 적당한 방문객 유지
3. 블로그 주제에 웹, 모바일이 연관되어 있어야 함.
4. 게시물은 원본일 것, 그리고 검색 가능
5. support@powermockup.com 에 메일을 보내면 싱글 라이센스를 받을 수 있다.
이전에 받았던 경험이 있기때문에 별다른 문제가 없다면 받을 수 있지 않을까 생각한다.
요즘 ORM으로는 하이버네이트, JPA등 많이 사용하고 있으나, 역시 SI 쪽은 mybatis(ibatis)를 많이 사용된다.
문제는 mybatis는 xml로 sql을 관리하고 있는데 보통 조금 바꿀때 마다 서버를 재구동 시켜야 되는 문제가 된다.
시스템이 클 경우 재시작시 오랜 시간 걸리고 session 사용시 또 로그인을 해야 하는듯 개발의 흐름이 끊어지는 문제가 많이 발생한다.
예전에 ibatis를 사용 했을시에도 그런 부분이 많이 불편했었는데, 예전 대우정보시스템의 JCF 프레임워크에서 사용된다고 Refresh 되는 클래스 소스가 한번 공개 된적이 있었다. ( 몇년전인지 기억은 안나지만, 당시 인터넷 검색으로 찾았었다. )
그것이 버전이 문제인지 바로 사용이 안되어서 커스터마이징하고 사용을 잘사용 했었다.
그런데 지금 프로젝트가 mybatis로 진행하기 때문에 예전과 같은 불편함이 또 생기게 되었는데, 이 번에는 그 소스를 mybatis에 맞도로 커스터마이징 하기로 했다.
일단 사전 조건은
JDK 1.5 이상, Spring, mybatis, spring-mybatis 라이브러리가 설치되어 있는 환경에서만 된다.
일단 기존 Spring 에서 mybatis 설정을 보겠다.
보통 sqlSessionFactory를 이렇게 설정 한다.
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" p:mapperLocations="classpath*:패키지경로/**/mapper.xml" p:configLocation="classpath:/MapperConfig.xml" p:dataSource-ref="dataSource"/>
classpath*:패키지경로/**/mapper.xml <-이부분이 재로딩 될 xml 파일경로 ( 자신의 프로젝트에 따라 변경 )
이부분에서 굵게 표시한 class 부분만 새로 만든 클래스로 바꾸면 모든게 해결된다.
<bean id="sqlSessionFactory" class="패키지경로.RefreshableSqlSessionFactoryBean" p:mapperLocations="classpath*:패키지경로/**/mapper.xml" p:configLocation="classpath:/MapperConfig.xml" p:dataSource-ref="dataSource" />
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; | |
/** | |
* mybatis mapper 자동 감지 후 자동으로 서버 재시작이 필요 없이 반영 | |
* | |
* @author sbcoba | |
*/ | |
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(); | |
} | |
} |
만약에 재로딩 되는 시간을 바꾸고 싶으면
<bean id="sqlSessionFactory" class="패키지경로.RefreshableSqlSessionFactoryBean" p:mapperLocations="classpath*:패키지경로/**/mapper.xml" :configLocation="classpath:/MapperConfig.xml" p:dataSource-ref="dataSource" p:interval="1000" />
p:interval="1000" 이부분을 수치를 정해주면된다. ( 디폴트는 500, 단위 ms )
이제 설정을 서버를 시작해서 위에 로케이션 해당하는 mapper.xml ( sql이 있는 xml , 설정에 따라 다름)에서
sql을 바꿔 보자 그리고 클라이언트에서 바뀐지 확인해보자
큰 문제가 없다면 반영될것이라고 생각된다.
아 단 운영 시스템에 사용은 보장 못합니다~ 개발시에서만 사용하세요~
이 소스는 예전에 인터넷에서 나돌던 RefreshableSqlMapClientFactoryBean 소스를 커스터마이징한 소스인데 문제가 있다면 연락주시길바랍니다.
'FrameWork' 카테고리의 다른 글
[Spring, Log] 로그가 안보일때, 쿼리가 안나타날때 로그 출력하는 방법 (0) | 2019.03.23 |
---|---|
iBatis에서 AutoResultMap Error 해결방법 (0) | 2015.03.04 |
iBATIS 간단한 Tip.. (0) | 2012.10.17 |
보통 전자정부같은 Spring기반의 프로젝트를 할때 에러가 나던가 혹은 서버에서 어떤 액션이 일어나면 이클립스 콘솔창에
많은 정보들이 후두두둑 하고 올라간다.
내가 날린 쿼리가 무엇인지, 내가 요청한 URL은 무엇있지 버그나 오류가 있을땐 어떤 오류인지 온갖 정보들이 나타나는데
가끔 쿼리를 실행해도 어떤 쿼리를 실행했는지 나타나지 않는 경우가 있다. 단지 오류가 났을때만 콘솔창에 로그를 뿌려주는 경우
이럴때 버그가 있더라도 시스템에서 오류가 나지 않는다면 어떤 쿼리를 실행시켰는지 어떤 url을 요청했는지 알기 힘들게 된다.
이런경우의 대부분은 로깅 프로퍼티의 설정 레벨이 warn 정도로 되어 있는 경우 인것 같다.
정상적인 쿼리도 콘솔창에 보이게 할수 있도록 설정하는 방법을 알아보자.
log4j를 쓰던 log4j2를 쓰던 logback를 쓰던 설정 xml파일의 이름엔 [log]가 들어간다
프로젝트의 구성,설계에 따라 해당 xml파일은 리소스에 들어가는 경우도 있고 web-inf에 들어가는 경우도 있다
파일을 열어 보면
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 |
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration
xmlns:log4j="http://jakarta.apache.org/log4j/"
debug="false">
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %5p [%c] %m%n" />
</layout>
</appender>
<logger name="egovframework" additivity="false">
<level value="DEBUG"/>
<appender-ref ref="console"/>
</logger>
<!-- log SQL with timing information, post execution -->
<logger name="jdbc.sqltiming" additivity="false">
<level value="WARN" />
<appender-ref ref="console" />
</logger>
<logger name="jdbc.audit" additivity="false">
<level value="WARN" />
<appender-ref ref="console" />
</logger>
<logger name="jdbc.resultset" additivity="false">
<level value="WARN" />
<appender-ref ref="console" />
</logger>
<level value="INFO" />
<appender-ref ref="console" />
</logger>
<logger name="java.sql" additivity="false">
<level value="WARN"/>
<appender-ref ref="console"/>
</logger>
<root>
<level value="INFO" />
<appender-ref ref="console" />
</root>
</log4j:configuration>
|
cs |
이런식으로 되어 있을 것이다. 이부분은 log4j나 logback나 크게 다른 부분은 없다.
단지 로거 안에 레벨이 들어가냐 안들어가냐 어펜드가 포함되냐 안되냐 그리고 최상단에의 정보들같은 이런 약간의 차이만 있을 뿐이다.
보면 <level value="warn"/> 이라고 되어있는데
여기서 밸류값에 들어가는 종류는
DEBUG, INFO, WARN, ERROR, FATAL, ALL, OFF 가 있다.
ALL은 모든 로깅
OFF는 로깅 해제
DEBUG = 디버깅
INFO = 강조정보
WARN = 경고
ERROR = 오류
FATAL = 심각한 오류
라는 의미 이며
WARN은 경고수준 이하 레벨의 정보는 로깅하지 않는 다는 뜻이다
그러니 정상적으로 돌아가는 SQL쿼리문은 콘솔창에 나타나지 않으니
레벨의 밸류를 전부 DEBUG 로 바꿔주자
로거 name="" << 이부분의 네임은 어떤 부분에서 로그를 띄워줄지 적는 부분이다.
뭔지 모르겠고 sql만 띄우고 싶다면 jdbc.sqltiming 이부분의 레벨만 DEBUG 로 바꿔보도록 하자
설정에 따라
1
2
3
4
5
6
7 |
<appender-ref ref="console" />
</logger>
<logger name="org.mybatis" level="DEBUG " additivity="false">
<appender-ref ref="console" />
</logger> |
cs |
이런식의 스프링프레임워크나 마이바티스에 관한 로그를 띄울 수도 있다.
아무튼 원하는 부분의 레벨을 DEBUG로 설정한 후 구동시키면 정상적으로 실행되는 정보들도 출력이 된다.
additivity 이부분은
http://seosh81.info/?p=404 << 이분의 블로그에서 잘 설명이 되어 있으니 궁금하면 들어가서 보자
간단히 설명하자면 로그이벤트를 부모에게 전달하도록 하느냐 마느냐 정도의 설정부분인것 같은데
어차피 나는 야매개발자라서 정확하고 자세하겐 모른다.
이부분에서 ConversionPattern이부분의 밸류는 콘솔에 표현되는 표현식을 지정하는 부분이다.
표현식은 아래와 같다.
%m: 로그 내용 출력
%p: debug, info, warn, error, fatal 등의 priority 출력
%r: 어플이 시작 후 이벤트가 발생하는 시점까지의 경과시간 밀리세컨드로 출력
%c: package 출력
%c{n}: n(숫자) 만큼의 package를 가장 하단 부터 역으로 출력
예) %c{2} 일때 a.b.c 는 b.c 로 출력된다.
%n: 개행문자 출력. 플렛폼에 따라 \r\n 또는 \n 출력.
%d: 이벤트 발생 날짜 출력 ( 프로그램의 실행속도를 느리게 한다.)
예) %d{HH:mm:ss} 또는 %d{dd MMMM yyyy HH:mm:ss}
%C: 호출자의 클래스명 출력
예) %C{2} 일때 a.b.c.TestClass 는 c.TestClass 로 출력된다.
%M: method 이름.
%F: 프로그램 파일명.
%l: caller의 정보
%L: caller의 라인수
%x: thread와 관련된 NDC(nested diagnostic context)
%X: thread와 관련된 MDC(mapped diagnostic context)
%%: % 표시를 출력
%t: 쓰레드 이름
로그띄울줄 몰라서 들어온 사람이라면 어차피 저 표현식을 쓸일은 없을테니 그냥 이런게 있다는것만 알아두고 넘어가자.
'FrameWork' 카테고리의 다른 글
Spring + mybatis 환경에서 xml 파일 변경시 서버 재시작 없이 반영 방법 (2) | 2019.03.24 |
---|---|
iBatis에서 AutoResultMap Error 해결방법 (0) | 2015.03.04 |
iBATIS 간단한 Tip.. (0) | 2012.10.17 |