File Upload
파일 업로드를 위한 form은 반드시 method가 post 이어야 하고 enctype을 multipart/form-data로 설정해야 합니다.
spring은 Multipart 기능을 지원하기 때문에 별다른 설정없이도 multipart form의 데이터를 받을 수 있습니다.
spring에서는 filed을 server에 저장할 때 commons-fileupload 의존성이 필요하고 CommonsMultipartReslover Bean이 필요합니다.
Spring에서는 3가지 방법으로 file Upload를 처리할 수 있습니다.
HttpServletRequest 대신에 MultipartHttpServletRequest를 이용해서 getFiles 메소드로 처리
파라미터의 자료형을 MultipartFile로 해서 처리하는 방법이 있습니다.
(@RequestParam, Command 객체 이용도 가능)
MultipartFile 에서 업로드하는 방법
- getBytes() 메소드로 파일이 내용을 바이트 배열로 리턴받아서 직접 전송하는 방법
transferTo(File dest) 메소드에게 File 객체를 대입해서 업로드
파일을 업로드 할 때 주의할 점은 동일한 파일명은 ?
- UUID 클래스를 이용해서 중복되지 않는 이름을 만들던가 업로드하는 유저의 id를 붙여서 중요한 파일명을 만들지 않도록 하는 방법이 있습니다.
이모티콘 -> 유저별로 디렉토리를 만들어서 서비스 블로그, 메모 -> 회원가입 테이블, 디렉토리 만들어서 별도의 디렉토리에 저장함. 본인테이블에서만 로드하는 형태
삽입의 처리 과정
- 삽입링크 클릭 -> 삽입할 수 있는 페이지로 포워딩 -> 입력을 하고 전송을 누르면 유효성 검사를 해서 유효성 검사를 통과하면 서버에게 전송 -> 서버는 처리를 하고 결과 페이지로 리다이렉트를 합니다.
서버가 처리를 하고 포워딩을 하게 되면 에러가 발생하던지 동일한 데이터가 중복해서 삽입되게 됩니다.
새로고침을 했을 때 이전에 수행한 작업이 다시 발생해도 되면 포워딩을 해도 되고 다시 발생하면 안되는 경우는 반드시 리다이렉트를 해야 합니다.
1.파일업로드를 위한 데이터 삽입을 위한 링크를 home.jsp 파일에 추가
<a href="insert">데이터삽입 </a><br/>
2.HomeController에서 insert 요청을 처리하는 메소드를 추가
@RequestMapping(value="/insert", method=RequestMethod.GET)
public String insert(HttpServletRequest request, Model model ) {
//뷰이름을 리턴
return "insert";
}
3.views 디렉토리에 insert.jsp 파일을 만들고 데이터 삽입 폼을 작성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>데이터 삽입</title>
</head>
<body>
<div align='center'>
<h3>데이터 삽입</h3>
<form method="post" enctype="multipart/form-data" id="myform">
<table border="1">
<tr>
<td rowspan="5" align="center">
<p></p> <img id="img" width="100" height="100" border="1" />
<br />
<input type="file" id="pictureurl" name="pictureurl" accept=".jpg, .jpeg, .gif, .png" />
<br />
</td>
</tr>
<tr>
<td align="right">코드</td>
<td><input type="text" name="itemid" id="itemid" required="required" />
<div id="iddiv"></div>
</td>
</tr>
<tr>
<td align="right">이름</td>
<td><input type="text" name="itemname" id="itemname" required="required" />
</td>
</tr>
<tr>
<td align="right">가격 </td>
<td><input type="text" name="price" id="price" required="required" />
</td>
</tr>
<tr>
<td align="right">효과 </td>
<td><input type="text" name="description" id="description" required="required" />
</td>
</tr>
<tr>
<td colspan="3" align="center">
<input type="submit" value="전송"/>
<input type="button" value="메인" id="mainbtn" />
</td>
</tr>
</table>
</form>
</div>
</body>
</html>
4.insert.jsp 파일에 이미지 미리보기 기능 구현
- javascript에서 파일을 불러오는 동작은 비동기(콜백)적으로 처리됩니다.
<script>
//이미지 미리보기
var pictureurl = document.getElementById("pictureurl");
var img = document.getElementById("img");
//이미지 선택이 변경되면
pictureurl.addEventListener("change", function(){
//업로드한 이미지 파일의 내용을 img에 출력
//파일 읽기 객체 생성
var reader = new FileReader();
if(pictureurl.files && pictureurl.files[0]){
//파일 읽기
reader.readAsDataURL(pictureurl.files[0]);
//파일의 내용을 전부 읽으면 출력(비동기)
reader.addEventListener("load", function(e){
img.src = e.target.result;
})
}
});
</script>
5.itemid 중복 검사
1) itemid를 받아서 중복 여부를 판별해서 리턴할 서버의 요청 처리 메소드를 생성
- itemid를 파라미터로 받아서 서버에가서 데이터가 존재하는지 확인하면 됩니다.
데이터가 하나인걸 찾아올때면 있으면 데이터가 오지만 없으면 null이 리턴됨
Service 클래스의
- JSONController 클래스에 itemid 중복 검사를 위한 메소드를 생성
// itemid를 파라미터로 받아서 중복체크를 해서 리턴하는 메소드
@RequestMapping(value = "/itemidcheck", method = RequestMethod.GET)
public Map<String, Object> itemidCheck(HttpServletRequest request, @RequestParam("itemid") int itemid) {
// 서비스 메소드 호출
Item item = itemService.getItem(request, itemid);
// 리턴할 Map을 생성
Map<String, Object> map = new HashMap<String, Object>();
if (item == null) {
map.put("result", "ture");
} else {
map.put("result", "false");
}
return map;
}
}
2) insert.jsp 에서 중복 체크 하는 코드를 작성
// itemid 중복체크를 위한
var itemid = document.getElementById('itemid');
var iddiv = document.getElementById('iddiv');
// 중복체크를 통과했는지 여부를 저장할 변수
var idcheck = false;
// itemid 란에서 포커스가 떠나면 중복 체크
itemid.addEventListener('focusout', function(e) {
//ajax 객체 만들기
request = new XMLHttpRequest();
//요청 생성
request.open('GET', 'itemidcheck?itemid=' + itemid.value);
//요청 전송
request.send('');
//요청에 대한 처리를 위한 콜백 메소드 등록
request.onreadystatechange = function() {
// 정상응답이 오면
if (request.readyState == 4 && request.status >= 200
&& request.status < 300) {
//넘어온 데이터 파싱
var data = JSON.parse(request.responseText);
if (data.result == 'ture') {
iddiv.innerHTML = '멋진코드네요';
iddiv.style.color = 'green';
idcheck.value=true;
} else {
iddiv.innerHTML = '이미사용중인코드입니다.';
iddiv.style.color = 'red';
idcheck.value=false;
}
}
}
})
Validation Check(유효성 검사)
- 데이터를 삽입하거나 삭제 또는 갱신 및 조회를 할 때 올바른 데이터인지 확인하는 작업
- 웹 애플리케이션에서는 클라이언트(웹 브라우저 - 자바스크립트) 와 서버의 service 클래스 그리고 데이터베이스에서 할 수 있다
- 클라이언트 측에서만 하는 경우에는 데이터를 전송할 때 헤더를 변경해서 자바스크립트에서 는 올바른 데이터였지만 서버에 도달할 때는 다른 데이터일 수 있습니다.
- ip와 userAgent 값을 변경해서 전송하는 경우가 많습니다.
- 서버에서 header 값들을 읽어서 확인해야 합니다.
데이터베이스에서 제약조건을 이용해서 다시 한번 검사를 하는 것이 안전합니다.
보안이 필요한 유효성 검사는 클라이언트에서는 하면 안됩니다.
게시판 글번호(기본키) - 일련번호 번호만 가지고 만들지 말것
기본키 - 자동으로 인덱싱 설정 , 검색과 분류를 할 가능성이 있다.
ID, emal : 입력 - 중복체크
상품번호 : 자동생성 (분류코드 + 일련번호로 생성)
상품번호 : 그룹화 - 종류별, 추천
기본키 중복 검사
기본키를 직접 입력받는 경우에는 중복검사를 해주는 것이 좋다.
- 이벤트는 포커스가 잃어버릴 때를 많이 이용하고 이 때 ajax나 websocket을 이용해서 서버에게 기본키 입력 값을 전송하고 서버는 넘어온 기본키 입력 값을 가지고 데이터베이스에서 조회해서 그 결과를 json이나 xml 형태로 클라이언트에게 알려주며 클라이언트는 그 값을 가지고 중복 여부를 판단해서 처리를 한다.
기본키를 일련번호 형태로 자동 생성하는 경우는 auto_increment 나 sequencs를 이용하기도 하고 현재 입력된 번호 중 가장 큰 번호를 찾고 여기에 +1을 해서 생성하는 방법도 있다
- 이 방법은 일반적으로 비추천하고 되도록이면 숫자나 문자의 조합을 이용해서 분류의 기능을 갖도록 만드는 것을 권장한다. 대표적으로 주민번호나 학번.
폼의 데이터 전송 시 유효성 검사
클라이언트에서의 유효성 검사
- 클라이언트에서 유효성 검사를 하게 되면 서버로 전송하지 않고 수행을 하기 때문에 속도나 트래픽 면에서는 유리하지만 보안이 되지 않기 때문에 클라이언트가 유효성 검사 로직을 파악 할수 있다.
폼의 경우는 submit 이벤트에서 유효성 검사를 하고 유효성 검사를 통과하지 못하면 서버로 전송하지 못하도록 이벤트객체의
preventDefault()
를 호출하면 된다.- 가장 많이 수행하는 유효성검사는 필수 입력, 중복검사 통과여부, 패턴 일치, 2개의 값일치 등이 있다.
실습 - insert.jsp 에 유효성 검사를 위한 스크립트 코드를 추가
// 폼의 데이터를 전송할 때 발생하는 이벤트 처리
document.getElementById("myform").addEventListener("submit", function(e) {
//중복체크 통과 여부 확인
if(idcheck.value == false) {
iddiv.innerHTML = "아이디 중복검사를 하세요";
iddiv.style.color = "red";
itemid.focus();
//폼의 데이터를 전송하지 않도록 하기
e.preventDefault();
return;
}
var price = document.getElementById("price")
//price 입력란의 숫자만 입력되었는지 체크
// +나 -기호를 앞에 붙일 수 있는지
// ,의 경우는 어떻게 할것인지
// 하나씩 가져와서 비교
// 길이는 lenghth, 갯수는 count
for(var i=0; i<price.value.length; i=i+1) {
var ch = price.value.charAt(i);
//유효성 검사를 통과하지 못할때
if(i==0){
if(!(ch== '+'|| ch == '-' || (ch >= '0' && ch <= '9' ))){
price.focus();
alert("가격의 첫번째 자리는 숫자나 +, - 기호여야 한다. ")
//폼의 데이터를 전송하지않도록 하기
e.preventDefault();
return ;
}
}else {
if(!(ch >= '0' && ch <= '9')){
price.focus();
alert("가격은 숫자로만 입력하세요 ")
//폼의 데이터를 전송하지 않도록 하기
e.preventDefault();
return ;
}
}
}
});
- type을
number
로 바뀌면 스마트폰에서 입력할때 숫자키만 입력하게 나옴 - type을
email
로 바꾸면 '@' 이 안들어 가면 입력 불가<tr> <td align="right">가격</td> <td><input type="number" name="price" id="price" required="required" /></td> </tr> <tr> <td align="right">효과</td> <td><input type="email" name="description" id="description" required="required" /></td> </tr>
spring 에서의 file upload 처리
- MultipartFile 타입으로 처리
1. 준비사항
1) commons-fileupload 라이브러리의 의존성 설정
2) CommonsMultipartResolver 클래스의 bean을 생성
2.MultipartFile
- 파라미터를 직접 이 타입으로 받아도 되고 HttpServletRequest 대신에 MultipartHttpServletRequest를 이용해서 요청을 받고 getFile(String parameterName)을 이용해서 가져올 수 있습니다.
3. 실습
1) 파일 업로드 처리를 위한 라이브러리의 의존성을 pom.xml 파일에 추가
<!-- 파일 업로드 처리를 위한 의존성 라이브러리 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
2) 파일 업로드를 처리해 줄 수 있는 CommonsMultipartFile 클래스의 bean을 추가
- servlet-context.xml 파일에 추가
<!-- 파일 업로드를 처리할 Bean(spring이 생성하고 관리하는 instance)을 생성 -->
<beans:bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver"
id="multipartResolver">
</beans:bean>
3) DAO 클래스에 데이터를 삽입하기 위한 메소드를 생성하고 추가
//데이터 1개를 삽입하는 메소드 int , void
public int insertItem(Item item) {
sessionFactory.getCurrentSession().save(item);
return 1;
}
// 지울때는 save(item); -> delete(item);으로 수정
4) ItemService 인터페이스에 데이터를 삽입하기 위한 메소드를 선언
//데이터 삽입(파일 업로드) 처리를 위한 메소드
public int insertItem(MultipartHttpServletRequest request);
5) ItemServiceImpl 클래스에 데이터를 삽입하기 위한 메소드를 구현
어노테이션이 붙으면 내가 모르는 코드가 있는 것이다.
@Override
@Transactional
public int insertItem(MultipartHttpServletRequest request) {
// 파라미터 읽기
String itemid = request.getParameter("itemid");
String itemname = request.getParameter("itemname");
String price = request.getParameter("price");
String description = request.getParameter("description");
// 파라미터는 문자로만 전송 가능
// 데이터베이스에서의 타입에 맞춰서 변경이 필요하다.
// DAO 객체의 파라미터 만들기
Item item = new Item();
item.setItemid(Integer.parseInt(itemid));
item.setItemname(itemname);
item.setPrice(Integer.parseInt(price));
item.setDescription(description);
//파일 읽기
MultipartFile mf = request.getFile("pictureurl");
// 업로드할 파일이 있는 경우에만(파일 유/무)
if(mf.isEmpty() == false) {
// 원본 파일이름 가져오기
String originName = request.getFile("pictureurl").getOriginalFilename();
// 원본 파일은 여러개의 파일을 업로드 하다보면 중복될 수 있기 때문에
// 파일 이름을 만들 때는 동일한 디렉토리에 저장한다면 중복되지 않도록
// 파일 이름을 생성할 필요가 있습니다.
// 기본키와 파일명을 합치는 방법이 있고 UUID 클래스를 이용해서 만드는 방법
String uploadName = itemid + originName ;
item.setPictureurl(uploadName);
//파일을 저장할 경로를 생성
// 프로젝트 내의 경로를 가지고 절대경로를 생성
// 프로젝트 외의 경로면 직접 경로를 작성
String uploadPath = request.getRealPath("/img");
//servlet 3.0 이상인 경우는
//request.getServletContext().getRealPath("/img");
//업로드할 File 객체 생성 (파일경로생성)
File file = new File(uploadPath + File.separator + uploadName);
try {
request.getFile("pictureurl").transferTo(file);
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//데이터 삽입
return hibernateDao.insertItem(item);
}
6) HomeController 클래스에 insert를 post 방식으로 요청했을 때 처리를 위한 메소드를 생성
@RequestMapping(value="/insert", method=RequestMethod.POST)
public String insert(MultipartHttpServletRequest request, Model model ) {
//서비스의 메소드를 호출
itemService.insertItem(request) ;
// 삽입, 삭제, 갱신 등 새로고침을 했을 때 이전 작업을 다시 수행하면 안되는 경우에는
// 리다이렉트를 해야 한다.
// 리다이렉트를 할 때는 View 이름이 아니고 URL을작성
return "redirect:./ ";
}
7) 실행하고 파일을 확인해야 하는 디렉토리
- 프로젝트는 원래 모양 그대로 있고 실행이 될 때는 프로젝트의 내용을 build 해서 위의 디렉토리에 있는 내용이 실행되는 것이다.
- 프로젝트를 복사해서 다른 곳에서 실행하는 경우 프로젝트를 만들 때 존재했던 파일들은 그대로 있지만 실행 중 생성한 파일들은 없다.
- 개발환경에서 운영환경으로 이전(이행) 할 때 기존 데이터베이스 내용을 전부 삭제해야할때도 있다.