[Spring] MyBatis Batch + Transaction 을 이용한 대용량 SQL작업


마을에서 오크잡는 퀘스트 하고 있는데 갑자기


중간보스를 잡아오라는 퀘스트가 떨어졌다...



일정시간마다 라즈베리파이에서 받아온 원시데이터를 재가공하여


DB에 insert 해주어야 하는 작업


로우수가 적다면 그냥 만들겠지만 대용량 작업일 경우 답이 안나온다.


약 1만건~10만건 정도의 데이터를 날려줘야 하는데 ㅂㄷㅂㄷㅂㄷ


그래서 찾아본 방법은 Batch와 Transaction 을 이용한 대용량 sql 작업



나도 정확히 내가 뭘 한건지도 모르고 그냥 스택오버 플로우, 오키, 전자정부 뒤적거리면서 이것 저것 다 때려 박느라

필요 없는 설정이 있을 수도 있으니 아는 사람은 댓글좀 달아주시길 바랍니다.



1.  XML 설정


1) mapper 설정

context-mapper.xml 파일이나 context-sqlMap.xml 파일에


마이바티스 연동을 위해 만들어놨던 설정을 아래와 같이 바꿔준다.


9번 라인의 batch설정으로 batch 사용이 가능 하도록 하는 듯 하다.


1
2
3
4
5
6
7
8
9
10
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:/egovframework/sqlmap/example/sql-mapper-config.xml" />
        <property name="mapperLocations" value="classpath:/egovframework/sqlmap/example/mappers/mssql/*.xml" />
    </bean>
 
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate" destroy-method="clearCache">
        <constructor-arg index="0" ref="sqlSession" />      
        <constructor-arg index="1" value="BATCH" />
    </bean>
cs



2) datasource 설정

DB접속 정보 작성하는 곳에 트렌젝션메니저 설정을 하는데 이걸 해야 하는 건진 잘 모르겠다.


1
2
3
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
cs



3) dispatcher-servlet 설정


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:p="http://www.springframework.org/schema/p"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xmlns:task="http://www.springframework.org/schema/task" 
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
                   http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                http://www.springframework.org/schema/context 
                http://www.springframework.org/schema/context/spring-context-4.0.xsd
                http://www.springframework.org/schema/task
                http://www.springframework.org/schema/task/spring-task.xsd
                http://www.springframework.org/schema/mvc 
                http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
                http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
 
 
<tx:annotation-driven proxy-target-class="true"/>
cs


7번 라인과 17번 라인을 추가해주고 20번라인의 내용을 넣어 주도록 한다.



4) pom.xml


1
2
3
4
5
6
7
8
<!-- 트랜젝션 처리를 위함 -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
cs



디펜던시에 추가를 해줘야 트랜젝션 처리가 가능한건진 모르겠음. 전자정부 자체에 관련된게 이미 추가 되어 있을 수도 있고

정확히 모르겠음 저건



2. DAO or impl 작성


나는 DAO를 사용하지 않고 공통DAO하나 만들어 놓고 impl에서 바로 쿼리를 날려주는 방식을

좋아 한다.


어차피 DAO에서 특별히 해줄것도 없고.. 해줘야 하는것이 있어도 impl에서 해주면 되니까



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
    public void updateSchedulerHistoryRow(List<MinHistoryVO> historyList) {
        // TODO Auto-generated method stub
        
       // 트렌젝션 시작
        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
        long startTime = System.currentTimeMillis();
            try {
                
                for (MinHistoryVO list : historyList) {                     
                    sqlSession.update("scheduler.updateSchedulerHistoryRow", list);
                }
 
            } finally {
                sqlSession.flushStatements();
                sqlSession.close();
            }
 
        long endTime = System.currentTimeMillis();
        long resutTime = endTime - startTime;
        System.out.println("트랜젝션 배치" + " 소요시간  : " + resutTime/1000 + "(ms)");
    }
cs



난 이런식으로 작성 했다.


가끔 6번 라인에 ExecutorType.BATCH 이 매개변수를 안넣고 시작 할 수 있는데

그러면 트렌젝션 안돌고 커넥션 다 찍으면서 돌게 된다.


저렇게 해도 수만건이 돌게 되면 세션에저장될 데이터들이 넘쳐흘러서 그런지 버벅거릴때가 있는데


그럴땐 컨트롤러에서 조금씩 끊어서 날려 주도록 하자



