3.x 버전대의 스프링 프레임워크가 나온지 얼마 안된 것 같은데 벌써 4.1버전대가 릴리즈 됐다. 그래도 3.x 버전대까지는

종종 설정도 살펴보고 이것저것 시도해봤는데 프로젝트 때문에 한참을 못보다가 최근에 시간이 되서 바뀐건 없나 살펴보니

꽤 많이 바꼇다.. 예전 2.x버전대의 설정지옥에 비하면 요즘에는 많이 편해지긴 했지만 아직도 잘 돌아가게 설정 하려면 시간

깨나 잡아먹는 일이긴 하다. 그리고 설정이 간편해진 대신 구조가 복잡해서 모르고 복붙만 해서 쓰다보면 돌아가긴 하지만

잠재적인 위험을 가지고 있는 코딩을 할 수 있는 경우도 이번에 느꼈다. 보통 스프링 메이저 버전이 업그레이드 되면 한번씩

둘러보느라 이번에 최신버전으로 설치하고 테스트 해봤는데 설정에서 꽤 막혔다. 이 글에선 막혔던 부분과 해결 방법에 대해서

정리 해보려고 한다.

1. MyBatis, iBatis

스프링 4.x버전대 부터 iBatis의 지원이 전면 중단 되었다. 이전에 iBatis를 사용하던 프로젝트를 하던 분들은 아래와 같은

설정을 사용하면서 스프링 연동 라이브러리였던 ibatis-spring-xxxx와 함께 iBatis라이브러리를 사용하셨을 건데 4.x버전

에서 사용할 수 없으므로 삭제 하고 MyBatis라이브러리와 mybatis-spring-xxx 라이브러리를 받아서 import해줘야 한다.

* Spring 3.x대의 iBatis 설정 예


    

* Spring 4.x대의 MyBatis 설정 예

    
    
        
        
        
    
            	

변경된 점만 보려는 것이기 때문에 MyBatis설정은 따로 정리해보겠다. Mapper라는 개념이 새롭게 생기긴 했는데

마이그레이션을 생각하고 설정을 해보니 Mapper를 우선은 굳이 사용하지 않고 기존 메소드에서 변경된 메소드로

변경해주고 typeAliases같은 경우 따로 관리를 하므로 따로 config하는 xml로 빼고 쿼리 사용 방식등을 손보면

그나마 마이그레이션 범위가 많이 줄어들지 않을까 싶다. 물론 프로젝트에서 잘돌아가고 있는 iBatis를 MyBatis로

변경할 확률은 거의 제로라고 보지만.. 어쨋든 iBatis개발이 중단되고 MyBatis로 바뀐지 꽤 됐는데 이번엔 아예

지원이 중단되서 좀 섭하긴 하다.

2. Ajax를 통한 ResponseBody, RequestBody JSON처리

이제까지 스프링을 사용하면서 개인적으로 박수 백번 쳐주는 기능이다. RequestBody의 경우는 일반적인 DB 쿼리시

파라미터 전송시에 그냥 좀 편하네 정도 였지만 SAP연동시 Table Structure 파라미터를 보낼때는 정말 편리하다고

생각된다. Ajax로 처리할때는 아래와 비슷하게 하면 된다.

- JSP

var param = {};
var paramList = [];

for(var i=0; i<=10; i++){
      paramList.push({testdata : i});
}
param["list"] = paramList;

$.ajax({
      url : "getTestData.do"
     ,async : false
     ,type : "POST"
     ,dataType : "json"
     ,data : JSON.stringify(param)
     ,contentType: "application/json; charset=utf-8"			
     ,success : function(data){
        alert(JSON.stringify(data));
     }
});

- JAVA(Spring Bean)

                
@Autowired
private ObjectMapper objectMapper;

@RequestMapping(value = "/getData", method = RequestMethod.POST)	
public @ResponseBody Map getTestData(@RequestBody String httpParam, Model model) throws IOException, SQLException{
         Map paramMap = new HashMap();
         paramMap = objectMapper.readValue(httpParam, new TypeReference(){});

         List list = testService.getData(paramMap); 
         Map resultMap = new HashMap();
         resultMap.put("testDataList", list);
	
          return resultMap;
}

근데 3.0, 3.1~4.X 버전대의 ObjectMapper 설정이 조금씩 달라진다. JSON 처리를 위해 Message Converter로 많이 사용되는

것중에 Jackson 라이브러리가 있는데 일단 핸들러가 변경되었다. 아래 설정을 비교해 보겠다.

- 스프링 3.0.x

                
	
	
		
			
				
				
			
		
		
	
	

 

- 스프링 3.1~4.X

                
		 
	
        
            
            
                
                    
                        text/html;charset=UTF-8
                        application/json;charset=UTF-8
                    
                
                        
            
            	            	
            
        
                                
    

- 스프링 3.1~4.X (mvc:annotation-driven 사용시)

                
     
     	
            
                
                    
                        text/html;charset=UTF-8
                        application/json;charset=UTF-8
                    
                
                        
           
            	            	
            			
     		
             

3.0버전대에서 사용되던 AnnotationMethodHandlerAdapter가 3.1버전대에서 deprecate됐다. 그래도 사용가능하긴 하다.

그리고 위 설정에서 MappingJackson2HttpMessageConverter가 있는데 이는 Jackson 라이브러리가 2.x 버전에서 생겼고

1.x 버전에서는 MappingJacksonHttpMessageConverter가 사용된다.

Spring 4.x에서는 MappingJacksonHttpMessageConverter 또한 지원하지 않기 때문에 Jackson 2.x 버전대로 변경을

권장한다. 또한 ObjectMapper의 패키지도 변경되었으므로 기존 1.x 버전대의 Jackson 라이브러리를 사용하고 있었다면

패키지 명도 변경해줘야 한다. 그리고 MappingJackson2HttpMessageConverter의 경우 JSP에서 Ajax로 보낼때

(jQuery 기준) contentType: "application/json; charset=utf-8" 와 같이 명시해주지 않으면 RequestBody에서 파싱에

실패했다는 메시지가 뜰 것이다. 설정때문에 인터넷을 찾다보면 AnnotationMethodHandlerAdapter를 사용한 설정예가

주를 이뤄서 꽤 많이 찾아 다녔던 것 같다. 설정 변경부분만 살펴봤는데 역시 변화에 유연한 스프링이긴 해도 버전이

올라가면서 코어 마이그레이션 하기는 쉽지 않은듯 하다. 여담으로 이전에 3.0 버전대에서 보통 ResponseBody를 처리

할때 AnnotationMethodHandlerAdapter를 사용하면서 스프링 Model(org.springframework.ui.Model)를 리턴하는 방식을

사용했는데 변경된 방식을 사용해보니 Resolver를 타고 RequestMapping에 선언된 View 페이지로 계속 리턴이 되서

의아했다. 여러 글을 보다보니 mvc-annotation-driven을 사용해서 설정하거나 3.2이상 버전에서 권장되는

RequestMappingHandlerAdapter를 사용했을때의 처리 방식이 달라져서 이다.  처리 요청을 받았을때 스프링에서 인스턴스

들을 특정 order로 등록하게 되는데 컨트롤러의 메소드에서 리턴되는 값이 있을경우 RequestMappingHandlerAdapter의

getDefaultReturnValueHandlers() 에서 핸들러들을 검색하다가 supportsReturnType() 메소드를 통해 먼저 발견되는 경우

true를 리턴하게 되고 스프링의 Model을 핸들링하는 ModelMethodProcessor보다 ResponseBody를 관리하는

RequestResponseBodyMethodProcessor보다 우선순위가 상위이기 때문에 먼저 처리가 된것이고 (ResponseBody의

경우 MessageConverter를 통해 결과가 처리되므로 컨트롤러 메소드에서 리턴되는 결과와는 조금 별개로 볼 수 있다.)

이미 View Resolver는 설정에 추가된 상태이기 때문에 처리가 된 것으로 보인다. 좀 복잡해서 확실하게 이해되진 않았고

확실히 맞는지는 모르겠지만 대강은 윤곽이 잡히는 것 같다. 근데 DefaultAnnotationHandlerMapping을 사용했을시

스프링 Model을 ResponseBody로 리턴해도 정상적으로 처리된것은 아직 이해가 되질 않는다. deprecate된데에 뭔가

이유가 있지 않을까? 여튼  스프링이 버전업 될때마다 느끼는 거지만 이런 이해안되는 부분을 찾다보면 Java개발 패턴도

보게 되고 여러모로 공부가 된다. 다음 프로젝트는 이상한 Wrapper클래스로 떡칠해서 스프링 기능도 제대로 못써먹는 

이놈도 저놈도 아닌  모 프레임워크(egov 아님 ;) 환경 말고 스프링 기능을 접해볼 수 있는 프로젝트로 가보고 싶다.

설정변경 부분 보다 다른말이 더 길어지긴 했는데 이제 설정 부분은 코어 버전 바뀔때가 아니어도 가끔 확인해봐야될

필요가 있다 생각됐다. 글을 보시는 분들도 참고가 되셨으면 한다.

-- Syntax Highliter 처음 써보는데 글쓰기 힘드네요. 긁어가기 쉽게 글 쓰는 분들 감사합니다. ㅠㅠ

 

신고

회사에서 조그마한 모바일 웹 개발 개선작업 프로젝트를 진행하고 있다. jQuery Mobile과 전자정부 프레임워크로 구성되어

있는데 사실 굉장히 작은 규모의 프로젝트라 전자정부 프레임워크 내부 API를 이것저것 사용할 일도 없고 사정상 지금 회사

에서 사용하는 노트북이 거의 10년전에 집에서 뒹굴던 노트북(싱글코어.. 램 2Gb)이라 이클립스가 무거우면 견디기가 어렵고

전자정부 프레임워크 개발환경은 왤케 무거운지.. validator 한번 돌아가면 노트북 팬이 터질듯이 돌아가고 손도 댈수 없을

정도여서 구조 리팩토링을 결심했다. 기존소스에서 전자정부 API를 사용하는 부분을 제거하고 Spring설정도 다시 잡고

하다보니 이틀이 꼬박.. 전자정부 프레임워크.. 참 좋긴한데 이번에는 제대로 신경질 나게 해주는 개발툴이 되버렸다.

물론 오래된 노트북 탓이 크긴하지만 ㅜ

기존소스에서 사용하던 jQuery Mobile이 훌륭한 디자인 테마와 템플릿을 제공하는건 높히 평가하지만 개인적으로 CSS가 너무

많아서 복잡한 편이고 제일 중요한건 이 프로젝트가 완전히 상업용으로 진행되는 프로젝트가 아니라서 파일럿 프로젝트보단

조금 부담있는 프로젝트이기 때문에 구조변경시에 디자인 문제가 맘에 걸렸다. 그래서 jQuery Mobile을 걷어내기로 하고

기존 HTML도 재사용 가능부분을 빼고 전부 새로 작성하기로 했다. 또 한가지 Value Object로 파라미터가 왔다갔다 하는걸

싫어해서 이 부분도 개선하려고 했지만 이것저것 다하면 눈에 보이는 결과물이 안나올 것 같아서 백엔드는 그대로 가기로 했다.

 

jQuery Mobile의 테마를 보면 위 사진과 같이 라운드 형태의 디자인이 많은데 요새 iOS도 그렇고 웹디자인 트렌드가 전반적

으로 플랫디자인으로 흘러가는 것 같아서 시간도 별로 없고 색하고 폰트만 잘 맞추면 플랫디자인 그까이꺼.. 생각하고 덤볐는데

역시..디자인하고는 거리가 먼 듯 하다. 플랫디자인 정말 어렵다 ㅜ

 

 

이렇게 플랫디자인을 한답시고 작업하다 보니 망할 콤보박스를 생각을 못했다. 셀렉트 박스를 그냥 넣으면 위와 같이 브라우저

or OS테마에 따라 제각각 다르게 보이기도 하고 우선 모양새가 영 좋지 않다. input 태그의 경우 보통 스타일링 하면

