본문 바로가기

국비교육/국비교육

day43 - 0926

파일 업로드 (DB를 이용하지 않고)

1. 파일 전송

<form>태그의 enctype 속성

- 폼 데이터를 서버로 보낼 때 해당 데이터가 인코딩되는 방식을 명시

application/x-www-form-urlencoded default 값으로, 모든 문자들을 서버로 보내기 전에 인코딩됨을 명시
text/plain 공백 문자(space)는 “+” 기호로 변환, 나머지 문자는 모두 인코딩하지 않음을 명시
multipart/form-data
모든 문자를 인코딩하지 않음을 명시한다.
이 방식은 <form> 요소가 파일이나 이미지를 서버로 전송할 때 주로 사용한다.

 

- multipart/form-data

<form>에는 여러 content-type의 <input>이 여러 개 들어갈 수 있다

서로 다른 content-type의 데이터가 하나의 HTTP Request Body에 들어갈 때

각각의 데이터를 구별하여  HTTP Request Body에 넣는 방법

< 참고 : https://velog.io/@shin6403/HTTP-multipartform-data-%EB%9E%80 >

 

** 파일 업로드

- <form> 태그의 method(전송 방식)를 post로, enctype(인코딩 방식)을 multipart/form-data로 한다

- <input> 태그의 type를 file로 하면 파일을 첨부할 수 있다

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<h1>DB없이 파일 업로드</h1>
<form action = "/" method = "post" enctype = "multipart/form-data">
	<input type = "text" name = "uploader">
	<br><br>
	<input type = "file" name = "attachment">
	<br><br>
	<button type = "submit">업로드</button>
</form>

 

2. src/main/resources에서 application.properties에서 multipart resolver에 대한 설정 추가

- spring.servlet.multipart.max-file-size : 서버로 전송되는 파일의 개별 용량 제한

- spring.servlet.multipart.max-request-size : 서버로 전송되는 파일의 총 용량 제한

# multipart resolver
spring.servlet.multipart.max-file-size=1MB
spring.servlet.multipart.max-request-size=10MB

 

3. Controller에서의 파일 수신 (Multipart Request 처리)

- Controller에서는 multipartFile 형태로 파일 수신

- 기존 @RequestParam, @ModelAttribute 등의 입력 방식 사용 가능

 

@Controller
public class FileController {
	
	// 의존성 주입
	@Autowired
	private AttachmentDao attachmentDao;
	
	// 파일 업로드 Mapping
	// 파일 업로드 Mapping에 DTO 전달 및 DB 처리 후 파일 저장
	@PostMapping("/upload")
	public String upload(@RequestParam MultipartFile attachment) throws IllegalStateException, IOException {
		
		// 첨부파일 업로드 기록 등록
		// 파일 업로드 기록 등록을 위해 다음 시퀀스 번호 반환
		int attachmentNo = attachmentDao.sequence();
		
		// AttachmentDto의 인스턴스 생성
		// - 첨부파일 번호(attachmentNo)는 반환한 다음 시퀀스 번호
		// - 첨부파일 이름(attachmentName)은 전달받은 MultipartFile의 원본 파일 이름
		// - 첨부파일 종류(attachmentType)는 전달받은 MultipartFile의 파일 종류
		// - 첨부파일 크기(attachmentSize)는 전달받은 MultipartFile의 파일 크기
		// 생성한 AttachmentDto의 인스턴스로 첨부파일 업로드 기록 등록(INSERT) 
		attachmentDao.insert(AttachmentDto.builder()
							.attachmentNo(attachmentNo)
							.attachmentName(attachment.getOriginalFilename())
							.attachmentType(attachment.getContentType())
							.attachmentSize(attachment.getSize())
						.build());
		
		// 실제 첨부파일을 특정 경로에 저장
		// 해당 문자열을 추상 경로로 변환하여 File 클래스의 인스턴스 dir 생성
		File dir = new File("C:\\\\Users\\\\hyeul\\\\upload");
		
		// 해당 추상 경로의 디렉토리 생성 (폴더가 없으면 자동으로 생성)
		dir.mkdirs();
		
		// dir의 추상 경로를 상위 경로, 해당 파일의 시퀀스 번호를 하위 경로로 하는 File의 인스턴스 생성
		File target = new File(dir, String.valueOf(attachmentNo));
		
		// View에서 전달받은 MultipartFile을 해당 디렉토리로 보내서 시퀀스 번호를 파일명으로 바꿔서 저장
		attachment.transferTo(target);
		return "redirect:/";
	}
	