3. controller


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
        //컨트롤러에서 일정 개수 단위로 끊어서 날려 준다.
        int insertCount = 0;
        List<MinHistoryVO> divHisList  = new ArrayList<MinHistoryVO>();
        for(int hisCount = 0, hisSize = historyList.size(); hisCount < hisSize; hisCount++){
            MinHistoryVO _tempData = new MinHistoryVO();
            _tempData = historyList.get(hisCount);
            divHisList.add(_tempData);
            if(insertCount == 1000 || hisSize-1 == hisCount){
                schedulerService.updateSchedulerHistoryRow(divHisList); // 트렌젝션
                divHisList =  new ArrayList<MinHistoryVO>();
                insertCount = 0;
            }
            else{                    
                insertCount++;
            }
        }
cs



while문 사용하는게 익숙하지 않아서 나는 for문을 주로 사용 한다.

리스트에 잔뜩 있는 데이터들을 새로운 작은 바구니에 담아서 끊어서 날려준다.








이렇게 하면 MsSql Server 2005버전 기준 1만건 insert하는데 5초정도 걸린다.


저기에  마이바티스 foreach까지 써서 벌크인서트 하면 시간은 더 단축 된다.


원시데이터를 쪼개서 60개컬럼에 따로 박아야 하기 때문에


넘겨주는 파라미터 개수 2100개 제한이 있어서 제대로 사용 못했는데


여러개로 쪼갠다음에  Mybatis foreach 돌려서 한번에 날려주면 시간은


훨씬 더더더더더 단축 된다.





oracle MyBatis에서 다중 Insert 하는 방법



mssql에서 oracle로 마이그레이션을 하는 도중 하나의 맵핑된 쿼리 안에서


여러개의 행을 insert하는 것에서 문제가 발생했다


mssql을 사용할떈 그냥 insert문을 여러개 쓰는것 만으로 한번에 여러 행을 입력 할 수 있었는데


오라클에선 구문오류가 발생하게 된다.


그렇게 방법을 찾다가 발견한 방법은


INSERT ALL 이다



사용법은 아주아주 굉장히 간단하다.


일단 MyBatis에서 여러 데이터를 맵형태로 보내 다이나믹 쿼리로 만드는 방법은


아래 링크를 참고하고

2015/07/09 - [Yame Programmer/전자정부프레임워크] - [ibatis] 동적쿼리 생성 및 outOfBoundsException 에러


ibatis와 mybatis의 사용방법이 상이하긴 하지만 맥락은 같으니 참고하면 좋을 것이다.




쿼리문은 아래와 같이 사용 하면 된다.