span{vertical-align:middle;padding:4px;border:2px solid #ccc;}

span input{border:0;}

과 비슷하게 input의 border를 지우고 감싸는 태그에 border를 주는것으로 스타일을 주면 되지만 불행하게도 우리가 흔히 쓰는

콤보박스 (Radio, Check, Select) 는 일반적인 방법으로는 스타일링 할수가 없다. 셀렉트 박스의 경우도 input과 마찬가지로

border를 0을 주면 전체적인 틀까지는 스타일링 할수 있지만 우측 Arrow는 어쩔수가 없다. 디자인만 보자면 셀렉트 박스를

UL, LI를 사용해서 새로 만들던지 기존 셀렉트 박스를 레이어처럼 감싸서 스타일링 하는 방법밖에 없다. 전자를 택하면

기존 로직도 다 변경해야 되니 귀찮기도 하고 시간도 오래 걸리고 두번째 방법도 만들자니 언제 만들고 있냐 싶어서

플러그인을 찾아봤다. 커스터마이징이 쉽고 일단 돌려봐서 기존 마크업 변경없이 최대한 빨리 예를 볼 수 있는걸로 찾아봤는데

jqTransform이라는걸 처음 찾았다.

 

위와 같이 예제에서는 꽤 괜찮아 보였는데 막상 적용해 보니 고치려면 손댈부분이 꽤 여러군데 있어서 다른 플러그인을 다시

찾아봤다. 대신 다른 유형의 콤보박스들도 한번에 디자인 적용이 되는걸 보니 괜찮아 보였다. 여튼.. 셀렉트 박스가 주목적이니

다시 찾아봤는데 그다지 맘에 드는 플러그인이 없었다. 그러던 중 이름도 거창한 Select Or Die라는 플러그인을 찾게됐다.

 

사용법은 간단하다 데모 페이지를 실행해보면 뭔지 알수 있다. 예를 들어 셀렉트 태그 전체를 스타일 적용한다면

$("select").selectOrDie();

라고 해주면 기본으로 아래와 같이 적용된다. 

스타일의 경우 selectordie.css를 고쳐서 사용하면 된다. 이 플러그인의 장점은 군더더기가 없어 보인다는 것이다. 보통

다른 플러그인의 경우 디자인을 덕지덕지 입혀놔서 처음 적용하고 나면 수정할 부분이 상당한데 SOD의 경우 CSS를 간단히

몇군데 수정하는 것으로 많은 부분이 해소 된다. 데모 페이지를 보면 다양한 옵션이 있어서 하나씩 적용해 보면 된다.

한가지 더 좋은점은 이벤트 발생후의 callback처리 등이 가능하다는 것이다. 다른 플러그인은 자세히 사용안해봐서 어떨지

모르겠지만 우선 대강 봤을때 이렇게 여러가지 옵션이 있는 플러그인은 없었다. 셀렉트 박스를 쓰다보면 Option태그를 동적

으로 셋팅해줄 경우가 생기는데 이 경우에는 option 태그를 append 한뒤 $("select").selectOrDie("update")와 같이

써주면 적용된다. 한가지 단점은 select 박스에 option태그가 있고 텍스트가 뭐라도 있어야 저 옵션이 적용됐었다. 글을

쓰다 보니 placeHolder옵션이 있어 어떻게 해볼수 있을것 같기도 하다. 이 플러그인은 위에서 썼듯 기존 마크업과 로직을

건드리지 않고 동적으로 select 박스의 겉을 감싸는 스타일링을 적용해주는 플러그인이다. 개인적으로 jQuery 플러그인

또한 많이 사용하는것을 좋아하진 않는 편인데 이유는 한번 피봐서 ;; 지금은 어느정도 버전이 변경되도 잘 돌아가는편인데

예전 jQuery 1.x 하위 버전때는 버전업된 jQuery에 맞춰지지 못해 오류가 나거나 오작동하는 경우가 많았다. 그래서

가능하면 플러그인에 의존하지 않으려고 했는데 이 플러그인의 경우 간만에 괜찮은걸 건진 것 같다. 아직 계속해서

개선이 이뤄지고 있는부분중에 Multiple셀렉트가 있어 보인다.

이 플러그인은 jQuery 1.8, 2.1에서 테스트 완료됐다고 한다. IE8, jQuery 2.1에서 지원하지 않는다고 하는데 어짜피

jQuery 2.x 대부터 IE8지원이 중단됐으니 상관없어 보인다. GitHub에서 받을수도 있지만 귀찮은 분들은 아래 첨부파일을

받아서 사용해 보시길..

-  다운로드

Select-or-Die-master.zip

 

 

 

신고

보통 우리가 인터넷을 사용하면서 주소창에 흔히 볼수있는 주소 표현으로 다음과 같이..

 

http://xens.tistory.com/content/list.jsp?user=park&page=1 과 같은형태를 많이 보게 됩니다. 이를 RESTful 하게 표현하면

 

http://xens.tistory.com/content/list/user/park/page/1 과 같이 표현할 수 있게됩니다. 개념상으로는 단순히 이렇게 주소

 

표기를 위해서 뜬금없이 나타난 것은 아니고 'URI로 자원대상을 명시하고 Method로 자원에대한 행위를 정의한다'

 

를 베이스로 가지고 있는 아키텍쳐 이며 기본적으로 CRUD(Create, Read, Update, Delete)와 매치될수 있는 형태의 서비스

 

에서 쓰이고 있습니다. 저도 여기저기서 글만 봤지 최근에서야 공부겸 예제를 작성해 봤는데 글 제목에서 썼듯 URL Mapping

 

문제로 굉장한 삽질을 했습니다. 분명 시간이 지나면 잊어버릴게 뻔하니 간단히 정리를 해봅니다.

 

예제를 만들때는 Spring Framework 3.0.5와 Tomcat 7, jQuery를 사용했습니다. 이클립스에서 프로젝트 구조는 다음과

 

같다고 보시면 됩니다.

 

 

 

 

※JSP 호출 오류 해결방법

  REST형식으로 호출하면서 처음부터 진입을 가로막는것이 JSP 입니다. 처음 시작페이지가 content.jsp라고 하겠습니다.

 

 요즘에는 보통 서비스(URL)호출시 AJAX를 사용하여 콜백으로 온 데이터를 통해 결과를 출력하거나 하는 개발 방식을 많이

 

 사용하고 있는데 예를 들어서 content.jsp 에서 아래와 같이 호출한다고 가정합니다(처음 실행페이지가 content.jsp).

 

 $.ajax({

   ...

   url : "/person/parkdi"

   ...

 });

 

 그리고 스프링 컨트롤러는 다음과 같이 작성 했다고 가정합니다.

 

 @RequestMapping(value="/person/{id}", method=RequestMethod.GET)
 public ModelAndView getDataRestfulUrlPattern(@PathVariable("id") String id){...}

 

 이렇게 작성하고 바로 호출을 해보면 호출하는 content.jsp 자체가 에러가 납니다. 일단 이상현상을 보게된 후

 

 제일 먼저 봐야할것은 WAS의 web.xml 파일입니다. 우리는 Spring Framework를 사용했기 때문에 DispatcherServlet를

 

 다음과 비슷하게 매핑시켜줬을 겁니다.

 

  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>ContextConfigLocation</param-name>
      <param-value>/WEB-INF/webmvc_config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>

 

실행할때의 톰캣의 Console로그를 보시면 content.jsp파일도 DispatcherServlet에서 처리하려고 하는것을 볼수 있습니다.

 

그러나 위의 web.xml 설정과 같이 *.do에 대한 패턴을 매핑하도록 했기 때문에 content.jsp에 대한 매핑 URL을 찾을수가

 

없어서 에러가 발생하게 됩니다. 우선 문제에 대한 해결책은.. web.xml을 수정해야 합니다.

 

  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>ContextConfigLocation</param-name>
      <param-value>/WEB-INF/webmvc_config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  <servlet-mapping> 
    <servlet-name>default</servlet-name>
    <url-pattern>*.js</url-pattern>
    <url-pattern>*.css</url-pattern>
    <url-pattern>*.jpg</url-pattern>
    <url-pattern>*.gif</url-pattern>
    <url-pattern>*.png</url-pattern>
    <url-pattern>*.ico</url-pattern>
    <url-pattern>*.swf</url-pattern>
 </servlet-mapping>       

 

우선 문제에 대한 해결책은 위의 붉은 글씨처럼 Dispatcher서블릿의 url-pattern을 '/' 로 변경하고 deault servlet의 url-pattern

 

을 추가해줬습니다. 호출하는 페이지인 content.jsp에서는..

 

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

 $.ajax({

   ...

   url : "<c:url value='/person/parkdi' />"    //컨텍스트Path(spring_webmvc_restful_ex1/person/parkdi)

   ...

 });

 

컨트롤러에서는 위에 작성된 예와 같이 그대로 두고 호출하면 됩니다. 일단 문제는 해결됐지만 이유는 알아야 되겠죠.

 

※오류 원인

 

JSP는 WAS(톰캣 등)의 서블릿 컨테이너에서 처리를 하게 됩니다. 톰캣을 예로 보면 이클립스 프로젝트에서의 web.xml이 아닌

 

%CATALINA_HOME%(톰캣 설치 디렉토리)의 conf/web.xml을 열어보면

 

 <sevlet-mapping>

  <servlet-name>jsp</servlet-name>

  <url-pattern>*.jsp</url-pattern>

 </sevlet-mapping>

 <servlet>
    <servlet-name>jsp</servlet-name>
    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
    <init-param>
       <param-name>fork</param-name>
        <param-value>false</param-value>
    </init-param>
    <init-param>
        <param-name>xpoweredBy</param-name>
        <param-value>false</param-value>
     </init-param>
    <load-on-startup>3</load-on-startup>
  </servlet>

 

위와같이 org.apache.jasper.servlet.JspServlet 에서 jsp를 처리한다는 것을 볼 수 있습니다. JSP 인식오류 해결을 위해

 

위에서 추가했던 <servlet-name>default</servlet> 의 경우 서블릿 컨테이너의 DefaultServlet을 의미하는데 

 

url-pattern을 '/'로 정의한 경우에 디폴트 서블릿을 의미하며 이 디폴트 서블릿은 서블릿 매핑 URL에 걸리지 않는 요청을

 

처리한다고(톰캣 설정파일의 주석) 되어있습니다. 그래서 이제 DispatcherServlet의 url-pattern을 '/'로 설정함으로서

 

JSPServlet의 JSP처리와 DispatcherServlet의 처리를 분리 시켰습니다. 이렇게 분리 할경우 JSP와 스프링 컨트롤러 매핑은

 

잘 처리되지만 css, js등의 정적 컨텐츠들이 나오지 않는것을 볼수 있습니다. 이는 DispatcherServlet의 url-pattern을 '/'로

 

설정하면서 톰캣의 server.xml에 정의되있는 DefaultServlet의 url-pattern을 무시하게 되기 때문에 DefaultServlet이 작동하지

 

않게 되기 때문입니다. 따라서 호출시 정적 컨텐츠들도 DispatcherServlet이 처리하게 되니 표현되지 않게 됩니다.

 

<servlet-mapping> 
  <servlet-name>default</servlet-name>
  <url-pattern>*.js</url-pattern>
  <url-pattern>*.css</url-pattern>
  <url-pattern>*.jpg</url-pattern>
  <url-pattern>*.gif</url-pattern>
  <url-pattern>*.png</url-pattern>
  <url-pattern>*.ico</url-pattern>
  <url-pattern>*.swf</url-pattern>

</servlet-mapping> 

그래서 DefaultServlet이 처리할 url-pattern을 재정의 해준것이 윗 부분입니다. 사실 위와 같이 정의해줄경우 정적 컨텐츠에

 

대한 url-pattern을 전부다 정의해줘야 줘야하므로 지저분하기도 합니다. 그래서 스프링 3.0에서는 이 부분을

 

<mvc:default-servlet-handler /> 를 선언해줘서 해결을 한다고 합니다. 여기에는 주요 WAS의 DefaultServlet 명에

 

대한 내용을 처리하도록 되있다고 하네요. 저도 위의 DefaultServlet의 매핑을 재정의 해주기 전에 발견해서 적용해봤는데

 

xmlns:mvc="http://www.springframework.org/schema/mvc ,

xsi:schemaLocation=http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd

 

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" p:alwaysUseFullPath="true" />

 

위 내용이 분명히 선언되있는데도 아래와 같이

 

org.xml.sax.SAXParseException: cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element 'mvc:default-servlet-handler'.

 

찾을수 없다고 계속 떠서 포기했습니다. 찾아봐도 위 내용의 XML확인이나 <mvc:annotation-driven /> 추가 여부만

 

확인하라는 답변밖에 없더군요.. 혹시 비슷한 경험 있으신 분은 답변 주시면 감사하겠습니다. 어쨋든 그냥 REST의 개념만

 

정리하고 CRUD에 해당하는 스프링의 RequestMethod(POST,GET,PUT,DELETE)를 좀 더 알아보려고 했던 것인데

 

하루종일 삽질만 한 것 같습니다;;

신고

1부에서는 HttpServletResonse의 getWriter()를 통해 마샬링한 데이터를 바로 출력하는 예제,

 

2부에서는 마샬링한 데이터를 OutputStream에 저장후 InputStream으로 변환하여 언마샬링 하는 예제를 작성해 봤습니다.

 

이번에는 마샬링 결과를 파일로 저장후 InputStreamReader를 통해 File을 읽어와서 언마샬링 하는 예제를 작성해 보겠습니다.

 

-----------------------------------------------------------------------------------------------------------------

※Object를 마샬링 후 파일로 저장

import com.thoughtworks.xstream.XStream;

 

XStream xstream = new XStream();
final String xmlPath = "D:\\test.xml";
OutputStreamWriter osw =  

new OutputStreamWriter(new FileOutputStream(new File(xmlPath)), "utf-8");

 

UserBltnListCollections userBltnListCollections = new UserBltnListCollections();
userBltnListCollections.setUserBltnList(list);
xstream.processAnnotations(UserBltnListCollections.class);
xstream.toXML(userBltnListCollections, osw); //마샬링한 데이터를 파일로 저장

-----------------------------------------------------------------------------------------------------------------

 - UserBltnListCollections Class

  import com.thoughtworks.xstream.annotations.XStreamAlias;
  import com.thoughtworks.xstream.annotations.XStreamImplicit;

 

  //루트 Element의 별칭을 지정해줌

  //주의점 : 당연한 결과겠지만 마샬링시 필드명에 Alias를 지정하여 필드명 변경등이

  //있을경우 언마샬링시에도 XStreamAlias를 사용해서 일치하게 설정해야 한다.
  @XStreamAlias(value="userBltnList")
  public class UserBltnListCollections {
 
  //하위 필드(Collection)에 대해 XStreamImplicit Annotation으로 지정함
  //(XAJB의 경우 @XmlElement(name="bltnList")로 지정함)

  //(UserBltnListVO클래스는 일반적인 getter/setter를 포함한 Value Object Class임)
  @XStreamAlias(value="bltnList")
  @XStreamImplicit(itemFieldName="bltnList")
  private List<UserBltnListVO> userBltnList = null;
  
  public List<UserBltnListVO> getUserBltnList() {
   if(userBltnList==null){
    userBltnList = new ArrayList<UserBltnListVO>();
  }
   return userBltnList;
  }

  public void setUserBltnList(List<UserBltnListVO> userBltnList) {
   this.userBltnList = userBltnList;
  }  
 }

-------------------------------------------------------------------------------------------------

- UserBltnListVO (ValueObject Class) 

 

import com.thoughtworks.xstream.annotations.XStreamOmitField

 

public class UserBltnListVO {
 
 //@XStreamAsAttribute로 지정시 속성으로 지정된다.
 //JAXB에서는 @XmlAttribute(name="xx")로 지정함 필드일 경우엔 마샬링시
 //JAXB와 마찬가지로 따로 지정할 필요는 없는것 같다.
 //JAXB는 마샬링시 모든 필드를 바인딩 하지만 XStream에서는
 //@XStreamOmitField가 지정된 필드는 제외시킨다. 
 
 @XStreamOmitField
 private String no = "";
 private String subject = "";
 private String writeDay = "";
 private String readNum = "";
 private String isSecret = "";
 private String name = "";

 ..

  getter/setter 생략

 ..

}

-----------------------------------------------------------------------------------------------------------------

※Object를 마샬링후 데이터를 저장한 파일을 InputStreamReader로 읽어와서 언마샬링

InputStreamReader isr = new InputStreamReader(new FileInputStream(xmlPath),"utf-8");
UserBltnListCollections userBltnListCollectionsToUnMarshall = null;

//지정된 XStream 관련 Annotation을 처리한다.
xstream.processAnnotations(UserBltnListCollections.class);   
//마샬링된 파일을 InputStreamReader로 읽어와서 언마샬링 함
userBltnListCollectionsToUnMarshall = (UserBltnListCollections)xstream.fromXML(isr);   

-----------------------------------------------------------------------------------------------------------------

JAXB에서 마샬링된 XML을 언마샬링 할때는 File클래스로 저장 후 바로 읽어와서 언마샬링 하도록 했으나 이번엔 방식은

 

비슷하나 Stream을 통해서 저장하는 방식으로 바꿔봤습니다. 예제에서는 작은 파일이기 때문에 어떤 방법으로 처리해도

 

IO에 무리가 되지 않으나 XML크기가 크고 빈번한 요청이 일어나는 Task라면 어떤 방식으로 처리할것인가를 고민해야 할

 

것으로 보입니다. 여기까지 XStream을 통한 마샬링, 언마샬링 방법에 대해서 알아봤습니다. 개념은 조금 다를지 몰라도

 

결과적으로 봐선 XStream을 사용하는것이 더 좋을것 같습니다. 좀더 유연하다고나 할까요? 시간이 되면 List<Map>을

 

언마샬링 하는 예제를 작성해 보려고 합니다. 이경우엔 따로 구현을 해야되는 것으로 알고 있습니다. JAXB 사용방법

 

예제를 작성할때도 잠깐 언급했지만 DomParser사용할때에 비해서 이렇게 비교가 안될정도로 편하게 쓸 수 있게 만들어준

 

개발자 분들에게 항상 존경과 감사함을 느낍니다.

신고

1부에서는 HttpServletResonse의 getWriter()를 통해 마샬링한 데이터를 바로 출력하는 예제를 작성해 봤습니다.

 

이번에는 OutputStream으로 마샬링한 데이터를 저장하고 이를 InputStream으로 변환하여 언마샬링 처리하는 방법을

 

예제로 작성해보겠습니다. JAXB를 사용한 예제도 비슷하게 HttpUrlConnection을 통해 가져온 InputStream을

 

언마샬링 하는 예제가 있으니 참고하시기 바랍니다.

 

-----------------------------------------------------------------------------------------------------------------

※Object를 마샬링 후 OutputStream으로 저장한 후 InputStream으로 변환

  import com.thoughtworks.xstream.XStream;

 

  ByteArrayOutputStream ous = new ByteArrayOutputStream();
  ByteArrayInputStream ins = null;
  byte[] byteData= null;
  
  UserBltnListCollections userBltnListCollections = new UserBltnListCollections();
  userBltnListCollections.setUserBltnList(list); //list는 List Collection클래스임
  
  XStream xstream = new XStream();    

  //지정된 XStream 관련 Annotation을 처리한다.
  xstream.processAnnotations(UserBltnListCollections.class); 

  //UnMarshalling시 InputStream을 처리하기 위해 OutputStream으로 저장
  xstream.toXML(userBltnListCollections, ous); 
  byteData = ous.toByteArray();
  ins = new ByteArrayInputStream(byteData); //InputStream으로 변환 

-----------------------------------------------------------------------------------------------------------------

 - UserBltnListCollections Class

  import com.thoughtworks.xstream.annotations.XStreamAlias;
  import com.thoughtworks.xstream.annotations.XStreamImplicit;

 

  //루트 Element의 별칭을 지정해줌

  //주의점 : 당연한 결과겠지만 마샬링시 필드명에 Alias를 지정하여 필드명 변경등이

  //있을경우 언마샬링시에도 XStreamAlias를 사용해서 일치하게 설정해야 한다.
  @XStreamAlias(value="userBltnList")
  public class UserBltnListCollections {
 
  //하위 필드(Collection)에 대해 XStreamImplicit Annotation으로 지정함
  //(XAJB의 경우 @XmlElement(name="bltnList")로 지정함)

  //(UserBltnListVO클래스는 일반적인 getter/setter를 포함한 Value Object Class임)
  @XStreamAlias(value="bltnList")
  @XStreamImplicit(itemFieldName="bltnList")
  private List<UserBltnListVO> userBltnList = null;
  
  public List<UserBltnListVO> getUserBltnList() {
   if(userBltnList==null){
    userBltnList = new ArrayList<UserBltnListVO>();
  }
   return userBltnList;
  }

  public void setUserBltnList(List<UserBltnListVO> userBltnList) {
   this.userBltnList = userBltnList;
  }  
 }

-------------------------------------------------------------------------------------------------

- UserBltnListVO (ValueObject Class) 

 

import com.thoughtworks.xstream.annotations.XStreamOmitField

 

public class UserBltnListVO {
 
 //@XStreamAsAttribute로 지정시 속성으로 지정된다.
 //JAXB에서는 @XmlAttribute(name="xx")로 지정함 필드일 경우엔 마샬링시
 //JAXB와 마찬가지로 따로 지정할 필요는 없는것 같다.
 //JAXB는 마샬링시 모든 필드를 바인딩 하지만 XStream에서는
 //@XStreamOmitField가 지정된 필드는 제외시킨다. 
 
 @XStreamOmitField
 private String no = "";
 private String subject = "";
 private String writeDay = "";
 private String readNum = "";
 private String isSecret = "";
 private String name = "";

 ..

  getter/setter 생략

 ..

}

-----------------------------------------------------------------------------------------------------------------

 - InputStream을 언마샬링

 UserBltnListCollections userBltnListCollectionsToUnMarshall = null;  

 //지정된 XStream 관련 Annotation을 처리한다.
 xstream.processAnnotations(UserBltnListCollections.class);

 //InputStream을 읽어서 바인딩될 Object에 저장  
 userBltnListCollectionsToUnMarshall =  (UserBltnListCollections) xstream.fromXML(ins);

-----------------------------------------------------------------------------------------------------------------

 

JAXB로 마샬링후 언마샬링 처리할때와 방식은 거의 비슷합니다. JAXB나 XStream둘 중 한가지라도 써보셨다면 결국

 

마샬링이나 언마샬링을 처리하는 부분의 약간의 차이, 어노테이션 이름 차이정도가 있으며 결국 맵핑을 얼마나 잘 시켜서

 

사용하느냐가 관건인 듯 싶습니다.

신고

XStream을 통한 마샬링, 언마샬링 처리에 대해서 알아보겠습니다. 최근 JAXB(Java Architecture for XML Binding)

 

API와 더불어 OXM(Object/Xml Mapping)에 많이 사용되고 있는 것 같네요. 스프링 프레임워크를 쓰시는 분들은

 

JAXB, XStream 모두 Spring OXM에 포함되어 있으므로 간단히 의존성 주입후 사용하시면 되겠습니다.

 

저는 따로 라이브러리를 받아서 사용 했습니다. 마샬링, 언마샬링 구현 방식은 JAXB와 거의 유사합니다만

 

가독성은 XStream이 더 간결했으며 대략적인 개념은 비슷한 라이브러리 들이지만 약간씩 차이가 있습니다.

 

개인적으로는 편의성만 보면 XStream이 쓰기 더 편하게 느껴집니다. 이제 간단한 예제부터 한번 보겠습니다.

 

대략적인 설명은 예제의 주석 부분을 보시면 이해 되실겁니다. 실습해보면서 나름 이해한 내용이라 잘못된 이해일수도

 

있습니다.

 

*JAXB를 통한 Marshalling, UnMarshalling 방법은 아래 링크들을 참고하시면 됩니다.

 

1부 : 파일을 통한 마샬링, 언마샬링 처리

2부 : HttpUrlConnection을 통해 얻어온 InputStream을 언마샬링

3부 : 마샬링된 데이터를 OutputStream에 저장하고 다시 InputStream으로 변환 후 언마샬링

 

 

※마샬링 후 출력

  import com.thoughtworks.xstream.XStream

 

  UserBltnListCollections userBltnListCollections = new UserBltnListCollections();
  userBltnListCollections.setUserBltnList(list);

 

  //마샬링시 xpp3 라이브러리의 종속성을 피하기 위해 DomDriver를 사용하도록 설정

  //할수 있다. 일부에선 속도저하가 심각하므로 꿈에서도 사용하지 말라는 소리가 있지만

  //그렇게 방대한 XML이 아니라면 상관 없을 듯 싶다.

  //ex) XStream xstream = new XStream(new DomDriver("utf-8"));


  XStream xstream = new XStream();

  //지정된 XStream관련 Annotation을 처리한다. 
  xstream.processAnnotations(UserBltnListCollections.class);
  xstream.toXML(userBltnListCollections, response.getWriter());   //출력

 

------------------------------------------------------------------------------------------------

 - UserBltnListCollections Class

  import com.thoughtworks.xstream.annotations.XStreamAlias;
  import com.thoughtworks.xstream.annotations.XStreamImplicit;

 

  //루트 Element의 별칭을 지정해줌

  //주의점 : 당연한 결과겠지만 마샬링시 필드명에 Alias를 지정하여 필드명 변경등이

  //있을경우 언마샬링시에도 XStreamAlias를 사용해서 일치하게 설정해야 한다.
  @XStreamAlias(value="userBltnList")
  public class UserBltnListCollections {
 
  //하위 필드(Collection)에 대해 XStreamImplicit Annotation으로 지정함
  //(XAJB의 경우 @XmlElement(name="bltnList")로 지정함)
  @XStreamAlias(value="bltnList")
  @XStreamImplicit(itemFieldName="bltnList")
  private List<UserBltnListVO> userBltnList = null;
  
  public List<UserBltnListVO> getUserBltnList() {
   if(userBltnList==null){
    userBltnList = new ArrayList<UserBltnListVO>();
  }
   return userBltnList;
  }

  public void setUserBltnList(List<UserBltnListVO> userBltnList) {
   this.userBltnList = userBltnList;
  }  
 }

------------------------------------------------------------------------------------------------

 

마샬링한 데이터를 곧바로 HttpServletResponse의 getWriter()를 통해 출력하는 예제를 작성해봤습니다. JAXB로 마샬링

 

할때와 결과는 동일하지만 가독성에 있어서는 XStream이 간결해 보입니다. 다음 예제에서는 OutputStream으로 저장한

 

마샬링 데이터를 InputStream으로 변환하여 출력하는 예제를 작성해 보겠습니다.

신고

1부에서는 파일을 통한 마샬링, 언마샬링 처리, 2부에서는 HttpUrlConnection을 통해 얻어온 InputStream을 언마샬링,

 

3부에서는 마샬링된 데이터를 OutputStream에 저장하고 다시 InputStream으로 변환하여 언마샬링 하는 예제를

 

작성해봤습니다. 마지막으로 가장 간단하게 HttpServletResponse 객체의 getWriter()를 통해 마샬링된 데이터를

 

바로 출력시키는 예제를 작성해 보겠습니다. 이번 예제는 마샬링 후 바로 출력하기 때문에 언마샬링을 거치지

 

않습니다.

 

-XML 원본 데이터 예시

 <userBltnList>
     <bltnList>
         <isSecret>Y</isSecret>
         <name>TEST</name>
         <no>TESTNO</no>
         <readNum>TESTREADNUM</readNum>
         <subject>Android GCM Push with Command Pattern</subject>
         <writeDay>2013-02-15 18:00:18.0</writeDay>
     </bltnList>
 </userBltnList>

-----------------------------------------------------------------------------------------------------------------

 

※ Collection클래스를 마샬링

  import javax.xml.bind.JAXBContext;

  import javax.xml.bind.Marshaller;

 

  //list는 List Collection클래스

  UserBltnList userBltnList = new UserBltnList();
  userBltnList.setList(list);

 

  JAXBContext jaxbContext = null;
  Marshaller jaxbMarshaller = null;

  
  jaxbContext = JAXBContext.newInstance(UserBltnList.class);
  jaxbMarshaller = jaxbContext.createMarshaller();
  jaxbMarshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");  
  jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
 
  jaxbMarshaller.marshal(userBltnList, response.getWriter()); 

 

-----------------------------------------------------------------------------------------------------------------

- UserBltnList Class

 @XmlRootElement(name="userBltnList")
 @XmlAccessorType(XmlAccessType.FIELD)
 public class UserBltnList {
 
  //Marshalling시에 Element의 이름을 정해준다. 반대로 UnMarshalling시에는
  //이 이름에 해당하는 Element속성이나 Element의 내용을 가져오게 된다.
  @XmlElement(name="bltnList")
  private List<UserBltnListVO> list = null;

  public List<UserBltnListVO> getList() {
   if(list==null){
    list = new ArrayList<UserBltnListVO>();
   }
   return list;
  } 
  public void setList(List<UserBltnListVO> list) {
   this.list = list;
  }
 }

-----------------------------------------------------------------------------------------------------------------

- UserBltnListVO(Value Object Class)

public class UserBltnListVO {
 private String no = "";
 private String subject = "";
 private String writeDay = "";
 private String readNum = "";
 private String isSecret = "";
 private String name = "";

 ..

  getter/setter 생략

 ..

}

-----------------------------------------------------------------------------------------------------------------

 

여기까지 4부에 걸쳐 JAXB(Java Architecture for XML Binding) API 사용방법에 대해 알아보았습니다. 예전에 DOMParser를

 

통해 삽질하던걸 생각해보면 정말 간편해 졌다는 생각이 듭니다. DOMParser를 사용하다 보면 XML이 복잡해지면 이놈이

 

저놈같고 저놈이 이놈같고 그랬었죠. 머리가 단순해서인지 계층구조는 저하고 좀 안맞는 것 같습니다.;; 지금 다니는 회사에선

 

사실 이런 데이터 변환 처리를 거의 할일이 없는데 알아두면 분명 유용할 듯 싶어 예제를 작성해봤습니다. 어딘가에서 유용하게

 

쓰였으면 좋겠군요. 소스는 그대로 붙혀서 사용하시면 당연히 실행이 안되실것이고 메소드 등은 환경에 맞춰 작성해서 참고

 

하여 사용하시면 될 듯 싶습니다.

신고

1부에서는 파일을 통한 마샬링, 언마샬링 처리, 2부에서는 HttpUrlConnection을 통해 얻어온 InputStream을 언마샬링

 

하는 방법에 대해 알아봤습니다. 이번엔 마샬링한 데이터를 OutputStream에 저장하고 InputStream으로 변환하여

 

언마샬링 처리 하는 예제를 작성해봤습니다. 일반적으로 이렇게 쓰이는 사례는 거의 없을듯 합니다.

 

-XML 원본 데이터 예시(UnMarshalling시 사용)

 <userBltnList>
     <bltnList>
         <isSecret>Y</isSecret>
         <name>TEST</name>
         <no>TESTNO</no>
         <readNum>TESTREADNUM</readNum>
         <subject>Android GCM Push with Command Pattern</subject>
         <writeDay>2013-02-15 18:00:18.0</writeDay>
     </bltnList>
 </userBltnList>

-----------------------------------------------------------------------------------------------------------------

※ 마샬링한 데이터를 OutputStream으로 생성

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;

 

JAXBContext jaxbContext = null;
Marshaller jaxbMarshaller = null;

 

//list는 List(Collection 클래스)

UserBltnList userBltnList = new UserBltnList();
userBltnList.setList(list);
  
ByteArrayOutputStream ous = new ByteArrayOutputStream();
ByteArrayInputStream xmlStream = null;
byte[] byteData = null;
   
jaxbContext = JAXBContext.newInstance(UserBltnList.class);
jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); 
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
 