	// 파일 업로드 기록 전체조회 Mapping
	@GetMapping("/list")
	public String list(Model model) {
		
		// model에 전체조회 결과를 첨부
		model.addAttribute("list", attachmentDao.selectList());
		return "list";
	}
}

 

MultipartFile 인터페이스 

- org.springframework.web 패키지에 포함

- 메소드 일부

byte[] getBytes() 파일의 내용을 byte 배열로 반환
String getContentType() 파일의 content-type 반환
String getName() multipart 형식의 매개변수 이름을 반환
long getOriginalFileName() 클라이언트의 파일 시스템에 있는 원래 파일 이름을 반환
boolean isEmpty() 업로드된 파일이 없는지 여부를 반환
default void transferTo(File dest) 수신한 파일을 해당 추상 경로로 보낸다

 

File 클래스

- java.io 패키지에 포함

- 생성자

File(File parent, String child) 상위 경로를 갖는 File 클래스의 인스턴스와 하위 경로 문자열을 이용하여 인스턴스 생성 
File(String pathname) 해당 경로명 문자열을 추상 경로로 변환하여 인스턴스 생성
File(String parent, String child) 상위 경로 문자열과 하위 경로 문자열을 이용하여 인스턴스 생성
File(URI uri) uri를 경로로 하는 인스턴스 생성

** File parent : 상위 디렉토리를 추상경로로 변환한 File 클래스의 인스턴스

** String parent : 상위 디렉토리를 나타내는 문자열

** String child : 하위 디렉토리를 나타내는 문자열

 

- 메소드

boolean mkdir() 추상 경로 이름의 디렉토리 생성 (상위 디렉토리가 없으면 생성하지 않는다)
boolean mkdirs() 추상 경로 이름의 디렉토리 생성 (상위 디렉토리가 없다면 생성한다)

 

회원 가입시 프로필 사진 업로드

MemberController

- 회원 가입시 첨부 파일을 저장할 수 있도록 회원 가입 Mapping을 수정

- 첨부파일이 존재할 때만 첨부파일을 특정 디렉토리에 저장하도록 설정

@Controller
@RequestMapping("/member")
public class MemberController {

	// 의존성 주입
	@Autowired
	private MemberDao memberDao;
	
	// 1. 회원 가입
	// 1-1. 회원 가입 Mapping
	@GetMapping("/join")
	public String join() {
		// 회원 가입 페이지(join.jsp)로 연결
		// return "/WEB-INF/views/member/insert.jsp";
		return "member/join";
	}
	
	// 1-2. 회원 가입 Mapping에 DTO 전달 및 DB 처리
	@PostMapping("/join")
	public String join(@ModelAttribute MemberDto memberDto, 
				@RequestParam MultipartFile memberProfile
				) throws IllegalStateException, IOException {
		
		// DB에서 등록(INSERT) 실행
		memberDao.join(memberDto);
		
		// 프로필 사진 첨부파일 저장
		if(!memberProfile.isEmpty()) {	// 첨부파일이 존재한다면(비어있지 않으면)
			
			// 해당 문자열을 추상 경로로 변환하여 File 클래스의 인스턴스 생성
			File directory = new File("C:\\Users\\hyeul\\upload\\member");
			
			// 해당 추상 경로의 디렉토리 생성 (폴더가 없으면 자동으로 생성)
			directory.mkdirs();
			
			// dir의 추상 경로를 상위 경로, memberDto의 memberId를 하위 경로로 하는 File의 인스턴스 생성
			File target = new File(directory, memberDto.getMemberId());
			
			// View에서 전달받은 MultipartFile을 해당 디렉토리로 보내서 memberDto의 memberId와 같은 이름으로 저장
			memberProfile.transferTo(target);
		}
		
		// 등록(INSERT) 처리 후 등록 완료(join_success) Mapping으로 강제 이동(redirect) (새로고침 시 중복 입력 방지)
		return "redirect:join_success";
	}
	
	// 1-3. 회원 가입 완료 Mapping
	@GetMapping("/join_success")
	public String joinSuccess() {
		// 회원 가입 완료 페이지(joinSuccess.jsp)로 연결
		// return "/WEB-INF/views/member/joinSuccess.jsp";
		return "member/joinSuccess";
	}
}

 

