JAVA 웹개발 과정 국비 83일차 정리

83일차

※ 배우는 과정이므로 정확하지 않을 수 있습니다.
잘못된 점은 알려주시면 수정하도록 하겠습니다!


1. Spring Framework 이어서..

1) 파일 업로드

기존 MVC2 모델에서 파일 업로드를 구현할 때는 COS라는 라이브러리를 사용했다.
그러나 스프링에서는 apach fileupload 라이브러리가 더 편하기 때문에 이 라이브러리를 사용한다.

① 라이브러리 의존성 추가

Maven Repository에서 apache fileupload를 검색하면 Apache Commons FileUpload 라이브러리가 나온다.
dependency 코드 복사 붙여넣기 해주기

② 인스턴스 생성

multipart data가 들어오는 것을 해결해 주는 객체인 multipartResolver를 생성한다.
웹에 관련된 것이기 때문에 servlet-context.xml 에 추가해준다.
maxUploadSize는 파일의 최대사이즈를 제한한다. 1024102410 의 계산 수치인 10485760을 적어주면 10MB까지의 파일만 이동 가능하다.
스프링은 자동으로 apache commons fileupload를 사용하기 때문에 bean 생성 코드만 적어주면 @Autowired 같이 직접 사용한다고 선언을 해주지 않아도 알아서 가져다가 쓴다.

<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
	<beans:property name="maxUploadSize" value="10485760"/>
</beans:bean>

③ jsp 코드

<form action="/file/upload" method="post" enctype="multipart/form-data">
	<input type=text name="title"><br>
	<input type=text name="contents"><br>
	<input type=file name="file" multiple><br>
	<button>작성완료</button>
</form>

위와 같이 title, contents, file 의 name을 가진 값들을 jsp에서 form으로 넘겨준다.
enctype 속성을 주는 것은 필수이다.
그리고 여러개의 파일을 받고 싶으면 input type=filemultiple 속성을 준다.

④ 컨트롤러 코드

컨트롤러에서 값을 받는 건 name을 그대로 가져오는 것까지는 같다.
그러나 file은 자료형을 MultipartFile로 받는다.
multiple속성으로 여러 파일들을 submit 한다면 MultipartFile[] 배열으로 받아준다.
대부분 MVC2 패턴에서 배웠던 개념을 그대로 사용하지만, 달라진 것들 위주로 아래에 정리한다.

@Controller
@RequestMapping("/file/")
public class FileController {

	@Autowired
	private HttpSession session;

	@RequestMapping("upload")
	public String upload(String title, String contents, MultipartFile[] file) throws Exception {

		// 업로드 된 파일 중 첫번째 파일이 비어있지 않다면
		if(!file[0].isEmpty()) {
			for(MultipartFile mf : file) {
				// 더 안전하게 하고싶다면
				if(!mf.isEmpty()) {
					String realPath = session.getServletContext().getRealPath("upload");
					File realPathFile = new File(realPath);
					if(!realPathFile.exists()) {realPathFile.mkdir();}

					String oriName = mf.getOriginalFilename();
					String sysName = UUID.randomUUID()+"_"+oriName;

					// 서버에 업로드되어 메모리에 적재된 파일의 내용을 어디에 저장할지 결정하는 부분
					mf.transferTo(new File(realPath+"/"+sysName));
				}
			}
		}
		return "redirect:/";
	}
}
ⓐ UUID 클래스

COS 라이브러리는 중복된 이름을 가질 때 자동으로 구분해주는 기능이 내장되어 있었지만, 아파치 파일 업로드 라이브러리는 그런 기능이 없다.
따라서 직접 설정을 해줘야 한다.
UUID라는 클래스의 randomUUID 메소드가 겹치지 않는 문자열을 임의로 만들어주기 때문에 oriName과 연결해주면 중복이 없는 파일명을 생성할 수 있다.

String sysName = UUID.randomUUID()+"_"+oriName;
ⓑ 업로드 경로 설정

transferTo 메소드를 사용하여 어떤 경로에 파일을 복사시킬 것인지 결정한다.
예외는 throws Exception 으로 전가시킨다.

mf.transferTo(new File(realPath+"/"+sysName));
ⓒ 업로드 경로 찾아가기

서버에 우클릭해서 Browse Deployment Location…를 누르면 realPath(프로젝트가 복사되는 곳)으로 이동할 수 있다.
클릭해서 upload 폴더에 들어가면 업로드된 파일을 쉽게 찾을 수 있다.
스프링에서 제공해주는 기능!

ⓓ 안전한 코드 만들기