jaxbMarshaller.marshal(userBltnList, ous);  //마샬링된 데이터를 OutputStream에 저장 
byteData = ous.toByteArray(); //OutputStream을 Byte로 처리
xmlStream = new ByteArrayInputStream(byteData); //Byte를 InputStream으로 처리  

-----------------------------------------------------------------------------------------------------------------

 

- UserBltnList Class

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

 

@XmlRootElement(name="userBltnList")
@XmlAccessorType(XmlAccessType.FIELD)
public class UserBltnList {
 
 //Marshalling시에 Element의 이름을 정해준다. 반대로 UnMarshalling시에는
 //이 이름에 해당하는 Element속성이나 Element의 내용을 가져오게 된다.
 @XmlElement(name="bltnList")
 private List<UserBltnListVO> list = null;

 public List<UserBltnListVO> getList() {
  if(list==null){
   list = new ArrayList<UserBltnListVO>();
  }
  return list;
 } 
 public void setList(List<UserBltnListVO> list) {
  this.list = list;
 }
}

 

-------------------------------------------------------------------------------------------------

- UserBltnListVO(Value Object Class)

public class UserBltnListVO {
 private String no = "";
 private String subject = "";
 private String writeDay = "";
 private String readNum = "";
 private String isSecret = "";
 private String name = "";