member 폴더의 join.jsp

- 프로필 사진을 업로드하기 위한 input을 추가한다

- <input> 태그의 type 속성을 file로 하면 첨부파일을 선택할 수 있다

- <input> 태그의 accept 속성을 설정하면 첨부파일의 확장자를 제한할 수 있다

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<jsp:include page = "/WEB-INF/views/template/header.jsp">
	<jsp:param name = "title" value = "회원 가입"/>
</jsp:include>
 
<h1>가입 정보 입력</h1>

<%-- enctype을 multipart/form-data로 변경 --%>
<form action = "insert" method = "post" enctype = "multipart/form-data">
	아이디 : <input name ="memberId" placeholder = "아이디 입력" type = "text" required><br><br>
	비밀번호 : <input name ="memberPw" placeholder = "비밀번호 입력" type = "password" required ><br><br>
	닉네임 : <input name ="memberNick" placeholder = "닉네임 입력" type = "text" required><br><br>
	생년월일 : <input name ="memberBirth" placeholder = "생년월일 입력" type = "date" required><br><br>
	연락처(선택) : <input name ="memberTel" placeholder = "연락처 입력" type = "text"><br><br>
	이메일(선택) : <input name ="memberEmail" placeholder = "이메일 입력" type = "text"><br><br>
	우편번호(선택) : <input name ="memberPost" placeholder = "우편주소 입력" type = "text" maxlength = "6" size = "6"><br><br>
	기본주소(선택) : <input name ="memberBaseAddress" placeholder = "기본주소 입력" type = "text"><br><br>
	상세주소(선택) : <input name ="memberDetailAddress" placeholder = "상세주소 입력" type = "text"><br><br>
	
	<%-- 프로필 사진 업로드용 input 추가 --%> 
	<%-- accept 속성을 통해 업로드 가능한 파일의 확장자를 제한할 수 있다 --%>
	프로필 이미지 : <input type = "file" name = "memberProfile" accept = ".png, .jpg"><br><br>
	<button>등록</button>
</form>

<jsp:include page = "/WEB-INF/views/template/footer.jsp"></jsp:include>

 

파일 다운로드 (DB를 이용하지 않고)

회원 가입시 업로드한 프로필 사진이 마이페이지에 나오도록

1. apache commons io 의존성 추가

 

2. Controller에서 ResponseEntity를 반환

- Controller에서 파일 다운로드에 대한 Mapping의 반환형은 ResponseEntity이다

- 파일 다운로드에 대한 Mapping의 메소드 실행 결과(return값)를 Http Response Body에 담아야 하므로

  메소드 위에 @ResponseBody 어노테이션을 붙인다

 

@Controller
@RequestMapping("/member")
public class MemberController {

	// 의존성 주입
	@Autowired
	private MemberDao memberDao;
	
	// 회원 가입시 업로드한 프로필사진 다운로드
	@GetMapping("/download")
	@ResponseBody
	public ResponseEntity<ByteArrayResource> download(@RequestParam String memberId) throws IOException {
		
		// 해당 문자열을 추상 경로로 변환하여 File 클래스의 인스턴스 생성
		File directory = new File("C:\\Users\\hyeul\\upload\\member");
		
		
		// dir의 추상 경로를 상위 경로, memberDto의 memberId를 하위 경로로 하는 File의 인스턴스 생성
		File target = new File(directory, memberId);
		
		if(target.exists()) { // 해당 디렉토리에 해당 파일이 존재한다면
			
			// target의 내용을 byte 배열로 변환
			byte[] data = FileUtils.readFileToByteArray(target);
			
			// byte 배열인 data를 이용하여 ByteArrayResource의 인스턴스 생성
			ByteArrayResource resource = new ByteArrayResource(data);
			
			// HTTP Response Header에 내용의 인코딩 방식, 길이, 배치 방식, resource의 형식 정보를 반환
			// HTTP Response Body에 ByteArrayResource를 포함하는 ResponseEntity 반환
			return ResponseEntity.ok()
					.header("Content-Encoding", "UTF-8")
					.header("Content-Length", String.valueOf(data.length))
					.header("Content-Disposition", "attachment; filename=" + memberId)
					.header("Content-Type", "application/octet-stream")
					.body(resource);
		}
		else { // 해당 디렉토리에 해당 파일이 존재하지 않는다면
			
			// 1) 사용자 정의 예외를 발생시키는 방법
//			throw new TargetNotFoundException("프로필 없음");
			
			// 2) 404 error를 전송하는 방법
			return ResponseEntity.notFound().build();
		}
	}
}

 