1
2
3
4
5
6
7
8
9
10
    <insert id="insertMtrlOrderList" parameterType="java.util.Map">
        <foreach collection="list" item="item" index="index"  open="INSERT ALL " separator=" " close="SELECT * FROM DUAL" 
         into FM_MATERIALS_ORDER_LIST(MTRL_ID, MTRL_ORDER_ID, 
            MTRL_ENTERPRISE, UNIT_COST, MTRL_ORD_QUANTITY, APPLY_DATE)
        values
            (#{item.MTRL_ID}, #{item.MTRL_ORDER_ID}, 
        #{item.MTRL_ENTERPRISE}, #{item.UNIT_COST}, 
        #{item.MTRL_ORD_QUANTITY}, SYSDATE)
        </foreach>
    </insert>
cs



시작할떄 INSERT ALL 을 써주고 본 쿼리으 insert문은 insert를 제외한 into부터 시작해서 작성하면 끝


역시 사람은 아는게 많아야 손발이 고생을 안하는 것 같다.

보통 전자정부같은 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>
    
    <logger name="org.springframework" additivity="false">
        <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
<logger name="org.springframework" level="DEBUG " additivity="false">
     <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  << 이분의 블로그에서 잘 설명이 되어 있으니 궁금하면 들어가서 보자


간단히 설명하자면 로그이벤트를 부모에게 전달하도록 하느냐 마느냐 정도의 설정부분인것 같은데


어차피 나는 야매개발자라서 정확하고 자세하겐 모른다.



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


이부분에서 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: 쓰레드 이름


로그띄울줄 몰라서 들어온 사람이라면 어차피 저 표현식을 쓸일은 없을테니 그냥 이런게 있다는것만 알아두고 넘어가자.



흔하게 실수 할 수 있는 spring mybatis 에러

Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener



만약 당신이 전자정부 프레임워크를 사용하거나 spring 프레임워크 mybatis를 사용하고 있으며


typealias를 매핑형식으로 sql-map-config.xml에서 관리를 하는데 새로운 쿼리를 추가 하거나

 

typealias를 추가 했는데 서버를 구동시키자 저런 흉악한 에러메세지를 뿜어낸다면 그리고 분명히


경로를 제대로 복사 하였는데도 해당 VO클래스를 찾지 못한다는 에러 메세지가 나타난다면 1개만 더 확인 해 보자.


<typeAlias type="egovframework.easyplatform.common.tree.TreeDataVO" alias="treeDataVO" /> 


<typeAlias type="egovframework.easyplatform.commonvo.tag.vo.ExtendTagDicVO " alias="extendTagDicVO" />





위의 두 vo설정 코드에서 이름과 경로가 다르다는 것 외의 또하나의 차이점을 발견 하였는가?


저기서 둘중 하나의 코드때문에 vo를 찾지 못하는 에러가 나타나게 된다.


아직 모르겠다면 아래쪽의 코드를 잘 살펴 보도록 하자


그것은 경로가 들어가야 하는 부분에 포함되어 있는 공백 때문이다.

<typeAlias type="egovframework.easyplatform.commonvo.tag.vo.ExtendTagDicVO " alias="extendTagDicVO" />

<typeAlias type="egovframework.easyplatform.commonvo.tag.vo.ExtendTagDicVO" alias="extendTagDicVO" />


이렇게 보면 좀더 확인 하기 쉬울 것이다.


vo클래스의 경로를 넣어 주는 부분에 복사 붙여넣기를 하다보면 공백이 들어가는 경우가 종종 생긴다.


그럴때 분명히 경로는 맞는데 왜 클래스를 찾지 못하는지 모르겠다면 공백이 들어가있는지를 확인해 보도록 하자.






심각: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'alarmService': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private egovframework.경로.GenericDao egovframework.easyplatform.common.alarm.service.impl.AlarmServiceImpl.genericDao; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'genericDao': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSession' defined in file [C:\eGovFrameDev-3.5.1-64bit\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\easy-common\WEB-INF\classes\egovframework\spring\context-sqlMap.xml]: Invocation of init method failed; nested exception is org.springframework.core.NestedIOException: Failed to parse config resource: class path resource [egovframework/sqlmap/example/sql-map-config.xml]; nested exception is org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: org.apache.ibatis.builder.BuilderException: Error registering typeAlias for 'extendTagDicVO'. Cause: java.lang.ClassNotFoundException: Cannot find class:  egovframework.경로.tag.vo.ExtendTagDicVO

at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:301)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1186)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)

at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)

at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:229)

at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)

at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:706)

at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:762)

at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)

at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:403)

at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:306)

at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:106)

at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:5068)

at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5584)

at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:147)

at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1572)

at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1562)

at java.util.concurrent.FutureTask.run(Unknown Source)

at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)

at java.lang.Thread.run(Unknown Source)

Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private egovframework.경로.generic.GenericDao egovframework.easyplatform.common.alarm.service.impl.AlarmServiceImpl.genericDao; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'genericDao': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSession' defined in file [C:\eGovFrameDev-3.5.1-64bit\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\easy-common\WEB-INF\classes\egovframework\spring\context-sqlMap.xml]: Invocation of init method failed; nested exception is org.springframework.core.NestedIOException: Failed to parse config resource: class path resource [egovframework/sqlmap/example/sql-map-config.xml]; nested exception is org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: org.apache.ibatis.builder.BuilderException: Error registering typeAlias for 'extendTagDicVO'. Cause: java.lang.ClassNotFoundException: Cannot find class:  egovframework경로.tag.vo.ExtendTagDicVO

at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:522)

at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)

at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:298)

... 22 more

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'genericDao': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSession' defined in file [C:\eGovFrameDev-3.5.1-64bit\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\easy-common\WEB-INF\classes\egovframework\spring\context-sqlMap.xml]: Invocation of init method failed; nested exception is org.springframework.core.NestedIOException: Failed to parse config resource: class path resource [egovframework/sqlmap/example/sql-map-config.xml]; nested exception is org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: org.apache.ibatis.builder.BuilderException: Error registering typeAlias for 'extendTagDicVO'. Cause: java.lang.ClassNotFoundException: Cannot find class:  egovframework경로.tag.vo.ExtendTagDicVO

at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:308)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1186)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)

at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)

at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:229)

at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)

at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1021)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:964)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:862)

at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:494)

