본문 바로가기
Lecture/Spring

[본격 게시판짜기 Part3.6 Spring MVC] multipartResolver를 활용한 파일업로드 변경하기

by cusmaker 2012. 10. 29.
반응형

2012/06/13 - [Lecture/HTML] - [본격 게시판짜기 Part1.1 - 게시판도 HTML부터] 게시글 리스트

2012/06/13 - [Lecture/HTML] - [본격 게시판짜기 Part1.2 - 게시판도 HTML부터] 글입력폼

2012/06/13 - [Lecture/Javascript-기초] - [본격 게시판짜기 Part1.3 - Dom 맛보기 ] 글입력폼 검사

2012/06/13 - [Lecture/Jsp] - [본격 게시판짜기 Part1.4 - HTML-> JSP] form 파라미터 받기

2012/06/26 - [Lecture/SQL / Oracle] - [본격 게시판짜기 Part1.5 JSP > Oracle] 게시판 DB 테이블 생성

2012/06/26 - [Lecture/Jsp] - [본격 게시판짜기 Part1.6 Oracle > JSP] Database 접속 및 Select

2012/07/05 - [Lecture/Jsp] - [본격 게시판짜기 Part1.7 JSP, SQLDeveloper] 게시글 입력 및 리스트조회기능

2012/07/08 - [Lecture/Jsp] - [본격 게시판짜기 Part1.8 JSP 게시글 조회] 게시글 조회기능 및 게시글 삭제

2012/07/10 - [Lecture/Jsp] - [본격 게시판짜기 Part2.1 Model2 MVC패턴] 뷰(View) 코드 분리

2012/07/10 - [Lecture/Jsp] - [본격 게시판짜기 Part2.2 Model2 MVC패턴] Entity Beans의 사용

2012/07/12 - [Lecture/Jsp] - [본격 게시판짜기 Part2.3 Model2 MVC패턴] Controller구성

2012/07/13 - [Lecture/Jsp] - [본격 게시판짜기 Part2.4 Model2 MVC패턴] Model 구성

2012/07/16 - [Lecture/Mybatis] - [본격 게시판짜기 Part2.5 Model2 MVC패턴] Model 구성2 - i-batis의 사용

2012/07/17 - [Lecture/Mybatis] - [본격 게시판짜기 Part2.6 Model2 MVC패턴] Model 구성2 - i-batis의 사용2

2012/07/21 - [Lecture/Mybatis] - [본격 게시판짜기 Part2.7 Model2 MVC패턴] i-batis의 queryForList 활용 Paging기능구현

2012/07/22 - [Lecture/Jsp] - [본격 게시판짜기 Part2.8 Model2 MVC패턴] count기능추가

2012/07/28 - [Lecture/Jsp] - [본격 게시판짜기 Part2.9 Model2 MVC패턴] 파일업로드 기능추가

2012/07/28 - [Lecture/Jsp] - [본격 게시판짜기 Part2.10 Model2 MVC패턴] 파일다운로드/삭제 기능추가

2012/07/31 - [Lecture/Jsp] - [본격 게시판짜기 Part3.1 Spring MVC] Ajax란?

2012/08/28 - [Lecture/Jsp] - [본격 게시판짜기 Part3.2 Spring MVC] lightbox 적용하기

2012/09/22 - [Lecture/Spring] - [본격 게시판짜기 Part3.3 Spring MVC] Spring MVC 개요 및 설정하기

2012/09/22 - [Lecture/Spring] - [본격 게시판짜기 Part3.4 Spring MVC] Spring MVC 개요 및 설정하기2

2012/10/13 - [Lecture/Spring] - [본격 게시판짜기 Part3.5 Spring MVC] SqlMapClientDaoSupport활용, Model 변경하기


안녕하세요 cocy입니다.

이번시간에는 기존에 모델2에서 구현한 파일업로드(글쓰기페이지)를 

스프링에서 지원하는 MultipartFile 파일 형식을 가지고 수정해 보도록 하겠습니다.



모든 액션들의 스프링형식으로의 구현이 선행되어있어야하는데요,

리스트페이지먼저 차근차근 진행하도록 하겠습니다.


보실부분은 list.jsp페이지 입니다. (WebContent > WEB-INF > board > list.jsp)

이전까지 잘 따라오셨다면 리스트를 뿌려주는것까진 정상적으로 뿌려지지만,

게시글을 더보는 기능의 경우 아직 고쳐지지 않았습니다.

스크립트부분에 loadNextPage() 함수를 보시면 요청 url이 이전과 같이 .do로 끝나기때문인데요

이것을 rest-ful 방식으로 고치고 해당 액션을 스프링 컨트롤러에 등록시켜보겠습니다.

우선 url에서 .do를 지워주고 요청 url 앞에 프로젝트 네임을 적어 절대경로로 표시합니다.

function loadNextPage(){

var page = $('#page').val();

page = parseInt(page);

page += 10;

$.ajax({

  type:'post',

  url:'ajaxList.do',

  data: ({page:page}),

  success:function(data){

  $('table').append(data);

  $('#page').val(page);

  }

});

}