 ..

  getter/setter 생략

 ..

}

-----------------------------------------------------------------------------------------------------------------

 

-----------------------------------------------------------------------------------------------------------------

※ OuputStream에 저장된 마샬링된 데이터를 InputStream으로 변환하여 언마샬링 처리

UserBltnListUnMarshall userBltnListUnMarshall = null;

jaxbContext = JAXBContext.newInstance(UserBltnListUnMarshall.class);
userBltnListUnMarshall =

 (UserBltnListUnMarshall) jaxbContext.createUnmarshaller().unmarshal(xmlStream);

-----------------------------------------------------------------------------------------------------------------

 

-----------------------------------------------------------------------------------------------------------------

- UserBltnListUnMarshall Class

@XmlRootElement(name="userBltnList")
@XmlAccessorType(XmlAccessType.FIELD)
public class UserBltnListUnMarshall {
 
 //Marshalling시에 Element의 이름을 정해준다. 반대로 UnMarshalling시에는
 //이 이름에 해당하는 Element속성이나 Element의 내용을 가져오게 된다.
 @XmlElement(name="bltnList")
 private List<UserBltnListVOUnmarshall> bltnList = null;

 public List<UserBltnListVOUnmarshall> getList() {
  return bltnList;
 }
 public void setList(List<UserBltnListVOUnmarshall> bltnList) {
  this.bltnList = bltnList;
 }
}

-----------------------------------------------------------------------------------------------------------------

 

-----------------------------------------------------------------------------------------------------------------

 - UserBltnListVOUnmarshall(Value Object Class)

@XmlAccessorType(XmlAccessType.FIELD)
public class UserBltnListVOUnmarshall {
 
 //UserBltnListUnMarshall에서 루트 Element인 userBltnList를 가져오도록 하고
 //집합 Element인 bltnList를 컬렉션 형태로 가져오도록 했다. 이 bltnList의
 //Element를 XmlElement Annotation을 통해 가져오도록 한다.
 //(Marshalling때와 반대로 UnMarshalling때는 XmlElement나 XmlAttribute Annotation
 //으로 지정한 명을 원본 XML의 Element나 Attribute명을 읽어오게 된다.)
 
 @XmlElement(name="no")
 private String no = "";
 @XmlElement(name="subject")
 private String subject = "";
 @XmlElement(name="writeDay")
 private String writeDay = "";
 @XmlElement(name="readNum")
 private String readNum = "";
 @XmlElement(name="isSecret")
 private String isSecret = "";
 @XmlElement(name="name")
 private String name = "";

 ..

  getter/setter 생략

 ..

}

-----------------------------------------------------------------------------------------------------------------

 

JAXB의 Unmashaller가 OutputStream을 알아서 처리해 줬다면 참 좋았겠지만 지원하지 않으므로.. 위와같은 방법으로 사용할

 

수 있음을 예제로 만들어 봤습니다. 다음 예제에서는 최종으로 HttpServletResponse객체의 getWriter()를 사용하여 마샬링된

 

데이터를 바로 출력할 수 있는 예제를 작성해 보겠습니다.

 

신고

JAXB를 이용한 File로 마샬링, 마샬링된 XML을 언마샬링 하는 방법에 대해 살펴보았습니다. 이번엔 HttpUrlConnection

 

을 통해 얻어온 InputStream으로 언마샬링 하는 예제를 써보겠습니다. 1부의 File을 통한 Marshalling, UnMarshalling

 

예제를 보시고 나면 금방 이해 되실 듯 싶습니다. 1부에서는 List형 데이터를 마샬링, 언마샬링 하는 방법을 다뤘고

 

이번에는 약간 변경하여 XML Element의 Attribute처리를 다뤄봤습니다.

 

-XML 원본 데이터 예시(UnMarshalling시 사용)

 <packing_list>
    <packing_data barcode_read_date="XXX" packing_no="XXX" reg_userid="XXX" complete_yn="Y"     matched_result="N" /> 

    <packing_data barcode_read_date="XXX" packing_no="XXX" reg_userid="XXX" complete_yn="Y"     matched_result="N" /> 
 </packing_list>

 