ResponseEntity<T> 클래스

- T : the body type

- T는 ByteArrayResource로 한다 (필수)

 

FileUtils 클래스

static byte[] readFileToByteArray(File file) File을 byte의 배열로 변환

 

ByteArrayResource 클래스

- 주어진 byte 배열로 Resource를 구현(implement)한다

 

3. 마이페이지에서 회원가입시 업로드한 이미지가 표시되도록 jsp 변경

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

<jsp:include page = "/WEB-INF/views/template/header.jsp">
	<jsp:param name = "title" value = "마이 페이지"/>
</jsp:include>

<h1>마이 페이지</h1>

<%-- 마이페이지에서 회원가입시 업로드한 이미지가 표시되도록 --%>	
<img src = "download?memberId=${memberDto.memberId}" width = "100" height = "100">
		
<h3>아이디 : "${memberDto.getMemberId()}"</h3>
<h3>닉네임 : ${memberDto.getMemberNick()}</h3>
<h3>생년월일 : ${memberDto.getMemberBirth()}</h3>
<h3>연락처 : ${memberDto.getMemberTel()}</h3>
<h3>이메일 : ${memberDto.getMemberEmail()}</h3>
<h3>우편주소 : ${memberDto.getMemberPost()}</h3>
<h3>기본주소 : ${memberDto.getMemberBaseAddress()}</h3>
<h3>상세주소 : ${memberDto.getMemberDetailAddress()}</h3>
<h3>포인트 : ${memberDto.getMemberPoint()}</h3>
<h3>등급 : ${memberDto.getMemberGrade()}</h3>
<h3>가입일 : <fmt:formatDate value="${memberDto.memberJoin}" pattern="y년 M월 d일 E a h시 m분 s초"/></h3>
<h3>최종 로그인 : <fmt:formatDate value="${memberDto.memberLogin}" pattern="y년 M월 d일 E a h시 m분 s초"/></h3>

<br>

<c:choose>
	<c:when test="${mg == '관리자'}">
		<!-- 관리자용 메뉴 -->
		<h2><a href = "member/list">목록 보기</a></h2>
		<h3><a href = "edit?memberId=${memberDto.getMemberId()}">회원 정보 변경</a></h3>
	</c:when>
	<c:otherwise>
		<!-- 회원용 메뉴 -->
		<h3><a href = "/member/information">개인 정보 변경</a></h3>
		<h3><a href = "/member/password">비밀번호 변경</a></h3>
		<h3><a href = "/member/goodbye">회원 탈퇴</a></h3>
	</c:otherwise>
</c:choose>

<jsp:include page = "/WEB-INF/views/template/footer.jsp"></jsp:include>

 


 

파일 업로드  (DB를 이용하여)

- DB를 이용하면 파일 업로드시 업로드한 파일의 정보를 DB에 기록하여 그 값을 조회를 통해 불러올 수 있다

- 파일 업로드 기록에 대한 CRUD가 필요하다

 

테이블 생성

첨부파일(attachment)
- 첨부파일 번호(attachment_no) : 사용자가 저장한 파일의 파일명을 시퀀스로 대체, 숫자, 기본키
- 첨부파일 이름(attachment_name) : 사용자가 업로드한 파일명, 문자(256 byte), 필수 입력
- 첨부파일 유형(attachment_type) : 업로드한 파일의 유형, 문자(30 byte), 필수 입력
- 첨부파일 크기(attachment_size) : 업로드한 파일의 크기, 숫자, 필수 입력, 0 이상의 값이 들어가도록
- 첨부파일 업로드 시간(attachment_time) : 업로드한 시간, 날짜, 기본값을 sysdate로, 필수 입력
-- 테이블 생성
create table attachment(
attachment_no number primary key,
attachment_name varchar2(256) not null,
attachment_type varchar2(30) not null,
attachment_size number not null check(attachment_size >= 0),
attachment_time date default sysdate not null
);

-- 시퀀스 생성
create sequence attachment_seq;

 

AttachmentDto

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AttachmentDto {

	// 필드
	int attachmentNo;
	String attachmentName;
	String attachmentType;
	long attachmentSize;
	Date attachmentTime;
}

 