function loadNextPage(){

var page = $('#page').val();

page = parseInt(page);

page += 10;

$.ajax({

  type:'post',

  url:'/board/ajaxList',

  data: ({page:page}),

  success:function(data){

  $('table').append(data);

  $('#page').val(page);

  }

});

}

그 다음은 해당 함수를 스프링 컨트롤러에 등록하기위해 

BoardController.java (src > spring.board.controller) 에서

구현할 메소드 타입을 정의하고 @RequestParam을 통해 page파라미터를 지정하여 정형을 선언합니다.

BoardController.java

@RequestMapping("/ajaxList")

public abstract ModelAndView ajaxListAction(@RequestParam Integer page) throws Exception;


그다음은 구현 클래스인 BoardService.java (src > spring.board.service) 에 

기존에 작성했던 AjaxListAction.java  (src > com.board.action) 의 내용을 수정하여 추가합니다.

BoardService.java

@Override

public ModelAndView ajaxListAction(Integer page) throws Exception {

if(page == null){ // 넘어온 파라미터가 없으면

page = 0; //0으로 초기화

}  

ArrayList<Board> articleList =boardDao.getArticleList(page);

return new ModelAndView("ajaxList", "articleList", articleList);

}

수정된 내용은 주석으로 설명하였으며 소스가 좀더 간결해 진것을 확인 하실 수 있습니다.

여기서 리턴하는 ModelAndView객체의 첫번째 인자는 이전에 스프링 구성에서 

타일즈를 활용한 뷰 구성을 위해 template-board.xml (spring.board.view) 에서 등록한 

<definition name="ajaxList" template="/WEB-INF/board/ajaxList.jsp" />

이 구문의 name에 해당하는 값입니다.

잘 따라하셨다면 정상적으로 구동되는것을 보실 수 있습니다. 


그럼 이제 본론으로 돌아와서 글쓰기 폼과 글등록 액션도 스프링형식으로 바꿔보겠습니다.

list.jsp페이지에서 하단부에 작성한 글쓰기의 경우 

별도의 데이터베이스 커넥션이 필요없어, jsp페이지를 링크시켰는데요,

<br/><a href="write.jsp">글쓰기</a>  

이부분을 이전에 작성한 lightbox를 통해 불러오도록 하겠습니다.

a 태그의 href속성을 #으로 주시고 onclick속성에 자바스크립트 함수를 걸어 비동기요청 후 lightbox안에 표시하도록 하죠.

<br/><a href="#" onclick="loadWriteForm()">글쓰기</a>

그럼 스크립트 태그안에 함수도 작성해 줍니다.

function loadWriteForm(){

$.ajax({

  type:'post',

  url:'/board/write',

  success:function(data){

  $('.mw_layer').addClass('open');

  $('#layer').html(data);

  }

});

}

단순 글쓰기 폼 요청이기때문에 파라미터는 없구요,

이전에 컨텐츠를 lightbox를 통해 확인하는 openContent() 자바스크립트 함수를 참고하여 구성하였습니다.

그럼 마찬가지로 스프링 컨트롤러에 등록시키기위해

BoardController.java (src > spring.board.controller) 에서 해당 url과 처리할 메소드의 정형을 선언합니다.

@RequestMapping("/write")

public abstract ModelAndView writeAction() throws Exception;

그다음은 구현 클래스인 BoardService.java (src > spring.board.service) 에 해당 메소드를 구현합니다.

@Override

public ModelAndView writeAction() throws Exception {

return new ModelAndView("write");

}

메소드의 내용은 단순히 페이지의 반환이기때문에 뷰이름만 ModelAndView 객체에 첫번째 인자값으로 생성하여 넘겨줍니다.


수정이 끝나셨다면 해당 기능도 정상적으로 나오는 것을 확인하실 수 있습니다.


하지만 write.jsp페이지에서는 아직 insert.do 요청을 하고있기때문에 글쓰기는 동작하지 않습니다.

write.jsp페이지를 열고 form태그에서 action에 insert.do요청을 /board/insert 로 바꿔줍니다.


마찬가지로 BoardController.java (src > spring.board.controller) 에서 해당 url과 처리할 메소드의 정형을 선언합니다.

@RequestMapping( "/insert")

public abstract ModelAndView insertAction(@ModelAttribute Board article) throws Exception;

// 폼에서 넘어온 값 자동으로 빈에 셋팅


※ 여기서 잠시 스프링 설정에서 사용되는 부분은 dispatcher-servlet.xml(spring.board.config) 의 아래 부분입니다. 

내용이 추가 안되신분들은 추가하셔야합니다.

<!-- 파일 업로드를 위한 멀티파트 리졸버 -->

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

   <property name="maxUploadSize">

      <value>10000000</value>

   </property>

</bean>


그런데 이때, 기존에 구현했던 메소드와는 다른점이 발생합니다.