... 24 more

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSession' defined in file [C:\eGovFrameDev-3.5.1-64bit\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\easy-common\WEB-INF\classes\egovframework\spring\context-sqlMap.xml]: Invocation of init method failed; nested exception is org.springframework.core.NestedIOException: Failed to parse config resource: class path resource [egovframework/sqlmap/example/sql-map-config.xml]; nested exception is org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: org.apache.ibatis.builder.BuilderException: Error registering typeAlias for 'extendTagDicVO'. Cause: java.lang.ClassNotFoundException: Cannot find class:  egovframework.경로.tag.vo.ExtendTagDicVO

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1554)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)

at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)

at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:229)

at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)

at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:198)

at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:450)

at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:424)

at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:549)

at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:178)

at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)

at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:305)

... 35 more

Caused by: org.springframework.core.NestedIOException: Failed to parse config resource: class path resource [egovframework/sqlmap/example/sql-map-config.xml]; nested exception is org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: org.apache.ibatis.builder.BuilderException: Error registering typeAlias for 'extendTagDicVO'. Cause: java.lang.ClassNotFoundException: Cannot find class:  egovframework경로.tag.vo.ExtendTagDicVO

at org.mybatis.spring.SqlSessionFactoryBean.buildSqlSessionFactory(SqlSessionFactoryBean.java:430)

at org.mybatis.spring.SqlSessionFactoryBean.afterPropertiesSet(SqlSessionFactoryBean.java:336)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1613)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1550)

... 47 more

Caused by: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: org.apache.ibatis.builder.BuilderException: Error registering typeAlias for 'extendTagDicVO'. Cause: java.lang.ClassNotFoundException: Cannot find class:  egovframework.경로.tag.vo.ExtendTagDicVO

at org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XMLConfigBuilder.java:106)

at org.apache.ibatis.builder.xml.XMLConfigBuilder.parse(XMLConfigBuilder.java:89)

at org.mybatis.spring.SqlSessionFactoryBean.buildSqlSessionFactory(SqlSessionFactoryBean.java:424)

... 50 more

Caused by: org.apache.ibatis.builder.BuilderException: Error registering typeAlias for 'extendTagDicVO'. Cause: java.lang.ClassNotFoundException: Cannot find class:  egovframework.경로.tag.vo.ExtendTagDicVO

at org.apache.ibatis.builder.xml.XMLConfigBuilder.typeAliasesElement(XMLConfigBuilder.java:127)

at org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XMLConfigBuilder.java:96)

... 52 more

Caused by: java.lang.ClassNotFoundException: Cannot find class:  egovframework.경로.tag.vo.ExtendTagDicVO

at org.apache.ibatis.io.ClassLoaderWrapper.classForName(ClassLoaderWrapper.java:188)

at org.apache.ibatis.io.ClassLoaderWrapper.classForName(ClassLoaderWrapper.java:87)

at org.apache.ibatis.io.Resources.classForName(Resources.java:254)

at org.apache.ibatis.builder.xml.XMLConfigBuilder.typeAliasesElement(XMLConfigBuilder.java:120)

... 53 more

스프링 관련해서 여기저기 코드를 끌어다 쓰다보면 프로퍼티를 추가 해 주어야 하는 경우가 있다.


기본적으로 전자정부 프레임워크의 기본 샘플 프로젝트에서는 메세지 프로퍼티가 있지만


그건 메세지 소스에 추가 되어 있고 (사실 나도 잘 모름)


그냥 노멀하게 프로퍼티를 추가 하고 싶다 라고 하면 생각보다 굉장히 간단하니 아래 코드를 참고 하여 배워보자




1. 프로퍼티 생성


일단 main 폴더 아래에 resources 폴더 밑에 프로퍼티를 넣어놓을 폴더를 생성하고 그 안에


프로퍼티를 넣는다.


간략히 resources 폴더 밑에 property 라는 폴더를 만들어 file.properties 라는 프로퍼티를 넣어 놓았다.


(이름이 똑같은 필요는 없으니 알아서 하시길)



2. 프로퍼티 추가 등록


전자정부프레임워크의 spring 폴더 밑에 