-------------------------------------------------------------------------------------------------※ HttpUrlConnection을 통해 XML을 InputStream으로 가져옴

 public InputStream getXmlStreamFromUrl(Map<String, Object> paramMap){
  String packingNo = (String)paramMap.get("packing_no");
  
  URL url = null;
  HttpURLConnection httpUrlConnection = null;
  InputStream ips = null;
  
  try {
   url = new URL("
http://testurl.com/test.xml");   
   URLConnection urlConnection = url.openConnection();
   httpUrlConnection = (HttpURLConnection) urlConnection;
   
   httpUrlConnection.setDoInput(true);
   httpUrlConnection.setDoOutput(true);
   httpUrlConnection.setRequestMethod("POST");
   httpUrlConnection.setUseCaches(false);
   httpUrlConnection.setDefaultUseCaches(false);
   httpUrlConnection.setRequestProperty("Content-Type", "text/xml");
   
   ips = httpUrlConnection.getInputStream();
   ips.close();
  } catch (MalformedURLException e) {
   e.printStackTrace();
  } catch (IOException e) {
   e.printStackTrace();
  }
  return ips;
 }

------------------------------------------------------------------------------------------------

 - HttpUrlConnection을 통해 가져온 XML(InputStream) 언마샬링

 //UnMarshalling을 위해 XML을 HttpURLConnection을 통해 가져온 InputStream을
 //가져온다.  

 import javax.xml.bind.JAXBContext;

 import javax.xml.bind.Marshaller;

 

 JAXBContext jaxbContext = null;


 InputStream xmlStream = getXmlStreamFromUrl(paramMap);   
 PackingList packingList = null;

 

  try {   
   jaxbContext = JAXBContext.newInstance(PackingList.class);
   packingList = (PackingList) jaxbContext.createUnmarshaller().unmarshal(xmlStream);  
  } catch (JAXBException e) {
   e.printStackTrace();
  }

------------------------------------------------------------------------------------------------

- UnMarshalling을 위한 Java Class - PackingList

//UnMarshalling시에는 Marshalling과 반대로 XmlRootElement어노테이션에 지정한 이름에
//해당하는 데이터를 가져오게 된다.
@XmlRootElement(name="packing_list")
@XmlAccessorType(XmlAccessType.FIELD)
public class PackingList {
 //UnMarshalling시에는 Marshalling과 반대로 XmlElement어노테이션에 지정한 이름에 해당하는
 //데이터를 가져오게 된다.(Marshalling시에는 필드이름으로 지정하게 됨)
 
 //PackingListVO에서는 원본 XML과 같이 각 속성의 이름을 통해 값을 가져오도록 함.
 @XmlElement(name="packing_data")
 private List<PackingListVO> list = null;

 public void setPackingList(List<PackingListVO> packList){
  this.list = packList;
 }
 public List<PackingListVO> getPackingList(){
  return this.list;
 }
}

------------------------------------------------------------------------------------------------

- PackingListVO Class - ValueObject Class(DTO)

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;

 

//XML로드시 필드명으로 읽어 들이겠다고 선언
@XmlAccessorType(XmlAccessType.FIELD)
public class PackingListVO {
 
 //XML에 정의된 속성명을 동해 값을 가져오게 된다. 
 @XmlAttribute(name="barcode_read_date")
 private String barcodeReadDate = "";
 @XmlAttribute(name="packing_no")
 private String packingNo = "";
 @XmlAttribute(name="reg_userid")
 private String regUserID = "";
 @XmlAttribute(name="complete_yn")
 private String completeYN = "";
 @XmlAttribute(name="matched_result")
 private String matchedResult = "";

 

 ..

  getter/setter 생략

 ..

------------------------------------------------------------------------------------------------

HttpUrlConnection을 통해 언마샬링 처리는 실무에서 직접 적용 해본바로는 다른 프로그래밍 언어를 사용하는 타사와의

 

데이터 연동시에 유용하게 쓰였습니다. 사용하는 방법은 1부에서 파일로 XML마샬링 후 처리하는것과 비슷하게 크게

 

복잡한 부분은 없었습니다. 접속만 잘된다면 매우 유용하겠지요.

신고

흔히 데이터 처리 방법으로 과거부터 많이 XML을 SAX나 DOM API를 사용 하여 데이터를 가져오는 방식을 많이

 

사용 해왔습니다. 요즘은 프론트 엔드에서 AJAX를 사용한 화면전환없이 처리하는 방법을 많이 사용하면서

 

JSON(Java Script Object Notation)을 많이 사용하고 있지만 여전히 XML도 많이 사용되고 있습니다.

 

보통 스프링 프레임워크를 사용하시는 분들은 XStream을 사용하시는 것 같은데 JAXB에 비해 코드 처리가 간결해 보이긴

 

했습니다. 아직 XStream을 써보지 않은 관계로 JAXB를 통한 여러가지 XML Marshalling, UnMarshalling 처리에 대해

 

몇가지 써볼까 합니다.

 

1. JAXB(Java Architecture for XML Binding)

 

  이걸 뭐라고 읽어야 될지부터 좀 난감합니다. 전 그냥 '작스비' 라고 합니다. AJAX도 부르기 나름인것 같은데 어떤 분은

 

  에이잭스, 아약스, 아작스등..ㅎㅎ 아무튼 XML 스키마를 통해 Java 클래스 데이터를 Marshalling(직렬화), UnMarshalling

 

  (역직렬화) 작업을 좀 더 편리하게 하기 위해 만들어진 API라고 보시면 되겠습니다. Serialize와 Marshalling에는 개념의

 

  차이가 조금 있긴 합니다만 사전적 의미로 Marshalling은 '정렬하다'에 의미를 많이 두고 있는데 직렬화도 정렬된 데이터를

 

  바이트 스트림으로 전송가능하게 만든다는 의미를 두고 있으므로 차이는 있지만 비슷한 개념으로 생각하고 있습니다.

 

  어쨋든 이론은 훌륭해도 실제 써보기 전엔 의미에 불과하니 몇가지 처리방법에 대해 알아보겠습니다. 참고로 JDK 1.6버전

 

  이상에서는 별도의 라이브러리 없이 사용가능하다고 되있는데 저는 라이브러리를 다운받아서 테스트 해봤습니다.

 

1-1.  Marshalling (XML 파일 생성)

 

 처음 JAXB를 접하면서 구글링하면서 가장 많이 본 예제가 Java Object데이터를 마샬링하여 XML파일로 생성하는

 

 예제 였습니다. 실제 구현하다 보면 파일로 마샬링 하는것이 가장 심플하긴 합니다. 예를 들어보겠습니다. 자세한 소스코드는

 

생략하고 핵심 부분만 알아보겠습니다. 중간중간에 있는 주석을 보시면 이해되실 것 같습니다.

 

-XML 원본 데이터 예시(UnMarshalling시 사용)

 <userBltnList>
     <bltnList>
         <isSecret>Y</isSecret>
         <name>TEST</name>
         <no>TESTNO</no>
         <readNum>TESTREADNUM</readNum>
         <subject>Android GCM Push with Command Pattern</subject>
         <writeDay>2013-02-15 18:00:18.0</writeDay>
     </bltnList>
 </userBltnList>

 

-----------------------------------------------------------------------------------------------------------------

 

※ 마샬링한 데이터를 파일(XML)로 생성

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

 

 JAXBContext jaxbContext = null;
 Marshaller jaxbMarshaller = null;

  UserBltnList userBltnList = new UserBltnList();
  userBltnList.setList(list);
  UserBltnListUnMarshall userBltnListUnMarshall = null;
  
  File file = new File("D:\\xmlresult.xml");
  jaxbContext = JAXBContext.newInstance(UserBltnList.class);
  jaxbMarshaller = jaxbContext.createMarshaller();  
  jaxbMarshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");  
  jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
  jaxbMarshaller.marshal(userBltnList, file);

 

- 마샬링 대상 UserBltnList Class

 //Marshalling시에 Element의 이름을 정해준다. 반대로 UnMarshalling시에는
 //이 이름에 해당하는 Element속성이나 Element의 내용을 가져오게 된다.

 //(XmlRootElement Annotation을 사용하여 UserBltnList클래스가 RootElement로

//  사용됨을 선언함)

 @XmlRootElement(name="userBltnList")
 @XmlAccessorType(XmlAccessType.FIELD)
 public class UserBltnList {
 
 //Marshalling시에 Element의 이름을 정해준다. 반대로 UnMarshalling시에는
 //이 이름에 해당하는 Element속성이나 Element의 내용을 가져오게 된다.
 @XmlElement(name="bltnList")

 

 //Generic을 통해 Collection클래스에 UserBltnListVO(Value Object)가

 //저장됨을 지정함. 
 private List<UserBltnListVO> list = null;

   public List<UserBltnListVO> getList() {
     if(list==null){
       list = new ArrayList<UserBltnListVO>();
     }
     return list;
   } 
   public void setList(List<UserBltnListVO> list) {
     this.list = list;
   }
  }

 

UserBltnListVO Class - ValueObject Class(DTO)

public class UserBltnListVO {
 private String no = "";
 private String subject = "";
 private String writeDay = "";
 private String readNum = "";
 private String isSecret = "";
 private String name = "";
 ..

  getter/setter 생략

 .. 
}

-----------------------------------------------------------------------------------------------------------------

※ 마샬링된 XML(파일)을 언마샬링

 UserBltnListUnMarshall userBltnListUnMarshall = null;

 jaxbContext = JAXBContext.newInstance(UserBltnListUnMarshall.class);
 userBltnListUnMarshall = (UserBltnListUnMarshall) jaxbContext.createUnmarshaller().unmarshal(file);

 

- 언마샬링 대상 UserBltnListUnMarshall Class

@XmlRootElement(name="userBltnList")
@XmlAccessorType(XmlAccessType.FIELD)
public class UserBltnListUnMarshall {
 
 //Marshalling시에 Element의 이름을 정해준다. 반대로 UnMarshalling시에는
 //이 이름에 해당하는 Element속성이나 Element의 내용을 가져오게 된다.
 @XmlElement(name="bltnList")
 private List<UserBltnListVOUnmarshall> bltnList = null;

 public List<UserBltnListVOUnmarshall> getList() {
  return bltnList;
 }
 public void setList(List<UserBltnListVOUnmarshall> bltnList) {
  this.bltnList = bltnList;
 }
}

 

- 언마샬링 대상 UserBltnListUnMarshall Class의 Collection클래스 List의 VO

@XmlAccessorType(XmlAccessType.FIELD)
public class UserBltnListVOUnmarshall {
 
 //UserBltnListUnMarshall에서 루트 Element인 userBltnList를 가져오도록 하고
 //집합 Element인 bltnList를 컬렉션 형태로 가져오도록 했다. 이 bltnList의
 //Element를 XmlElement Annotation을 통해 가져오도록 한다.
 //(Marshalling때와 반대로 UnMarshalling때는 XmlElement나 XmlAttribute Annotation
 //으로 지정한 명을 원본 XML의 Element나 Attribute명을 읽어오게 된다.)
 @XmlElement(name="no")
 private String no = "";
 @XmlElement(name="subject")
 private String subject = "";
 @XmlElement(name="writeDay")
 private String writeDay = "";
 @XmlElement(name="readNum")
 private String readNum = "";
 @XmlElement(name="isSecret")
 private String isSecret = "";
 @XmlElement(name="name")
 private String name = "";

 ..

  getter/setter 생략

 ..

-------------------------------------------------------------------------------------------------

 

글로 쓰다보니 내용이 꽤 길지만 개념상으론 그렇게 복잡하진 않습니다. 다만 XML이 복잡할수록 대응되는 자바 클래스를 생성

 

해야하는것이 좀 복잡할 수 있겠습니다. 원래는 File외에 HttpURLConnection이나 InputStream, OutputStream,

 

HttpServletResponse의 getWriter()를 사용한 예도 한번에 쓰려고 했지만 복잡스러운것 같아서 4부에 걸쳐 작성하도록

 

하겠습니다.


신고

지금 회사에는 쓰는 ERP시스템을 좀더 편하게 보기위해서 MIS비스무레한 걸 만들다가 하도 오랜만에 날짜관련된걸

만드려니 한참 해매게 된..ㅜ Calendar API는 정말 편리하긴 하지만 사용법을 잘 모르면 API 뒤지느라 좀 짜증나기도 하네요.

역시 반복학습과 경험이 중요한것 같습니다.

사용법 :

   -  첫번째 예제 : 파라미터로 기준 날짜(ex)201304, 가감할 숫자를 넘깁니다.

   -  두번째 예제 : 파라미터로 가감할 숫자를 넘깁니다.

   -  세번째 예제 : 파라미터로 기준 날짜(ex)201304를 넘깁니다.

 //파라미터의 해당하는 년월의 전달을 구한다.
 public String getBeforeYearMonthByYM(String yearMonth, int minVal){
  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMM");
  Calendar cal = Calendar.getInstance();
  int year = Integer.parseInt(yearMonth.substring(0,4));
  int month = Integer.parseInt(yearMonth.substring(4,6));

  cal.set(year, month-minVal, 0);

  String beforeYear = dateFormat.format(cal.getTime()).substring(0,4);
  String beforeMonth = dateFormat.format(cal.getTime()).substring(4,6);
  String retStr = beforeYear + beforeMonth;

  System.out.println("retStr : "  + retStr);
  return retStr;
 }

 //현재 년월의 전달을 구한다.
 //param : minVal - ex)0,1,2,.....
 public String getBeforeYearMonthByYM(int minVal){
  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMM");
  Calendar cal = Calendar.getInstance();
  cal.add(cal.MONTH, -minVal);  
 
  String beforeYear = dateFormat.format(cal.getTime()).substring(0,4);
  String beforeMonth = dateFormat.format(cal.getTime()).substring(4,6);

  String retStr = beforeYear + beforeMonth;
  System.out.println("retStr : " + retStr);
  return retStr;
 }

 //해당년월의 마지막 날짜를 구한다.
 public String getLastDayOfMonth(String yearMonth){  
  String year = yearMonth.substring(0,4);
  String month = yearMonth.substring(4,6);
  
  int _year = Integer.parseInt(year);
  int _month = Integer.parseInt(month);
  
  Calendar calendar = Calendar.getInstance();
  calendar.set(_year, (_month-1), 1); //월은 0부터 시작  
  String lastDay = String.valueOf(calendar.getActualMaximum(Calendar.DATE));
  System.out.println("lastDay of present month  : " + lastDay);
  
  return lastDay;
 }

 

 

신고

지난번에 파일 업로드 프로그레스바 구현에 대한 글을 포스팅 한적이 있었습니다. 당시에는 단일 파일 업로드만 구현 했는데

이번에는 다중 파일 업로드 구현에 대해서 써볼까 합니다. 단일 파일 업로드 기능을 구현할때도 그랬지만 웹에서 파일업로드

기능 구현은 거의 해본적이 없습니다. 보통 OCX를 통한 파일 업로드 방식만  많이 사용해서 봐서 삽질을 많이 했네요.

일단 구현전에 괜찮은 파일 업로드 모듈을 찾아 봤습니다. 아무래도 제가 직접 제작하는것도 좋지만 저보다 뛰어난 분들이

만든 소스가 더 믿음직스러우니까요. 하지만 찾아보시면 보통 JS + Flash나 Flex를 사용한 파일 업로드 모듈을 보시게

될겁니다. 일반 PC환경에서면 크게 거슬리진 않지만 상황이 많이 나아지긴 했어도 모바일 환경하에서는 Flash 플레이어를

설치하지 않는 경우가 훨씬 많고 따로 설치 해야하는것도 은근히 번거롭게 느껴졌습니다. 그리고 요즘에 출시되는

스마트폰들은 성능이 좋지만 제가 현재 쓰고 있는 갤럭시U 모델의 경우 일반 웹도 버겁게 느껴질때가 많기 때문에 Flash나

Flex를 사용한 모듈은 배제하고 찾아보기로 했습니다. 그리고 다양한 기능보다는 파일업로드에 중점을 맞춘 모듈을 찾아보게

됐는데..

첫번째로 찾게 된것은 위와 같습니다. 제가 원했던 심플한 기능이었고 가장 맘에 들었던 것은 sumit 부분은 알아서 컨트롤 할 수

있게 되있다는 것 이었습니다. 그러나 input file을 css로 디자인 하게 되면 기능이 제대로  실행되지 않는다는 글을 보게 되서

쓰지 않게 됐습니다. (확실한 정보는 아닙니다.) 또 하나 찾게 됐던것은..


위와 같은 모듈입니다. (http://blueimp.github.com/jQuery-File-Upload/) 하지만 위 모듈의 경우 모바일 환경에서 일단 UI

때문에도 그냥 사용할 수도 없고 소스는 받아봤지만 커스터마이징 해서 쓰기에 시간이 너무 오래 걸릴것 같아 포기했습니다.

기능 자체는 상당히 깔끔하고 좋습니다. 한개 더 찾은 것 중에 쓸만하다 생각된것이 바로 아래와 같습니다. UI도 수정하기

크게 까다롭지 않고 플러그인 JS자체도 분석하기 수월 했습니다만.. 특정 ID를 가진 Submit버튼을 두고 전송을 해야해서

저의 경우 jQuery.form 의 ajaxSubmit()을 사용해야 할 필요가 있어서 패스하게 됐습니다.

보통은 찾아봤던 모듈들이 위와 같이 파일 업로드 자체만의 기능을 가지고 구현되어 있어서 다른 기능들에 포함해서

구현하려면 우선 모듈을 분석하는것이 먼저라 복잡하게 느껴져서 그냥 전부 패스 하고 직접 만들어 보게 됐습니다.

처음에 구현하려고 생각했던것이 위와 같은 형태입니다. '파일' 버튼(input type 'file'이 스타일링 된것)을 누르면 업로드

리스트가 추가되고 전송 버튼을 눌러 전송하는 형태 입니다. 위와 같이 구현하기 위해서는 가장 귀찮은 것이 파일추가 input

을 스타일링 하는 것입니다. 위 버튼의 경우 input file바깥의 div에 absolute position을 줘서 초과범위는 hidden 시키는

방법으로 input file을 숨겨놓은 형태입니다. 두번째로 문제가 되는것은 업로드할 파일 리스트 입니다. 업로드를 구현하는

back end(JSP,JAVA,PHP)는 구현하기 나름이겠지만 아래의 리스트에 파일 목록만 보일뿐 전송시에 input file의 value를

건네주지 못해서 구현 하나마나가 됩니다. 그러면 구현시 input file을 동적으로 생성하고 뿌려준다음 input file의 value를

주면 되지 않을까가 바로 생각나겠습니다만 보안상 이유로 input file의 경우 readonly 입니다. 때문에 사용자가 직접 파일을

선택하여 추가한 상태가 아니라면 value를 직접 set하는 것은 불가능 합니다. 예제에서는 위와 같이 스타일링 된 파일 버튼으로

파일을 선택하면 input file이 다시 동일 위치에 생성되며 이전의 element는 숨겨지는 형태입니다. 따라서 '파일' 버튼으로

다섯번 파일을 선택했다면 눈에 보이진 않으나 input file element가 다섯개가 생성된 상태입니다. 해당 내용은 예제내의

$("div#debug").text() 를 사용해서 뿌려보시면 이해가 가실 겁니다. 전에 포스팅한 글에서 예제로 업로드를 수행하는

부분의 스프링 MVC컨트롤러를 추가해뒀으므로 이번에는 JSP만 올려놓도록 하겠습니다. 항상 그랬지만 구현이 목적으로

실사용에 문제가 될 수 있는 부분도 있습니다. 이렇게 구현할 수도 있겠다 하는 방법만 보시면 되겠습니다.


multiuprogress.jsp


구현하고 보니 웹이 많이 발전하긴 했으나 파일입출력에 있어서는 아직 지원이 미흡하다는 느낌이 많이 드네요.

여담으로 HTML5에서는 위와 같은 꼼수를 쓰지 않고도 다중 파일 업로드를 구현할 수 있어 무척 반갑네요.

<input type="file" value="" name="upload[]" multiple>

위와같이 multiple 속성 선언으로 input의 '찾아보기' 버튼을 눌렀을때의 윈도우 다이얼로그에서 여러개의 파일을 선택할 수

있습니다. 저는 파이어폭스만 테스트 해봤으나.. 최신버전의 파이어폭스, 사파리, 크롬에서 작동한다고 합니다.

신고

요즘 N,D포털을 비롯해서 거의 모든 사이트에서 Front-end 구현 기술에 빠지지 않고 포함되는 기술로 AJAX가 사용되는

것 같습니다. 사실 AJAX자체는 신기술이라 할 수는 없지만 jQuery같은 훌륭한 자바스크립트 라이브러리가 나오기 전에는

뷰페이지를 표현하는데 있어서 AJAX가 많이 사용됐던것 같진 않습니다. 물론 이전에 prototype.js와 같은 라이브러리가

있긴 했으나 지금처럼 어느곳에서나 눈에 띌정도로 많이 사용됐던 것 같진 않네요. 저 같은 경우에도 AJAX를 깊이있게

사용해본적은 그다지 없고 request중에 비동기적 기능호출이 필요한 경우에만 종종 사용해왔던 것 같습니다.

하지만 대세를 따르다 보니 이젠 AJAX, JSON사용은 필요보다는 필수가 되가고 있네요. JSON이 두루 사용되기 전에는

AJAX를 통해 XML데이터나 Text데이터를 호출해서 사용하는 방식이 주였으나 최근에는 JSON 방식이 주로 사용되는

것 같습니다. JSON표기를 어떻게 사용하는지 확인해 보겠습니다.

- JSON (JavaScript Object Notation)

 이름과 같이 자바스크립트 객체 표기법을 JSON이라고 합니다. 예를 들어 다음과 같은 데이터를 JSON객체로 표현한다고

보겠습니다.

ex) 데이터

            친구
                -학교
                   - 김친구(성격:더러움), 이친구(성격:안착함)
                -회사
                   - 오대리(성격:좋음), 이대리(성격:다혈질)

친구는 학교친구도 있고 회사 친구도 있습니다. 학교친구,회사친구들은 각각 집합으로 볼수 있으니 배열로 표현 할수 있겠죠.

JSON에서는 객체나 배열 안에 또다른 객체나 배열을 표기 할수 있습니다. 그래서 학교친구와 회사친구를 배열로 만들고

학교와, 회사라는 객체에 각각 넣어준뒤 학교친구,회사친구 배열이 들어간 학교,회사 객체를 친구 객체에 추가 한다고 생각

하시면 되겠습니다. 이를 JSON으로 다음과 같이 표현할 수 있습니다.

ex) JSON

  var friend =

              {
                  "school" : {
                      "person" : [{"name":"김친구","personallity":"더러움"},

                                      {"name":"이친구","personallity":"안착함"}]
                  },
                  "company" : {
                      "person" : [{"name":"오대리","personallity":"더러움"},{"name":"이대리","personallity":"다혈질"}]
                  }
              }
;

JSON에서는 표기시 {"key" : "value"} 형식으로 키이름과 키이름에 따른 데이터가 함께 있어야 합니다. 위에서 보시면

person 배열에는 또다른 학교,회사 친구들의 이름과, 성격이 객체로 들어가 있습니다. 여기에 또 다시 배열이나 객체가

들어가는것도 가능합니다. 아래 예를 보시죠.

ex) 데이터

            친구
                -학교
                   - 김친구(성격:더러움)(핸드폰:010-2222-3333,010-3333-4444), 이친구(성격:안착함)
                -회사
                   - 오대리(성격:좋음), 이대리(성격:다혈질)

ex) JSON

  var friend =

              {
                  "school" : {
                      "person" : [{"name":"김친구","personallity":"더러움",

                                       "mobiles":["010-2222-3333","010-3333-4444"]},

                                       {"name":"이친구","personallity":"안착함"}]
                  },
                  "company" : {
                      "person" : [{"name":"오대리","personallity":"좋음"},

                                      {"name":"이대리","personallity":"다혈질"}]
                  }
              }