1. 파일 업로드 기록 등록 및 조회

AttachmentDao

public interface AttachmentDao {

	// 추상 메소드 - 첨부파일 번호를 위한 다음 시퀀스 번호 반환
	int sequence();
	
	// 추상 메소드 - 첨부파일 업로드 기록 등록
	void insert(AttachmentDto attachmentDto);
	
	// 추상 메소드 - 첨부파일 업로드 기록 전체조회
	List<AttachmentDto> selectList();
}

 

AttachmentDaoImpl

@Repository
public class AttachmentDaoImpl implements AttachmentDao {

	// 의존성 주입
	@Autowired
	private JdbcTemplate jdbcTemplate;
	
	// AttachmentDto에 대한 RowMapper
	private RowMapper<AttachmentDto> mapper = new RowMapper<>() {
		@Override
		public AttachmentDto mapRow(ResultSet rs, int rowNum) throws SQLException {
			return AttachmentDto.builder()
						.attachmentNo(rs.getInt("attachment_no"))
						.attachmentName(rs.getString("attachment_name"))
						.attachmentType(rs.getString("attachment_type"))
						.attachmentSize(rs.getLong("attachment_size"))
						.attachmentTime(rs.getDate("attachment_time"))
					.build();
		}
	};
	
	// AttachmentDto에 대한 ResultSetExtractor
	private ResultSetExtractor<AttachmentDto> extractor = new ResultSetExtractor<>() {
		@Override
		public AttachmentDto extractData(ResultSet rs) throws SQLException, DataAccessException {
			if(rs.next()) {
				return AttachmentDto.builder()
							.attachmentNo(rs.getInt("attachment_no"))
							.attachmentName(rs.getString("attachment_name"))
							.attachmentType(rs.getString("attachment_type"))
							.attachmentSize(rs.getLong("attachment_size"))
							.attachmentTime(rs.getDate("attachment_time"))
						.build();
			}
			else {
				return null;
			}
		}
	};

	// 추상 메소드 오버라이딩 - 첨부파일 번호를 위한 다음 시퀀스 번호 반환
	@Override
	public int sequence() {
		String sql = "select attachment_seq.nextval from dual";
		return jdbcTemplate.queryForObject(sql, int.class);
	}	
	
	// 추상 메소드 오버라이딩 - 첨부파일 업로드 기록 등록
	@Override
	public void insert(AttachmentDto attachmentDto) {
		String sql = "insert into attachment("
						+ "attachment_no, "
						+ "attachment_name, "
						+ "attachment_type, "
						+ "attachment_size) "
					+ "values(?, ?, ?, ?)";
		Object[] param = new Object[] {attachmentDto.getAttachmentNo(), 
						attachmentDto.getAttachmentName(), 
						attachmentDto.getAttachmentType(), 
						attachmentDto.getAttachmentSize()
 						};
		jdbcTemplate.update(sql, param);
	}

	// 추상 메소드 - 첨부파일 업로드 기록 전체조회
	@Override
	public List<AttachmentDto> selectList() {
		String sql = "select * from attachment";
		return jdbcTemplate.query(sql, mapper);
	}
}

 

AttachmentController

@Controller
public class FileController {
	
	// 의존성 주입
	@Autowired
	private AttachmentDao attachmentDao;
	
	// 파일 업로드 Mapping
	// 파일 업로드 Mapping에 DTO 전달 및 DB 처리 후 파일 저장
	@PostMapping("/upload")
	public String upload(@RequestParam MultipartFile attachment) throws IllegalStateException, IOException {
		
		// 첨부파일 업로드 기록 등록
		// 파일 업로드 기록 등록을 위해 다음 시퀀스 번호 반환
		int attachmentNo = attachmentDao.sequence();
		
		// AttachmentDto의 인스턴스 생성
		// - 첨부파일 번호(attachmentNo)는 반환한 다음 시퀀스 번호
		// - 첨부파일 이름(attachmentName)은 전달받은 MultipartFile의 원본 파일 이름
		// - 첨부파일 종류(attachmentType)는 전달받은 MultipartFile의 파일 종류
		// - 첨부파일 크기(attachmentSize)는 전달받은 MultipartFile의 파일 크기
		// 생성한 AttachmentDto의 인스턴스로 첨부파일 업로드 기록 등록(INSERT) 
		attachmentDao.insert(AttachmentDto.builder()
							.attachmentNo(attachmentNo)
							.attachmentName(attachment.getOriginalFilename())
							.attachmentType(attachment.getContentType())
							.attachmentSize(attachment.getSize())
						.build());
		
		// 실제 첨부파일을 특정 경로에 저장
		// 추상경로에 대한 File의 인스턴스 생성
		File dir = new File("C:\\\\Users\\\\hyeul\\\\upload");
		
		// 해당 추상경로의 디렉토리 생성 (폴더가 없으면 자동으로 생성)
		dir.mkdirs();
		
		// 해당 디렉토리에 해당 파일의 시퀀스 번호를 파일명으로 하여 파일을 저장하도록 하는 File의 인스턴스 생성
		File target = new File(dir, String.valueOf(attachmentNo));
		
		// View에서 전달받은 MultipartFile을 해당 디렉토리로 보내서 시퀀스 번호를 파일명으로 바꿔서 저장
		attachment.transferTo(target);
		return "redirect:/";
	}
	