context-common.xml 이라는 파일이 있다.



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
51
52
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:util="http://www.springframework.org/schema/util" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
 
    <context:component-scan base-package="egovframework">
       <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>
 
    <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>classpath:/egovframework/message/message-common</value>
                <value>classpath:/egovframework/rte/fdl/idgnr/messages/idgnr</value>
                <value>classpath:/egovframework/rte/fdl/property/messages/properties</value>
            </list>
        </property>
        <property name="cacheSeconds">
            <value>60</value>
        </property>
    </bean>
    
    <bean id="leaveaTrace" class="egovframework.rte.fdl.cmmn.trace.LeaveaTrace">
        <property name="traceHandlerServices">
            <list>
                <ref bean="traceHandlerService" />
            </list>
        </property>
    </bean>
 
    <bean id="traceHandlerService" class="egovframework.rte.fdl.cmmn.trace.manager.DefaultTraceHandleManager">
        <property name="reqExpMatcher">
            <ref bean="antPathMater" />
        </property>
        <property name="patterns">
            <list>
                <value>*</value>
            </list>
        </property>
        <property name="handlers">
            <list>
                <ref bean="defaultTraceHandler" />
            </list>
        </property>
    </bean>
    
    <bean id="antPathMater" class="org.springframework.util.AntPathMatcher" />
    <bean id="defaultTraceHandler" class="egovframework.rte.fdl.cmmn.trace.handler.DefaultTraceHandler" />
</beans>
 

cs


기본적으로 이런 모양새 인데 


 

먼저 가장 위에 beans 설정하는 schemaLocation 부분에 


 http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd


이 주소를 추가해주자


1
2
3
4
5
6
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:util="http://www.springframework.org/schema/util" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd">
cs



이렇게 되도록



그리고 


<util:properties id="fileProperties" location="classpath:/프로티 경로" />


를 beans 안에 추가 해 주도록 한다.



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
51
52
53
54
55
56
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:util="http://www.springframework.org/schema/util" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd">
 
    <context:component-scan base-package="egovframework">
       <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>
 
    <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>classpath:/egovframework/message/message-common</value>
                <value>classpath:/egovframework/rte/fdl/idgnr/messages/idgnr</value>
                <value>classpath:/egovframework/rte/fdl/property/messages/properties</value>
            </list>
        </property>
        <property name="cacheSeconds">
            <value>60</value>
        </property>
    </bean>
    
    <bean id="leaveaTrace" class="egovframework.rte.fdl.cmmn.trace.LeaveaTrace">
        <property name="traceHandlerServices">
            <list>
                <ref bean="traceHandlerService" />
            </list>
        </property>
    </bean>
 
    <bean id="traceHandlerService" class="egovframework.rte.fdl.cmmn.trace.manager.DefaultTraceHandleManager">
        <property name="reqExpMatcher">
            <ref bean="antPathMater" />
        </property>
        <property name="patterns">
            <list>
                <value>*</value>
            </list>
        </property>
        <property name="handlers">
            <list>
                <ref bean="defaultTraceHandler" />
            </list>
        </property>
    </bean>
    
    <bean id="antPathMater" class="org.springframework.util.AntPathMatcher" />
    <bean id="defaultTraceHandler" class="egovframework.rte.fdl.cmmn.trace.handler.DefaultTraceHandler" />
     
 
    <util:properties id="fileProperties" location="classpath:/property/file.properties" />
</beans>
 
cs



이렇게 추가를 하면 된다.



3. JAVA에서 사용 하기.



사용할 클래스에 


@Resource(name = "fileProperties")

private Properties fileProperties;


이런 코드를 추가 하여 준다. 어노테이션을 사용 하여 빈즈에서 끌어다 쓰는 것 같은데


저 위에 추가한 부분 id 를 이름으로 가져 오는 듯 하다.



그리고 메소드 내부에서 사용 할땐 아래 코드를 추가 하여 사용하고


String uploadPath = fileProperties.getProperty("가져올 데이터 이름");


간략히 코드를 구현하자면 아래와 같은 코드로 사용 하면 된다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 
@Service("commonFileService")
public class CommonFileServiceImpl implements CommonFileService {
    // 이렇게 어노테이션을 이용하여 불러오도록 준비하고
    @Resource(name = "fileProperties")
    private Properties fileProperties;
 
    @Override
    public 반환 fileUpload(MultipartHttpServletRequest mRequest) {
        // 저장되는 파일 리스트
        List<String> fileNameArray = new ArrayList<String>();
        
        // 이렇게 사용 한다.
        String uploadPath = fileProperties.getProperty("file.image.winPath");
 
 
        return 반환;
    }
    
 
}
 
cs



웹개발을 하다보면 많은 페이지마다 반복되어야 하는 것들이 있다


예를들어 페이지를 이동할때마다 권한 검사를 한다던가 로그를 기록해야 한다던가 하는 것들


또한 URL을 요청해 컨트롤러가 구동되기 전 혹은 구동된 후에 실행시키고 싶은 것들도 있다


그런 인터셉터 기능을 설정하는 방법을 알아 보자



1. dispatcher-servlet.xml 설정