파일 선택을 아무것도 하지 않고 submit을 하게되면 내용이 빈 파일이 업로드 된다.
이런 상황을 차단하려면 첫째, 업로드 된 파일 중 첫번째 파일이 비어있지 않다면 업로드를 실행하는 if문을 작성해주면 된다.
만약 “첫번째 파일”이라는 것이 조금 불안정하다고 생각된다면, 두번째 방법으로 forEach문 안에 if문을 만들어서 파일이 비어있지 않을 때 업로드가 진행되게끔 해줄 수 있다.

2) keyProperty

게시판에 파일 업로드 기능을 구현할 때, 파일 테이블에 parentSeq를 넣어주는 것이 까다로운 부분이다.
이전에는 nextval 번호를 따로 뽑아내는 메소드를 만들어서 file 테이블과 board 테이블에 각각 insert를 해주었는데, mybatis의 keyProperty 속성을 이용하면 한번에 손쉽게 해결할 수 있다.

<insert id="insert">
	INSERT INTO board VALUES(board_seq.NEXTVAL, #{writer}, #{title}, #{contents}, DEFAULT, DEFAULT)  <!-- 1, 2 -->
	<selectKey resultType="int" order="AFTER" keyProperty="seq">
		SELECT board_seq.currval FROM dual  <!-- 3, 4, 5 -->
	</selectKey>
</insert>
public int insert(BoardDTO dto) throws Exception {
	int result = mybatis.insert("Board.insert",dto);  // 6
	return dto.getSeq();  // 7
}
  1. INSERT 쿼리가 실행된다.
  2. NEXTVAL 이 생성된다.
  3. order=”AFTER” 속성의 영향으로 다음 SELECT 쿼리가 실행된다.
  4. CURRVAL의 값을 가져온다(2번의 값과 동일)
  5. 4번의 값을 resultType=”int” 로 int형으로 keyProperty=”seq”로 seq에 저장한다. 여기서의 seq는 BoardDTO의 멤버필드 seq를 의미한다. (INSERT 위해 BoardDTO dto를 인자값으로 넘긴 상태)
  6. 리턴값은 그대로 결과값 int이다. (실패 값은 존재하지 않음. 리턴값이 돌아오거나 예외가 생기거나 둘 중 하나)
  7. dto의 seq를 리턴한다.(5번으로 값이 생김)
  8. 7번의 값을 FilesDTO의 parentSeq 값으로 넣어준다.
int parentSeq = dao.insert(dto);  // 7
fdao.insert(new FilesDTO(0, oriName, sysName, parentSeq));  // 8

3) 파일 다운로드

파일 다운로드도 MVC2 때와 크게 다르지 않다.
우선 jsp에서 sysName과 oriName을 서버로 넘겨준다.

<c:if test="${!empty fileList }">
<tr>
	<td>
	<c:forEach var="i" items="${fileList}" >
	<a href="/file/download?oriName=${i.oriName}&sysName=${i.sysName}">${i.oriName }</a><br>
	</c:forEach>
</tr>
</c:if>
  1. 파일 위치 경로를 획득
  2. sysName과 결합하여 대상 파일 객체를 생성
  3. 대상 파일에 대한 InputStream, OutputStream 개방
  4. 대상 파일을 적재할 메모리 공간 확보
  5. 대상 파일 로딩
  6. 한글 인코딩 처리
  7. response 비우기
  8. 지금 보내는 응답은 랜더링 목적이 아니라 파일 내용이라는 것을 알려주기
  9. 내보낼 내용 저장
  10. 차 보내기~ 슝슝
@Controller
@RequestMapping("/file")
public class FileController {

	@Autowired
	private HttpSession session;

	@RequestMapping("download")
	public void download(HttpServletResponse response, String oriName, String sysName) throws Exception {

		String realPath = session.getServletContext().getRealPath("upload");  // 1
		File target = new File(realPath+"/"+sysName);  // 2

		try(
				DataInputStream dis = new DataInputStream(new FileInputStream(target));  // 3
				DataOutputStream dos = new DataOutputStream(response.getOutputStream()); // 3
				){
			byte[] fileContents = new byte[(int)target.length()];  // 4
			dis.readFully(fileContents);  // 5
			
			oriName = new String(oriName.getBytes(), "ISO-8859-1");  // 6
			response.reset();  // 7
			response.setHeader("Content-Disposition", "attachment;filename="+ oriName);  // 8

			dos.write(fileContents);  // 9
			dos.flush();  // 10
		}
	}
}

4) 그 외..

  • Dnd Fileupload api : 드래그 앤 드랍으로 파일 업로드를 진행 할 수 있게 해주는 api
  • resultType=boolean : mybatis를 사용할 때 select 태그에 resultType을 boolean으로 지정해주면 0일때는 false, 1일때는 true를 자동적으로 리턴해준다. (count(*)을 사용할 때)

댓글남기기