;

김친구는 핸드폰이 두개가 있습니다. JSON데이터는 다시 XML로 표현하는것도 가능하며 김친구의 핸드폰과 같은 텍스트

노드들의 집합은 배열로 지정합니다. JSON데이터는 아래 예와 같이 XML이나 JavaScript로도 다시 표현 할 수 있습니다.

ex)XML

<friend>

<school>

<person>

<name>김친구</name>

<personallity>더러움</personallity>

<mobiles>

<mobile>010-2222-3333</mobile>

<mobile>010-3333-4444</mobile>

</mobiles>

</person>

<person>

<name>이친구</name>

<personallity>안착함</personallity>

</person>

</school>

<company>

<person>

<name>오대리</name>

<personallity>좋음</personallity>

</person>

<person>

<name>이대리</name>

<personallity>다혈질</personallity>

</person>

</company>

</friend>


ex) Java Script

    var friend = new Object();
    friend.school = new Object();
    friend.company= new Object();
   
    friend.school.person = new Array();
    friend.school.person[0] = new Object();
    friend.school.person[0].name="김친구";
    friend.school.person[0].personallity="더러움";
    friend.school.person[0].mobiles = new Array();
    friend.school.person[0].mobiles[0] = "010-2222-3333";
    friend.school.person[0].mobiles[1] = "010-3333-4444";
    friend.school.person[1] = new Object();
    friend.school.person[1].name="이친구";
    friend.school.person[1].personallity="안착함";
   
    friend.company.person = new Array();
    friend.company.person[0] = new Object();
    friend.company.person[0].name="오대리";
    friend.company.person[0].personallity="좋음";
    friend.company.person[1] = new Object();
    friend.company.person[1].name="이대리";
    friend.company.person[1].personallity="다혈질";   

그렇다면 JSON은 뭐하러 사용할까요? 파싱과정을 거쳐야 하는 불편함이 있을때가 있지만 일단 가독성이 좋기 때문이죠.

하지만 XML의 경우 파싱하여 사용할때 DOM Parser를 사용하는데 DOM(Document Object Model)은  XML 구조를

트리형태로 구성하여 메모리 상에 구조를 만들면서 작은양의 데이터가 오갈때는 큰 무리가 없지만 큰 데이터가 오갈때는

사용자의 PC가 느려질수 있기도 합니다. JSON의 경우 경량 데이터 교환 포맷으로 XML에 비해 가볍다라고 하긴 하지만..

체감상으로 느껴본적은 없는 것 같습니다. 아마도 요즘엔 PC사양이 대부분 듀얼코어 이상의 웹서핑등에 사용될때는

고사양이라 그런 듯 싶습니다. 어쨋든 JSON 표기법은 처음엔 어색하지만 위의 예와 같이 우선 문자열 자체가 줄어들어

코딩량이 줄어들어드는 장점이 있습니다. 하지만 단점으로는 위와 같은 예제에서는 줄을 나눠서 가독성이 좋지만 한줄로

표현하게 되면 가독성이 좋지 않고 데이터 가공까지는 그렇다 쳐도 출력시킬때는 꽤 헷갈릴때가 많습니다.

실제로 개발을 하다보면 위와같이 데이터를 표현하는 경우는 드물고 한줄로 쫙 뿌리는 경우가 많습니다.

ex)JSON 데이터를 한줄로 표현

  var friend = {"school" : {"person" : [{"name":"김친구","personallity":"더러움","mobiles":                     ["010-2222-3333","010-3333-4444"]},{"name":"이친구","personallity":"안착함"}]},
                     "company" : {"person" : [{"name":"오대리","personallity":"좋음"},{"name":"이대                      리","personallity":"다혈질"}]}};