	// 파일 업로드 기록 전체조회 Mapping
	@GetMapping("/list")
	public String list(Model model) {
		
		// model에 전체조회 결과를 첨부
		model.addAttribute("list", attachmentDao.selectList());
		return "list";
	}
}

 

root.jsp (파일 업로드)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<h1>파일 업로드 테스트</h1>

<h1>DB 사용하는 업로드</h1>

<form action="/upload" method="post" enctype="multipart/form-data">
	<input type="file" name="attachment">
	<button type="submit">등록</button>
</form>

<h1><a href="list">파일목록 보기</a></h1>

 

list.jsp (파일 목록)

<c:forEach var = "attachmentDto" items = "${list}">
	<h1>파일 목록</h1>
	<h3>
		[${attachmentDto.attachmentType}]
		${attachmentDto.attachmentName}
		(${attachmentDto.attachmentSize} bytes)
	</h3>
</c:forEach>

 

파일 다운로드 (DB를 이용하여)

AttachmentDao

public interface AttachmentDao {
	
	// 추상 메소드 - 첨부파일 업로드 기록 단일조회
	AttachmentDto selectOne(int attachmentNo);
}

 

AttachmentDaoImpl

@Repository
public class AttachmentDaoImpl implements AttachmentDao {

	// 의존성 주입
	@Autowired
	private JdbcTemplate jdbcTemplate;
	
	// AttachmentDto에 대한 ResultSetExtractor
	private ResultSetExtractor<AttachmentDto> extractor = new ResultSetExtractor<>() {
		@Override
		public AttachmentDto extractData(ResultSet rs) throws SQLException, DataAccessException {
			if(rs.next()) {
				return AttachmentDto.builder()
						.attachmentNo(rs.getInt("attachment_no"))
						.attachmentName(rs.getString("attachment_name"))
						.attachmentType(rs.getString("attachment_type"))
						.attachmentSize(rs.getLong("attachment_size"))
						.attachmentTime(rs.getDate("attachment_time"))
					.build();
			}
			else {
				return null;
			}
		}
	};

	// 추상 메소드 - 첨부파일 업로드 기록 단일조회
	@Override
	public AttachmentDto selectOne(int attachmentNo) {
		String sql = "select * from attachment where attachment_no = ?";
		Object[] param = new Object[] {attachmentNo};
		return jdbcTemplate.query(sql, extractor, param);
	}
}

 

FileController

- View에서 입력받은 첨부파일 번호(attachmentNo)로 단일 조회 실행

- 해당 번호의 첨부파일이 존재한다면 File 클래스의 인스턴스 생성를 생성하여 byte 배열로 만든 후

  ByteArrayResource 형태로 HTTP Response Body에 첨부하여 해당 경로로 파일 다운로드 구현

@Controller
public class FileController {
	
	// 의존성 주입
	@Autowired
	private AttachmentDao attachmentDao;