스프링에서는 바로 MultipartFile이라는 객체형을 지원하여 해당 객체형으로 선언된 필드의 경우 파일을 직접 담아오게 됩니다.

그렇다고 filename필드에 객체형을 MultipartFile로 바꿔버리게되면 

데이터베이스에 insert시 타입에러가 나게되므로

파일업로드 처리를 하기위해 파일을 받을 수 있는 MultipartFile형 필드를 하나 더 선언하도록 하겠습니다.

추가하실 부분은 Board.java(com.board.beans)입니다.

private MultipartFile file;

public MultipartFile getFile() {

return file;

}

public void setFile(MultipartFile file) {

this.file = file;

}

코드 제네레이트 기능을 사용하여 getter와 setter을 만들어 주었습니다.

그럼 write.jsp의 form에도 약간의 수정이 필요한데요,

<input type="file" name="filename">

<input type="file" name="file">로 고쳐주시기바랍니다.


그다음은 구현 클래스인 BoardService.java (src > spring.board.service) 에 해당 메소드를 구현합니다.

@Override

public ModelAndView insertAction(@ModelAttribute Board article, HttpServletRequest request)

throws Exception {

MultipartFile file = article.getFile(); // 파일을 받고

if(!file.isEmpty()){ // 파일이 존재하면 

String filename = file.getOriginalFilename(); //파일에서 업로드 파일 이름을 받고

File tempfile =new File(request.getRealPath("/upload"), file.getOriginalFilename()); //파일 생성후 

if(tempfile.exists() && tempfile.isFile()){ // 이미 존재하는 파일일경우 현재시간을 가져와서 리네임

filename =System.currentTimeMillis()  +"_"+ file.getOriginalFilename() ;

tempfile = new File(request.getRealPath("/upload"),filename); //리네임된 파일이름으로 재생성

}

file.transferTo(tempfile); // 업로드 디렉토리로 파일 이동

article.setFilename(filename); // 게시글에 업로드된 파일이름 등록

}

article.setRegip(request.getRemoteAddr());

article.setCount(0);

boardDao.insertArticle(article);

return new ModelAndView("redirect:list/0"); // redirect: 라는 접두어로 바로 리다이렉트가 가능합니다.

}

한 액션이 추가될때는 계속해서 이러한 작업의 반복이기때문에 익숙해 지시길바랍니다.


※ 여기서 잠시 한글인코딩이 깨지시는 분들을 위해

서버측과 프로젝트 설정이 동일해야 한글 인코딩이 제대로 나옵니다.

web.xml부분에서 인코딩 설정부분을 EUC-KR로 맞춰주시고

<!-- 인코딩 설정 --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>EUC-KR</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

다음 그림과 같이 서버 목록에서 프로젝트가 실행되는 서버의 server.xml파일에서 

해당위치에 URIEncoding="euc-kr"를 추가해주세요.



작업이 끝나셨으면 파일업로드가 정상적으로 작동하는 것을 확인 하실 수 있습니다.


여기서 끝이 아니라 기왕 구현하는거 컨텐츠를 불러오는 액션도 스프링스타일로 바꿔보도록 하겠습니다.

먼저 list.jsp에서 count.do를 요청하는것을 /board/count로 변경하고

function openContent(idx){

$('.mw_layer').addClass('open');

$.ajax({

  type:'post',

  url:'/board/count',

  data: ({idx:idx}),

  success:function(data){

$('#layer').html(data);

  }

});

}

BoardController.java에 count를 처리할 메소드 정형을 선언합니다. 하는김에 동시에 content도 추가하죠

@RequestMapping("/count")

public abstract ModelAndView countAction(@RequestParam Integer idx, HttpServletRequest request) throws Exception;

@RequestMapping("/content")

public abstract ModelAndView contentAction(@RequestParam Integer idx) throws Exception;

이후 BoardService.java에서 해당 메소드들을 구현합니다. 기존에 만들었던 com.board.action의 클래스들을 참고합니다.

@Override

public ModelAndView countAction(Integer idx, HttpServletRequest request) throws Exception {

Board article =boardDao.getArticle(idx); // 게시글 전체를 가져옵니다.

String regip = request.getRemoteAddr(); // 현재 조회를 요청한 사용자의 ip를 받고


if(!regip.equals(article.getRegip())){ // 게시글의 ip와 동일하지 않으면

int count = article.getCount(); // 게시글의 ip를 받아서 

article.setCount(++count); // +1 해주고 게시글빈에 셋팅합니다.

boardDao.setArticleCount(article); // 이후 이 빈을 파라미터로 업데이트합니다.

}

                return new ModelAndView("redirect:content","idx", idx);

}


@Override

public ModelAndView contentAction(Integer idx) throws Exception {

Board article = boardDao.getArticle(idx);

return new ModelAndView("content","article", article);

}

수고하셨습니다.


다음시간에는 아직 돌아가지 않는 다운로드와 게시글 삭제를 마저 전환해 보도록 하겠습니다.


다음장으로 (json요청과 빈 네임 리졸버) >>>  http://cusmaker.tistory.com/136