ex)XML 데이터를 한줄로 표현

<friend><school><person><name>김친구</name><personallity>더러움</personallity>

<mobiles><mobile>010-2222-3333</mobile><mobile>010-3333-4444</mobile></mobiles>

</person><person><name>이친구</name><personallity>안착함</personallity></person>

</school><company><person><name>오대리</name><personallity>좋음</personallity>

</person><person><name>이대리</name><personallity>다혈질</personallity></person>

</company></friend>

보통 이와 같은 형태로 DB에 데이터를 일정형식으로 그대로 넣었다가 가져오는 경우도 있고 DB의 데이터를 JSON이나

XML형태로 가공하여 사용하기도 합니다. 그냥 얼핏 봐도 JSON 표기법의 경우 한줄로 표현할 경우 어디까지가 객체의 끝인지

배열의 끝인지를 알수가 없습니다. 물론 XML도 한줄로 표현하면 보기 힘든점은 마찬가지긴 합니다만 그래도 JSON표기에

비해서는 좀더 쉽게 구분이 갑니다. 예제에서야 간단한 데이터로 표현했지만 데이터량이 많아질수록 가독성이 떨어지는

것은 어쩔 수 없는 부분인것 같습니다. 그래도 우선 저의 경우는 XML의 경우 DOM Parser를 거쳐 파싱해야 하는 과정이

복잡하고 버겁게 느껴질때가 많아서 JSON사용이 주류가 되는것에 대해 환영하는 입장입니다. 그렇다고 XML이 JSON보다

나쁘다라고 할수는 없고 개발할때 느끼지만 항상 상황에 적당한 기술 사용이 더 중요한 것 같습니다.

※예제는 실제로 실행해 보지 않아서 확실히 맞는지는 모르겠습니다. JSON표기법을 어떻게 사용하는지 객체,배열의 매핑

방법이 이렇다 하는것만 알아보시고 실제로 브라우저로 테스트 해보시기 바랍니다.

신고

파일 업로드 라이브러리 선택(COS,Apache Commons Upload)에 대해 포스팅 했던 글에 이어 이번에는 파일 업로드

구현 예제에 대해 올려보겠습니다. 파일 업로드시에 보통 작은 데이터의 경우 손쉽게 jQuery의 ajaxStart, ajaxStop function을

통해 AJAX 요청 시작시, 완료시에 대한 이벤트를 처리 할 수 있으므로 ajaxStart()시에 GIF이미지 등을 화면에 띄워줘서

처리 상태를 사용자에게 알릴 수 있습니다. 보통 큰 용량의 파일이 업로드 될때는 JSP등의 페이지로 처리 하기 보다는 별도의

업로드 모듈을 사용하는것이 낫겠지만 5MB~10MB내의 파일이 오갈때에는 굳이 그럴 필요없이 웹으로만 구현하는것도

충분하다고 생각합니다. PC의 경우 굳이 프로그레스바를 보여주는것 보다는 간단하게 위와 같은 방법으로 처리하는것이

나을지 모르겠으나 스마트폰의 전송속도는 3G망이나 4G나 일반 유선통신 속도와 비교할것이 못되기도 하고 잘 안터지는

지역에서는 스피너 이미지로 프로그레스바를 보이는것 만으로는 사용자가 작업 완료 여부를 알 수 없기 때문에 만들어

보게 됐습니다.


progressbarex.zip

※압축첨부한 예제 파일들의 경우 거의 그대로 적용하시면 실행 되실겁니다. JAVA파일의 경우 package를 환경에 맞게

추가 하시면 됩니다. 구현이 목적으로 성능등은 고민한 흔적같은 것 없습니다. 그냥 예제로 봐주세요.ㅋ

그리고 Spring MVC컨트롤러에 대한 이해가 없으신 분들은 *.do로 호출되는 기능들만 따로 떼서 구현해보셔도 되겠습니다.

예전에 JAVA의 Swing을 사용해서 Thread 구현하여 만들어본 프로그레스바 외에는 이제까지 웹으로 프로그레스바를

구현해본적이 없어서 바로 감이 오지는 않았고 사실 구현 전에는 적당한 예제 소스를 많이 검색해 봤습니다. 근데 괜찮다 싶은

모듈이어도 적용하기가 어려운 경우가 많고 모바일 웹 화면에서 사용하려면 UI도 수정해야 되는 경우가 많았고 대부분의

예제는 JSP → <form> action으로 서블릿 호출 → 서블릿에서 Apache Commons Upload를 통해

전송상태를 PrintWriter로 출력 하는 것이 대부분이었습니다. 또한 파일 업로드 자체는 XMLHttpRequest에서 지원하지

않아 jQuery.form 라이브러리를 사용한 ajaxForm()등을 사용하여 업로드 하는 방식을 많이 보게 됐는데 잘 적용되지

않았습니다. 저의 경우 Spring Web MVC를 사용하여 구현하고 있었는데 이 구조 특성상 request가 완료된후 페이지로

포워딩 하므로 중간에 프로그레스 상태를 listen하여 화면에 출력될때 어떻게 하면 좋을까 고민이 됐습니다. 한참 고민중에

세션이 생각이 나더군요. 제가 만든 예제의 대략적인 프로그레스바 출력 흐름은 다음과 같습니다.

progressbardemo.jsp 에서 파일 선택후 전송버튼 클릭시 AJAX로 fileupload.do

(스프링 컨트롤러(Controller.java)에 매핑된 주소) 호출 ,

호출시에 jQuery.ajaxStart()에서 uploadstatus.do를 호출하여 fileupload.do가 실행중의 프로그레스 상태를

얻어오기 위해 주기적으로 UploadProgressListener를 호출함

fileupload.do에서 Apache Commons Upload를 사용하여 업로드 처리 중

UploadProgressListener에 세션을 셋팅 →

UploadProgressListener의 update()메소드에서 업로드 진행상태를

세션에 셋팅해서 upstatus.jsp로 포워딩함 →

초기에 progressbardemo.jsp에서 ajaxStart()에서 uploadstatus.do가 호출될때 세션에 셋팅된 파일의 전송정보

(읽은 바이트수, 전체크기, 전송속도) 등을 JSON으로 출력함 →

전송완료후 clearInterval로 setInterval로 확인하던 업로드 상태를 중지시킴

jQuery가 AJAX요청시에 지원하는 표현 간단하면서 강력한 기능들이 많은 도움이 됐네요. 이걸 이전 방식으로 XMLHttpRequest

를 일일히 얻어와서 구현하는 형태로 만들었다면 소스량도 많아지고 많이 복잡했겠지요. 예제 내용은 사실 효율적으로 보면

매우 좋지 않습니다. 결과만 보기위해서 만들긴 했습니다만 일단 실 서비스에 적용해서 사용한다면 파일 업로드가 진행중일때

upstatus.jsp가 JSON String을 얻어오기 위해 계속해서 호출이 되므로 여러 사용자가 사용시 서버에 많은 부하가 걸릴 수

있습니다. 이 방법보다는 MVC컨트롤러가 호출될때 세션에 셋팅된 파일 전송 정보 JSON Object를 얻을수 있는 방법을

전에 어느분의 블로그에서 본것 같은데 기억이 가물하네요. 찾아서 한번 적용해 봐야겠습니다.

또한 Spring Framework 자체에서 Apache Commons Upload를 Injection해서 사용할 수 있는 방법도 봤습니다만

일단은 익숙해진 방법으로 구현했습니다. 좀 더 효율적인 방법으로 수정해서 사용하실 분은 공유 해주셨으면 좋겠네요.

보통 파일 업로드 모듈이 Flash나 Flex를 사용한 환경이 많아 모바일 환경에서 사용자체는 가능하나 잘 맞지 않고 현재 제가

만든 게시판의 경우 하이브리드 앱 형태로 웹뷰를 사용한 앱위에서 웹페이지를 호출하는 형태인데 자료를 찾다보니 이 또한

안드로이드 앱 자체에서 업로드 진행상태 구현 방법에 대해서만 많이 나와 있어서 구현해 보게 됐습니다. 다운로드의 경우

에도 구현할수는 있겠지만 보통 다운로드의 경우 브라우저에서 진행상태를 보여주는 경우가 많으므로 생략했습니다.

좀 더 효율적인 방법 제시해주시는 분들은 언제나 환영입니다^^


신고

얼마전 공부겸 해서 Spring Framework 2.5, jQuery, iBatis를 사용한 모바일 전용 게시판을 제작 했습니다.

처음에는 그냥 기본적인 기능만 만들고 다른것 좀 공부 하려고 했는데 만들다 보니 재미가 붙어서 파일 업로드 기능도

추가 하게 됐습니다. 다중 업로드도 구현하려고 했지만 귀찮아서 단일 업로드만 구현하게 됐네요.

여튼.. 파일 업로드를 워낙 오랜만에 구현하려다 보니 기억나는 것도 없고 뭘 사용할까 고민이 됐습니다. 그래서 검색하다보니

가장 많이 사용하는듯 한 라이브러리로 cos.jar가 눈에 띄었습니다.

(com.oreilly.servlet의 약자이죠. 오라일리 책 시리즈로 으로 유명한..)

하나는 Apache Commons Upload 였는데 둘다 구현하기가 까다로운 편은 아니나 cos.jar가 구현방식 자체는 상당히

간편해 보였습니다. 그래서 cos.jar를 사용해서 파일 업로드를 구현하기로 맘 먹게 됐습니다. 하지만 cos.jar를

사용해서 구현 후에 다시 Apache Commons Upload를 사용하게 되었는데 구현시의 다른 부가적인 조작 편의성에 대해

의견을 써볼까 합니다.

※구현 방법등에 대해선 다루지 않겠습니다. 다른 훌륭한 분들이 올리신 구현 소스들은 조금만 검색해보셔도 많으니까요.

   또한 사용예로 제가 중간에 넣은 소스들은 단지 비교를 위한 예제 이므로 동작하지 않습니다. 참고만 하세요.


기능구현은 위와 같이 간단히 하기로 했습니다. 파일 선택 부분은 기능 구현 완료시 게시판의 UI에 추가 하기 위해서 스타일링

했네요. cos.jar 를 사용하던 Apache Commons Upload를 사용하던 업로드 시에는 BInary 코드로 보내게 되므로

<form>을 통해 전송시 enctype="multipart/form-data", method="post" 를 추가해서 보내게 되어 있습니다.

이렇게 전송된 데이터는 cos.jar를 사용할 경우 HttpServletRequest자체를 넘겨받아 내부에서 업로드를 몽땅 처리 하도록 되어

있습니다.

사용예)

MultipartRequest multi = null; 
  try{
    multi = new MultipartRequest(request, uploadFilePath, uploadFileSizeLimit, "utf-8",

new DefaultFileRenamePolicy());

   }catch(Exception e){}

위와 같이 MultipartRequest 의 객체가 생성되면서 넘겨받은 HttpServletRequest, 업로드할 경로, 업로드할 사이즈 제한

값등을 넘겨주면 바로 파일이 업로드 됩니다. 간단히 기능 구현시 이와 같이 사용법이 너무 심플하고 편하기 때문에 많이

이용되고 있는것 같습니다. 그럼 이번에는 Apache Commons Upload의 구현 방식을 보겠습니다.

사용예)

boolean isMultipart = ServletFileUpload.isMultipartContent(request);

if( isMultipart ) {
    File temporaryDir = new File(uploadFilePath);
    DiskFileItemFactory factory = new DiskFileItemFactory();
    factory.setSizeThreshold(factoryThresholdLimit);
    factory.setRepository(temporaryDir);
    ServletFileUpload upload = new ServletFileUpload(factory);

    List items = null;
    try {
        items = upload.parseRequest(request);
    } catch (FileUploadException fe){
        System.out.println(fe);
    }
    if(items!=null){
        Iterator iter = items.iterator();
        while (iter.hasNext()) {
            FileItem fileItem = (FileItem) iter.next();
            if (fileItem.isFormField()){
                //파일을 제외한 나머지 파라미터 처리
            }else{
                //파일 업로드 처리
                try{
                    File uploadedFile = new File(uploadFilePath, serverFileName);
                    fileItem.write(uploadedFile);
                } catch(Exception e){                      
                    System.out.println(e);
                }
            }
        }
    }
}

분석을 떠나서 cos.jar의 구현 방식에 비해 뭔가 좀더 복잡해졌다는 느낌부터 오실 것 같습니다. 물론 더 간단하게 구현은

가능합니다만 그냥 라인 수로만 봤을때 cos.jar가 더 심플합니다. 이제 구현 방식에 대해 비교를 좀 더 해보죠.

cos.jar를 HttpServletRequest를 통채로 넘겨받아 라이브러리 내부에서 전체적인 처리를 합니다. 보통 파일 업로드를