	// 파일 다운로드 Mapping
	@GetMapping("/download")
	public ResponseEntity<ByteArrayResource> download(@RequestParam int attachmentNo) throws IOException{
		
		// 입력받은 첨부파일 번호(attachmentNo)로 단일 조회 실행
		AttachmentDto attachmentDto = attachmentDao.selectOne(attachmentNo);
		
		// 해당 첨부파일 번호(attachmentNo)의 첨부파일이 존재하지 않을 경우
		if(attachmentDto == null) {
			return ResponseEntity.notFound().build();	// 404 error
		}
		
		// 해당 첨부파일 번호(attachmentNo)의 첨부파일이 존재할 경우
		// 해당 문자열을 추상 경로로 변환하여 File 클래스의 인스턴스 생성
		File directory = new File("C:\\\\Users\\\\hyeul\\\\upload");
		
		// directory의 추상 경로를 상위 경로, 해당 파일의 시퀀스 번호를 하위 경로로 하는 File의 인스턴스 생성
		File target = new File(directory, String.valueOf(attachmentNo));
		
		// target의 내용을 byte 배열로 변환
		byte[] data = FileUtils.readFileToByteArray(target);
		
		// byte 배열인 data를 이용하여 ByteArrayResource의 인스턴스 생성
		ByteArrayResource resource = new ByteArrayResource(data);
		
		// HTTP Response Header에 내용의 인코딩 방식, 길이, 배치 방식, resource의 형식 정보를 반환
		// HTTP Response Body에 ByteArrayResource를 포함하는 ResponseEntity 반환
		return ResponseEntity.ok()
			.header("Content-Encoding", "UTF-8")
			.header("Content-Length", String.valueOf(attachmentDto.getAttachmentSize()))
			.header("Content-Disposition", "attachment; filename=" + attachmentDto.getAttachmentName())
			.header("Content-Type", attachmentDto.getAttachmentType())
			.body(resource);
}

 

** ReponseEntity를 반환하는 방법

i) 상수를 이용하지 않고 ResponseEntity 반환

// 상수를 이용하지 않고 ResponseEntity 반환
return ResponseEntity.ok()
	.header("Content-Encoding", "UTF-8")
	.header("Content-Length", String.valueOf(attachmentDto.getAttachmentSize()))
	.header("Content-Disposition", "attachment; filename=" + attachmentDto.getAttachmentName())
	.header("Content-Type", attachmentDto.getAttachmentType())
	.body(resource);

 

ii) 상수를 이용하여 ReponseEntity 반환

// 상수를 이용하여 ResponseEntity 반환
return ResponseEntity.ok()
	.header(HttpHeaders.CONTENT_ENCODING, StandardCharsets.UTF_8.name())
	.contentLength(attachmentDto.getAttachmentSize())
	.contentType(MediaType.APPLICATION_OCTET_STREAM)
	.header(HttpHeaders.CONTENT_DISPOSITION, 
			ContentDisposition.attachment().filename(attachmentDto.getAttachmentName(), 
			StandardCharsets.UTF_8)
			.build().toString())
	.body(resource);

 

i) 방식과 ii) 방식의 비교

- Content-Encoding에 대하여 두 방식의 차이는 없다

- Content-Length에 대하여 String으로 값을 변환해야 하는 i) 방식은 번거로우므로 ii) 방식이 좋다

- Content-Type에 대하여 보다 일반적인 경우를 생각하면 i) 방식이 좋다 ( ii) 방식은 아예 Content-Type을 한정한다)

- Content-Disposition에 대하여 파일명이 한글이거나 띄어쓰기가 포함된 경우를 위해 ii) 방식이 좋다

 

list.jsp

- 업로드된 파일의 미리보기 표시

- 업로드된 파일을 다운로드할 수 있도록 하는 하이퍼링크 추가 (하이퍼링크를 통해 첨부파일 번호를 전달)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix = "c" uri="http://java.sun.com/jsp/jstl/core" %>

<c:forEach var = "attachmentDto" items = "${list}">

	<h3>
		<%-- 미리보기 표시 --%>
		<img src = "download?attachmentNo=${attachmentDto.attachmentNo}" width = "50" height = "50">
		
		[${attachmentDto.attachmentType}] 
		
		<%-- 업로드된 파일을 다운로드할 수 있도록 하는 하이퍼링크 추가 --%>
		<a href = "download?attachmentNo=${attachmentDto.attachmentNo}">${attachmentDto.attachmentName} </a>
		
		(${attachmentDto.attachmentSize})
	</h3>
</c:forEach>

'국비교육 > 국비교육' 카테고리의 다른 글

day45 - 0928  (0) 2022.09.28
day44 - 0927  (0) 2022.09.27
day42 - 0923  (0) 2022.09.24
day41 - 0922  (0) 2022.09.22
day40 - 0921  (0) 2022.09.22