1
2
3
4
5
6
7
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="interceptors">
            <list>
                <ref bean="localeChangeInterceptor" />
            </list>
        </property>
    </bean>
cs


이렇게 생겨먹은 부분이 있는데 이 List 사이에

<ref bean="localeChangeInterceptor" /> 얘는 건들이지 말고 그 밑에

<ref bean="id값" />을 넣어 준다


그리고 아래와 같은 코드를 추가 시켜 준다


1
2
3
4
5
6
7
8
<bean id="위에 추가한 id값" class="인터셉터 기능을 수행할 패키지경로.클래스명">
          <!-- 인증 체크가 필요 없는 URL 리스트  -->
      <property name="urls">
       <list>
            제외시킬 url 목록
       </list>
      </property>
  </bean
cs



다음은 실행시킬 java 클래스를 만든다


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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
 
package 패키지 경로;
 
import java.util.List;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
 
/**
* @패키지명 : 블로그용
* @파일명 : PlatformInterceptor.java
* @작성일 : 2016. 5. 23.
* @작성자 : "In Chee su"
* @설명 : 인터셉트
*/
public class 클래스명 extends HandlerInterceptorAdapter {
     
//  인증 체크가 필요 없는 URL 리스트  
 
List<String> urls;
 
 public void setUrls(List urls) {
  this.urls = urls;
 }
 
 
/* (non-Javadoc)
 * @see org.springframework.web.servlet.handler.HandlerInterceptorAdapter#preHandle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object)
 */
@Override
 public boolean preHandle(HttpServletRequest request,
   HttpServletResponse response, Object handler) throws Exception {
   // 기능을 수행하고 Controller를 실행 하려면
 
   // return true;
 
  
 
   // 특정 체크에서 일치하지 않는다면
 
   // response.sendRedirect("특정 에러 페이지로 보낸다");
 
   // return false;
 
    
    // 인증 체크가 필요 없는 URL 체크
 
          for(int i=0; i < urls.size(); i++){
         
                if (request.getRequestURI().matches(urls.get(i))) {
                System.out.println("== 인증 체크가 필요 없는 URL ============================");
                System.out.println("== URL : "+ urls.get(i) +" ============================");
                System.out.println("== return true ============================");
                System.out.println("== 인터셉터 종료 ============================");
                return true;
               }
         
         
               // 첫페이지가 로그인 페이지일 경우
             
               // 첫페이지 jsession때문에 request.getRequestURI().indexOf("/login/login.do") != -1 사용
             
               if(request.getRequestURI().indexOf(urls.get(i)) != -1){
             
                 return true;
         
           }
 
  
  }
 
 
// 세션이 있는지 체크
 
// 세션에 로그인한 정보가 있는지 체크
 
 
return true;
 }
 
 
}
 
 
cs



이렇게 만들면 된다.

서버를 구동할때 class를 실행시키는 방법이다.


주로 소켓통신을 위해 동시에 켜주거나 여러모로 자주 사용 하게 된다.


1. web.xml 


<web-app> </web-app> 사이에 추가해 준다. 전자정부 프레임워크 라면 ContextLoaderListener 가 이미

추가 되어 있을텐데 그 밑에 고대로 복사해준다.


1
2
3
     <listener>
        <listener-class>패키지경로.클래스명</listener-class>
    </listener>
cs



끝.



주의사항: 대충 다른거 긁어와서


1
2
3
4
5
 public static void main(String[] args) {
          // 5개의 쓰레드를 생성하는 서버를 생성한다.
          PlatformServer server = new PlatformServer(5);
          server.start();
      }

cs


저런 코드 그대로 놔두고 실행시키면 안된다.

생성자를 만들어 주지 않으면 아래와 같은 에러가 발생한다.


심각: Error configuring application listener of class 패키지 경로

java.lang.InstantiationException: egovframework.패키지경로

at java.lang.Class.newInstance(Unknown Source)

at org.apache.catalina.core.DefaultInstanceManager.newInstance(DefaultInstanceManager.java:114)

at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4984)

at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5584)

at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:147)

at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1572)

at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1562)

at java.util.concurrent.FutureTask.run(Unknown Source)

at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)

at java.lang.Thread.run(Unknown Source)

Caused by: java.lang.NoSuchMethodException: 패키지경로.<init>()

at java.lang.Class.getConstructor0(Unknown Source)

... 11 mor


하나의 was에 여러개의 프로젝트(컨텍스트)가 존재할 경우 일반적으론


서로간 세션의 공유가 되지 않는다. 