구현할때 단일 모듈로 파일 업로드 자체만을 구현할때도 있겠지만 보통은 파일정보 외의 다른 정보도 처리 해야 할때가

많이 있습니다. cos.jar사용시 기존의 HttpServletRequest로는 파일 정보외의 정보를 받을수가 없습니다. 위의 구현

예로 보시면 MultipartRequest multi 를 통해 다른 파라미터를 처리 할 수 있습니다.

ex) String name = multi.getParameter("name");

cos.jar의 경우 소스를 함께 제공하기 때문에 소스를 수정하여 여러가지 컨트롤을 개발자가 수정할수도 있겠지만

남이 만들어놓은 소스를 일일히 분석해서 구현하기에는 저같이 귀차니즘에 빠진 개발자분들에게는 무리라고 봅니다.

cos.jar가 업데이트가 자주 이뤄지진 않지만 혹시라도 업데이트가 이뤄지면 해당 내용에 맞춰서 다시 수정해야 하는

불편함도 감수해야 할수도 있겠죠. 제 경우에는 외부 라이브러리는 피치못할 사정이 없는한 수정하는 경우를 피하는

편입니다. 어쨋든 저렇게 MultipartRequest의 getParameter()를 사용해서 나머지 파라미터들을 처리 할수는 있으나

파일의 경우 보통 서버에 실 파일명으로 보존하는 경우는 그렇게 많지 않을것 같습니다. 저의 경우 직접 원본파일의

접근을 막기위해 중복되지 않는 난수 키값을 생성해서 원본파일명으로 저장 시키고 사용자가 웹에서 요청시에는

이 파일을 웹에서 접근가능한 context의 Temporary 디렉토리에서 읽어오도록 구현하는 것이 목적이었습니다.

하지만 cos.jar에서는 이와 같은 처리는 소스를 수정하지 않는한 불가능 하며 파일 업로드가 완료된후에 파일명을

바꿔주는 방식으로 밖에는 처리 할수가 없습니다. 혹시나 해서 찾아봤지만 제가 아는한은 수정밖에는 방법이

없었습니다. 그래서 Apache Commons Upload로 업로드 모듈을 교체 하게 됐습니다.

위의 예와 같이

            FileItem fileItem = (FileItem) iter.next();
            if (fileItem.isFormField()){ //
파일을 제외한 나머지 파라미터 처리 }

파일 정보외의 파라미터들을 처리 할수 있으며 이 정보를 HashMap에 담아 처리 할수도 있겠습니다. 물론 cos.jar 사용

시에도 파일정보외의 파라미터들을 처리하는데에는 불편한 점은 없었습니다.

            }else{
                //파일 업로드 처리
                try{
                    File uploadedFile = new File(uploadFilePath, serverFileName);
                    fileItem.write(uploadedFile);
                } catch(Exception e){                      
                    System.out.println(e);
                }
            }


하지만 위와같이 파일 정보에대한 가공처리 후 실제 디스크에 쓰기 작업을 하는 동작이 이뤄져야 할때

Apache Commons Upload를 사용하는 것이 상당히 맘에 들었습니다. 또한 구현하다보니 파일 다운로드 시에는 굳이

진행상태를 표시해줄수 있는 프로그레스바를 구현하지 않아도 진행상태를 알수있어서(모바일의 경우) 구현할 필요가 없었지만

업로드의 경우 프로그레스바가 필요했습니다. 그냥 스피너 프로그레스바로 진행중이라는 상태만 보여줄수도 있겠지만

누구나 내가 기능을 실행했을때 얼마나 진행됐는지를 알고싶어 하는것이 당연하겠지요. 그래서 결국 구현을 하다보니

cos.jar 사용(기본 라이브러리 상태)으로는 이 역시 불가능 하다는 결론을 내렸습니다.

(혹시 방법이 있으신분 있다면 피드백 주시면 대단히 감사하겠습니다!)

Apache Commons Upload의 경우 ProgressListener를 구현하여 프로그레스 상태를 Listen할수 있으므로 진행상태를

웹상에서 보여주는것이 가능합니다.

사용예)

           ProgressListener progressListener = new ProgressListener(){

                private long megaBytes = -1;
                public void update(long pBytesRead, long pContentLength, int pItems) {
                    long mBytes = pBytesRead / 1000000;
                    if (megaBytes == mBytes) {
                        return;
                    }
                    megaBytes = mBytes;
                    System.out.println("We are currently reading item " + pItems);
                    if (pContentLength == -1) {
                        System.out.println("So far, " + pBytesRead + " bytes have been read.");
                    } else {
                        System.out.println("So far, " + pBytesRead + " of " + pContentLength
                                           + " bytes have been read.");
                    }
                }
            };

            //프로그레스 리스너를 파일업로드시 추가한다.
            upload.setProgressListener(progressListener);

실제 디스크에 파일이 저장되기전 Apache Commons Upload의 경우 임시 파일 업로드를 진행합니다. 구현 하기에 따라

임시 업로드 디렉토리를 따뤄둬서 업로드 진행중 실패시 원본 저장소의 데이터를 해치지 않도록 할수도 있겠습니다.

또한 WEB-INF/web.xml 에 org.apache.commons.fileupload.servlet.FileCleanerCleanup 리스너를 추가하여

이와같은 쓰레기 파일들을 알아서 삭제하도록 할수도 있습니다.

성능상의 차이는 체감상 크게 느껴지지 않아서 잘 모르겠습니다. 두 가지 라이브러리가 사용용도에 따라서 훌륭한 모듈이라

모듈의 문제로 속도가 저하 된다거나 하기 보다는 I/O가 발생하는 Task이므로 하드웨어, 전송환경등이 더 중요하다고

생각됩니다. 글을 작성하다보니 cos.jar를 너무 무시 한듯 하네요. ㅎㅎ 정리하자면


cos.jar : 웹 to 웹으로 전송해야되는 간단한 데이터, 진행상태 표시 구현이 필요없는 파일 업로드 기능 구현시

매우 간단하게 구현 가능한 장점을 가짐, 소스가 제공되므로 개발자 편의에 따라 소스를 수정하여 사용할수도 있음.

(다만 얼핏 보기로는 cos.jar가 오픈소스는 아니라고 봤습니다. 이 부분에 대해서는 잘 판단해보시길..)

Apache commons upload : 일반적인 파일 업로드 기능 외에 파일명이나 저장되야할 위치에 대한 조작등 개발

의도에 따른 소스 구현이 좀 더 유연함. 업로드 진행상태를 알 수 있는 프로그레스바 구현을 ProgressListner를

통해 쉽게 구현 할 수 있음.

심플한 구현에 있어서는 cos.jar를 사용하는것이 감히 최고라고 말씀드릴수 있겠네요. 두개의 라이브러리 모두 장점이 있으니

적당한 라이브러리를 선택하셔서 개발시 도움이 됐으면 합니다. 자료를 찾다보니 구현방법은 많이 있는데 딱히 어떤 모듈을 구

현할때 사용햇으면 좋겠다 하는 내용이 없네요.


신고

정말 너무나 오랜만에 글을 써보는것 같습니다.. 거의 반년만인것 같은데.. 몇달간 자바를 배우느라

안돌아가는 머리 부여잡고 고생중입니다. 다들 재미없어하는 프로그래밍 얘기를 여기다 쓸줄이야

ㅋㅋㅋ 그런다고 잘 알면서 쓰는것도 아니고 잘못된 정보가 있을수도 있으니 보시는분들은 나름

필터링 해서 보시면 되겠습니다. 어짜피 파일도 많고 안의 내용을 전부 글로 쓰기는 시간도 없고

귀찮기도 해서 대략적인 파일구조만 설명하고 예제로 만든 파일을 첨부했습니다.

1. iBatis
  iBatis를 처음접했을때는 파일갯수만 늘어나고 복잡하다고 생각했습니다.. MVC Model 1에서
 주구장창 사용해오던 DAO클래스에 너무나 익숙해져서인지 왜 쓰는지도 사실 잘 몰랐고 새로운
 시도는 항상 어려움이 있는것 같습니다. 근데 계속써보니 DAO클래스 만들어서 PrepareStatement
 줄창써서 쿼리날릴때와는 비교도 안되게 편하더군요. 생각해보면 DAO클래스 제작은 노가다에
 가까웠던듯 싶습니다. 물론 단점은 존재하는것 같습니다. DAO클래스는 자바파일이라 쿼리문
 중간에 조건문을 사용해서 다양한 기능을 만들어볼수 있었는데 iBatis를 사용하게 되면 아직
 더많은 속성들을 몰라서인지 몰라도 PL/SQL의 Function이나 Procedure를 사용해야될일이
 많아질것 같았습니다. 물론 PL/SQL을 잘 모르는 저로서는 손발이 떨릴뿐.. 그래도 단순
 히 리스트 출력,수정,삭제,삽입에는 iBatis를 사용하는것이 편한것 같습니다.

2. Struts2
 Struts1까지는 그래도 MVC Model2를 초기에 공부할때 했던것들과 형식이 유사해서 편했
 습니다만 Struts2를 하면서 라이브러리가 개발자눈에 안보이게 처리하는 부분이 많아서
 적응하는데 좀 시간이 걸렸습니다. 에센셜한 부분에선 깡통같은 머리라 특히 Getter/Setter를
 어떻게 JSP에서 불러올수 있는지는 그냥 신기하기만 하네요. 기존에 JSP에서 Model2를 적용
 해서 만들면서 getAttribute나 setAttribute를 사용하지를 못하니 조금 답답한 면이 없지 않았
 습니다만 계속 만들어보다보니 그저 만들어보는데에는 기존방식과 크게 다르진 않더군요.
 대신 인터셉터가 들어가고 유효성검사(Validation) 체인이 걸리면 규모가 커질경우 보기에
 좀 복잡하겠다는 생각이 들었습니다.

 개발환경 : Eclipse EUROPA,Oracle 10g Database

 전체 UI :

사용자 삽입 이미지

모양은 별로 신경쓸 부분이 아니라 생각해서 대충.. 페이징도 없습니다.

Eclipse Workspace:

사용자 삽입 이미지

전체적인 파일구조입니다. 대략적인 설명부터..

iBATIS
custom.dao패키지
SqlMapConfig.xml : DB와 연결할 타입을 설정합니다. DBCP,JDBC,JNDI를 사용할수가 있는데
ConnectionPool이 어째 잘 적용이 안되서 JDBC로 설정했습니다.

Query.xml : SqlMapConfig.xml에서 읽어들이는 파일입니다. 여기에는 실제 쿼리문과 custom.dto
패키지의 CusomDTO클래스 파일을 읽어들일수 있는 설정을 해주게됩니다. 파일이름은 아무렇게나
만들어도 상관없습니다. 편의를 위해 Query.xml로 했습니다.

CustomDAO.java : struts1이나 MVC Model1에서 사용하던 DAO클래스와 같은 기능을 담당
합니다. 물론 이름은 임의로 줘도 상관없고 여기서 SqlMapConfig.xml을 파싱하게됩니다.
Query.xml은 SqlMapConfig.xml에서 읽어들이기 때문에 같이 파싱이 되겠죠. 이 파일내의
각 기능을 담당하는 메소드에서 Query.xml에 정의한 실제쿼리문에 대한 id를 참조하게 됩니다.

Struts2
struts.xml : Struts2의 각 액션에 대한 정의를 해주는 파일입니다. Struts1에서는 struts-config.xml
로 정의되있습니다. struts.xml에서는 라이브러리내의 struts-default를 상속받아 인터셉터나 기타
기능등을 사용할수가 있으며 이 struts-default는 재정의 가능합니다. 또한 라이브러리 내의
default.properties의 내용을 여기서 struts.properties라는 파일을 만들어서 재정의 가능합니다.
(물론 제대로 써본적은 없습니다..;;;)

대략적인 설명은 위와같습니다.

소스내에 주석을 달아놨는데..개인적인 용도로 썼던주석이라 보기에 좀 거북하겠지만 저같이

잘이해가 안되신 분들을위해..ㅋ  실행은 index.jsp로 하시면 되고 이클립스에서 war파일을

import시키면 되겠습니다.

 
간만에 글을 썼긴한데 설명도 별것도 없고 이상해졌네요. 주석에 모든 설명이 있긴합니다..

불확실한 정보지만.. 상당히 간단하게 만들었기 때문에 Struts2와 iBatis의 연동방식을

이해하는데에는 괜찮다고 생각됩니다.

다음에는 제 개인적 정리 차원에서 iBatis만 다뤄보도록 하겠습니다.

p.s : 깜빡하고 DB테이블을 넣질 않았네요.. DTO클래스에서 정의된 내용을 가지고 테이블 만드시면
되겠습니다. 혹시 필요하신분은 댓글 달아주세요~(물론 오라클이 설치되있다고 가정합니다.-0-
참고로 번호값을 받아올때 시퀀스를 생성하지 않았습니다..)

신고