이때 각 컨텍스트간의 세션이 공유될 수 있는 방법을 알아보자.



1. 서버의 context.xml의 변경


1
2
3
4
5
6
7
8
9
10
<!-- The contents of this file will be loaded for each web application -->
<Context>
 
    <!-- Default set of monitored resources -->
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
 
    <!-- Uncomment this to disable session persistence across Tomcat restarts -->
    <!--
    <Manager pathname="" />
    -->
cs


저 위의 컨텍스트를 아래와 같이 바꿔준다.


1
2
3
4
5
6
7
8
9
10
<!-- The contents of this file will be loaded for each web application -->
<Context crossContext="true">
 
    <!-- Default set of monitored resources -->
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
 
    <!-- Uncomment this to disable session persistence across Tomcat restarts -->
    <!--
    <Manager pathname="" />
    -->
cs



2. server.xml 변경


1
 <Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>

cs


1
 <Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" emptySessionPath="true"/>
cs


emptySessionPath="true" 를 추가 한다.



3. 세션 세팅


1
request.getSession().getServletContext().setAttribute("ssUserId", userId);

cs


기존의 방법에서 가운데 getServletContext()를 추가 하여 준다.


root로 지정되어 있지 않는 컨텍스트에선 

getServletContext("/sample") 와 같이 컨텍스트명을 지정하여 준다.



4. 세션 겟


1
String ssUserId =  (String) request.getSession().getServletContext().getContext("/").getAttribute("ssUserId");
cs


가지고 올땐 위와 같이 가져오며 루트가 아닌 컨텍스트에서 set 한 경우엔 getContext("/sample") 와 같이 컨텍스트 명을 넣어 사용 하면 된다.




DWGViewX 를 사용해 웹에서 cad 파일을 볼 수 있도록 해주는 도중 파일을 불러오지 못하는 오류가 발생 하였다.


일단 


<object id="DWGViewX" codebase="http://www.autodwg.com/dwgviewx/dwgviewx.cab" height="520" width="700" classid="clsid:AC53EFE4-94A7-47E6-BBFC-E9B9CF322299">

                        <param name="_Version" value="65536">

                        <param name="_ExtentX" value="18521">

                        <param name="_ExtentY" value="13758">

                        <param name="_StockProps" value="0">

                       <param name="DrawingFile" value="불러올 파일이 있는 경로">

                        <param name="ShowToobar" value="-1">

                        <param name="ShowLayoutBar" value="1">

                        <embed name="DWGViewX" width="700" height="520" type="DWGVIEWXLib.DwgViewX" showtoolbar="-1" showlayoutbar="1" pluginspage="http://www.autodwg.com/dwgviewx/dwgviewx.cab" title="undefined">

                    </object>



사용방법은 html안에 이렇게 사용해 넣으면 된다.


DrawingFile 파라메터의 밸류값에 불러올 파일이 있는 경로를 지정해 주면 된다.


절대경로를 사용한다면 클라이언 PC의 경로를 뒤진다 (ex) C/programs/..........기타등등)


서버에 있는 파일을 보여주고 싶다면


경로값이 해당 dwg파일을 다운로드 받는 url을 넣으면 해당 파일을 보여준다.



문제는 자꾸 no mapping uri가 나타나면서 에러가 나고 하얀색 화면만 나타난다는 점이다.


해당 경로를 브라우저의 URL창에 넣으면 파일은 정상적으로 다운로드가 되지만 dwg뷰어에서는 해당 url를 찾지 못한다고 나올 뿐이다.


어노테이션 설정에 문제가 있는 건지 한참을 뒤져보다가 원인을 찾게 되었다.


원인은 DrawingFile의 value값이 중간에 인코딩이 되어 요청된다는 것이다.


한글은 깨지고 영문은 전부 소문자로 변환된 후 요청을 한다.


해결방법은 그냥 호출받는 주소를 전부 소문자로 바꿔줘 버렸다...

파일이름은 파일이 업로드 될때 실제 저장된 파일이름은 UUID로 바꾼후에 영문소문자로 바꿔서 저장을 하도록 했다.


이제 도면파일 잘나온다.


액티브X 사라져버렸으면 좋겠다.

파일을 업로드 하고 다운로드 받는 과정에서 저장소에 동일한 파일이름을 가진 파일을 업데이트 하게 되면 저장되는 파일의

이름을 바꾸어 주어야 한다.


동일한 이름이 있을때마다 파일명 뒤에 카운트숫자를 달아 주는 방법을 사용한다거나 

UUID 같은 것을 파일명으로 바꾸고 저장한다거나 날짜순으로 저장하는등의 여러가지 방법들이 있다.


그렇다면 다운 받을때는 파일명이 uuid나 일반 사용자가 알아보기 힘든 파일명을 사용하면 안되기 때문에


다운로드 받을때는 다시 원래 파일명으로 다운 받도록 해주어야 한다.


그렇기 위해서는 파일업로드와 함께 DB에 해당 파일의 이름이나 이름이 포함된 경로를 넣으며 실제저장되는 변경된 파일이름과 원본파일의 이름 두가지를 넣어 주어야 한다.


문제는 여기서 발생한다. 흔히 나와있는 파일 업/다운로드 로직이나 전자정부 프레임워크를 사용하다보면 파일을 다운받을때 실제 저장된 파일이름만 가져와서 다운로드 받는 로직으로 짜여져 있고 단순히 복사해서 붙여넣기를 하는 방법으로 사용하니 다운받을때 디비에서 이름두가지를 가지고 오고 나서 이후에 어떻게 해야 할지 모르는 경우가 생긴다.


대표적으로 컨트롤러에서 


 return new ModelAndView("download", "downloadFile", file); 


이렇게 리턴값을 modelAndView로 사용하는 방법에서 실제저장된 파일명을 바꿔서 사용하는 방법을 알아보자.




일단 리턴되는  ModelAndView("download", "downloadFile", file) 여기서 


'download'는 dispatcher-servlet.xml 에서 지정되는 값으로. 디스패쳐 파일을 열고나서 download을 검색해 보면



<bean id="download" class="egovframework.XXXXXXXX.XXXXXXX.XXXXXXXX.DownloadView" />


와 같은 형식으로 쓰여 있을 것이다. 'download'라는 요청을 받으면 해당 클래스를 실행하겠다는 중간의 XXX표는 패키지 경로이다.


DownloadView 클래스를 열어 보면 



File file = (File) model.get("downloadFile"); <- 이런식으로 modelAndView의 두번째 인자를 불러오며 결과는 downloadFile뒤에 쓰여진 file이 된다.



아래로 좀 내려보면 


fileName = URLEncoder.encode(file.getName(),"UTF-8").replaceAll("\\+", "%20");


이런식으로 해당 파일의 이름을 꺼내서 


 response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ";");


이렇게 파일이름을 셋팅을 하는데


여기서 셋팅되는 fileName만 원래 파일이름으로 바꿔주면 되는 것이다.




이제부터 어떻게 해야 하는지 방법을 알아보자



우선 다운로드 URL이 어노테이션 되어 있는 컨트롤러에서 


리턴되는 값을 return new ModelAndView("download", "downloadFile", file); 가 있는 컨트롤러를




fileName = new String(fileName.getBytes("iso-8859-1"), "UTF-8"); // 파일이름 깨지니까 인코딩

    realName = new String(realName.getBytes("iso-8859-1"), "UTF-8");

//propertiesService.getString("Globals.FmsFilePath")는 미리 지정해놓은 경로니까 그냥 파일경로라고 생각하면 됨

    String fullPath = propertiesService.getString("Globals.FmsFilePath") + "\\경로\\" + fileName;

    

   File file = new File(fullPath); // 실제 파일 경로를 지정해 생성한 파일

   File file2 = new File(realName); // 바꿀 파일이름만 넣어놓은 파일  realName은 String값이다.


   ModelAndView mav = new ModelAndView();

   mav.setViewName("download");

   mav.addObject("downloadFile", file); // 실제 저장된 파일

   mav.addObject("realFileName", file2); //db에 저장해 놓은 원래 파일이름

   

   if(!file.exists()){

    return null;

   }

    //    return new ModelAndView("download", "downloadFile", file);

   return mav;



이렇게 바꾸어 주도록 하자.


즉 ModelAndView에 두개의 오브젝트를 넣어서 넘기는 것이다.



그리고 다시 DownloadView 로 가서 


 fileName = URLEncoder.encode(file.getName(),"UTF-8").replaceAll("\\+", "%20");

  response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ";");


위의 코드를 아래와 같이 바꿔주도록 하자.


   File file2 = (File) model.get("realFileName");

String fileName="test";

try{

fileName = URLEncoder.encode(file2.getName(),"UTF-8").replaceAll("\\+", "%20");

}

catch(Exception e){

 fileName = URLEncoder.encode(file.getName(),"UTF-8").replaceAll("\\+", "%20");

}

 response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ";");




이렇게 하면 끝! 완성! 해결!





+ Recent posts