Tiny Bunny [Spring] 파일 입출력 (사진 업로드) - 솜님의 블로그
솜님의 블로그
카테고리
작성일
2024. 10. 24. 01:08
작성자
겨울솜사탕

파일 입출력

 

 

몇 가지 사전 작업이 필요하다.

 

우선 사진이 저장될 테이블 생성이 필요!

 

그럼 이제 이 데이터를 볼 수 있는 상세페이지가 필요하므로

board.jsp 파일을 새로 만들어준다!

 

 

// board.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>글 상세페이지</title>
<script src="js/preview.js"></script>
</head>
<body>


내용 ${data.content} <br>
작성자 ${data.writer} <br>

<hr>

<c:if test="${empty path}">
	<img alt="기본 이미지" src="images/default.png">
</c:if>
<c:if test="${not empty path}">
	<img alt="비워두면 안됨" src="images/${path}">
</c:if>

<form action="updateBoard.do" method="POST" enctype="multipart/form-data">
	<input type="hidden" name="bid" value="${data.bid}">
	이미지 <input type="file" name="file" onchange="preview(event)"> <br>
	<img id="previewImage" style="display:none;margin:5px;" alt="미리보기 이미지"><br>
	<input type="submit" value="이미지 변경">
</form>

<hr>

<a href="main.do">메인으로 이동</a>

</body>
</html>

<c:if> 태그를 사용해서 이미지가 있는 상태일 때와 없는 상태일 때를 구분하여 보여준다.

c 태그를 사용했기 때문에 상단에 태그 사용할 수 있도록 선언 필요하고,

이 상세페이지에서 사진 수정이 가능하기 때문에 데이터를 넘겨줘야 하므로 form 태그 사용해서 작성한다.

 

이때 이 form은 이미지만을 다루기 때문에 enctype="multipart/form-data" 로 설정하고,

bid의 경우 view에서 넘겨주는 데이터라서, 사용자에게 보이지 않도록 hidden으로 설정해준다.

 

이미지가 수정되는 경우 보통 미리 보기가 있는데

이 미리 보기는 스크립트를 통해 가능 --> js 파일 별도로 만들어서 해당 jsp 파일에 연결해 준다.

 

하나 체크해야 할 사항은?

이 jsp 파일에서 필요한 데이터는 data와 path이다. 여기서는 updateBoard.do를 할 수 있다!

 

 

// preview.js

function preview(event) {
	const file = event.target.files[0]; // 사용자가 선택한 파일 목록에서 첫 번째 파일을 가져와 file 변수에 저장
	const reader = new FileReader(); // 파일을 읽을 수 있도록 해주는 메서드
	reader.onload = function() { // 파일읽기가 완료됐을 때 실행
		// id 요소를 찾아서 변수에 저장한다.
		const imgPreview = document.getElementById('previewImage');
		// reader.result ==> 읽어온 파일의 데이터 URL
		// 이 URL을 미리보기 이미지의 src 속성에 할당 
		imgPreview.src = reader.result;
		// 미리보기 이미지 스타일을 변경 
		imgPreview.style.display = 'block';
	};
	if (file) { // 사용자가 파일을 선택했다면
		// 파일을 데이터 url로 읽기 시작
		reader.readAsDataURL(file);
	}
}

사진을 미리 보기 할 수 있는 js 코드이다.

 

게시판 목록에서 글을 선택해 상세페이지로 갈 수 있도록 main도 경로를 달아주었다.

 

// main.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>메인 페이지</title>

<link rel="stylesheet" href="css/style.css" type="text/css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="js/boardList.js"></script>

</head>
<body>


    <h1>${userID}님, 안녕하세요! :D</h1>
<div class="header">
	${boardDTO} | ${boardDTO.bid} | ${boardDTO.content}
    <a href="logout.do" class="logout-link">로그아웃</a>
</div> 

<hr>



<div>
	<h3>글 목록</h3>
		<div class="action-links">
		    <a href="insertBoard.do">글 작성</a>
		    <a href="main.do" id="showBoardList">글 전체 목록 보기</a>
		</div>
</div>

<form method="GET" id="searchBoardForm">
	<select name="condition" id="condition">
		<option value="CONTENT" ${boardDTO.condition eq 'CONTENT' ? 'selected' : ''}>내용</option>
		<option value="WRITER" ${boardDTO.condition eq 'WRITER' ? 'selected' : ''}>작성자</option>
	</select>
	<input type="text" name="keyword" id="keyword" placeholder="검색어를 입력해 주세요." required>
	<input type="submit" value="검색">
</form>

<ul id="postList"> 
	<c:if test="${empty datas}">
		<li>검색결과가 없습니다.</li>
	</c:if>
	<c:if test="${not empty datas}">
		<c:forEach var="data" items="${datas}">
			<li>
				<a href="board.do?bid=${data.bid}">
					<div class="content">${data.content}</div>
					<div class="writer">작성자 : ${data.writer}</div>
				</a>
			</li>
		</c:forEach>
	</c:if>
</ul>

<hr>

</body>
</html>

content와 writer이 나오는 div 박스를 a 태그로 감싸주어서 글을 선택 시 

상세 페이지로 이동할 수 있도록 작성해 주었다.

 

 

(마우스 가져다 대면 파란색 줄이 뜨면서 반응함)

 

 


 

위에서 얘기했듯이 board.jsp에서는 data와 path가 필요하다.

Controller에서 이미지 파일을 손쉽게 보낼 수 있도록 MultipartFile을 사용한다.

 

MultipartFile이란?

Spring에서 제공하는 파일 업로드 처리에 사용되는 인터페이스이다.

파일 업로드를 간편하게 처리할 수 있도록 도와준다.

 

image 테이블을 생성했기 때문에 DTO와 DAO 생성이 필요하다.

이때 DTO의 멤버변수로 MultipartFile 속성의 멤버변수를 등록해 준다.

MultipartFile import는  아래와 같이 나오는데,

import org.springframework.web.multipart.MultipartFile;

public class ImageDTO {
	private int imageid; // 사진 id (PK)
	private String path; // 사진 경로
	private int bid; // 글 번호 (FK)
	private MultipartFile file; // Spring에서 파일 업로드 처리하기 위한 인터페이스

간혹 자동으로 제공을 하지 않는 경우가 있다!

그런 경우는 의존주입을 해주면 된다.

 

<dependency>
	<groupId>commons-fileupload</groupId>
	<artifactId>commons-fileupload</artifactId>
	<version>1.3.1</version>
</dependency>

 

 

이 MultipartFile를 사용하기 위해서는 Resolver가 필요하다.

<!-- multipartFile 사용하기 위해 추가 -->
<bean class="org.springframework.web.multipart.support.StandardServletMultipartResolver" id="multipartResolver" />

이것도 Resolver류이기 때문에 이름을 정확하게 작성해야 한다.

 

 

main.jsp에서 게시글 상세페이지로 보내는 board.do 요청과

상세페이지에서 이미지 업데이트를 하는 updateBoard.do 요청에 대한 Controller 코드가 필요하다.

 

// Controller_board.do

// 글 상세페이지 출력
	@RequestMapping(value="/board.do")
	public String selectBoard(BoardDTO boardDTO, ImageDTO imageDTO, Model model) {
		// 글 번호를 받아와서 상세페이지로 데이터 전달 및 이동시켜줌
		// data와 path가 필요하다.
		// 글 번호를 service로 보내서 data / path 반환받아 view 전달 필요
		System.out.println("com.koreait.app.view.board BoardController selectBoard 시작");
		System.out.println("com.koreait.app.view.board BoardController selectBoard boardDTO ["+boardDTO.getBid()+"]");

		// 게시글 찾기
		boardDTO = boardService.selectOne(boardDTO);

		// 사진 찾기
		imageDTO = imageService.selectOne(imageDTO);

		// 데이터 로그
		System.out.println("com.koreait.app.view.board BoardController selectBoard boardDTO ["+boardDTO+"]");
		System.out.println("com.koreait.app.view.board BoardController selectBoard imageDTO ["+imageDTO+"]");


		// 반환받은 객체를 model 객체에 저장한다. (v에게 전달하기 위함)
		model.addAttribute("data", boardDTO);
 
		if(imageDTO != null) { // 만약 반환받은 imageDTO에 데이터가 있다면
			// 반환받은 객체를 model 객체에 저장한다. (v에게 전달하기 위함)
			model.addAttribute("path", imageDTO.getPath());
		}


		// 데이터를 찾아서 board.jsp로 보내줘야 한다.
		// 데이터를 보내야 하므로 포워드 
		System.out.println("com.koreait.app.view.board BoardController selectBoard 종료");

		return "board";
	}

글 상세페이지가 추가되면서 위에 언급했던 data와 path가 필요하다.

따라서 이곳에서 커맨드 객체로 boardDTO와 imageDTO 둘 다 반영해 주고,

글 번호를 보내 selectOne으로 데이터를 받아온다.

 

// Controller_updateBoard.do

	@RequestMapping(value="/updateBoard.do")
	public String imageUpload(ImageDTO imageDTO) throws IllegalStateException, IOException  {
		
		System.out.println("com.koreait.app.view.board BoardController imageUpload 시작");
		System.out.println("com.koreait.app.view.board BoardController imageUpload imageDTO ["+imageDTO+"]");

		
		MultipartFile file = imageDTO.getFile(); // DTO에서 MultipartFile 객체를 가지고 옴 >> 업로드 된 파일 정보를 가지고 있음
		String fileName = file.getOriginalFilename(); // 원본 파일명
		
		System.out.println("com.koreait.app.view.board BoardController imageUpload 파일명 ["+fileName+"]");


			file.transferTo(new File(PATH+fileName)); // 업로드 된 파일을 서버의 경로에 저장한다.
			imageDTO.setPath(fileName);
			
			System.out.println("Inserting image with BID: " + imageDTO.getBid() + " and path: " + imageDTO.getPath());
			boolean flag = imageService.update(imageDTO);
			
			if(flag) {
				System.out.println("com.koreait.app.view.board BoardController imageUpload 파일 insert 성공");

			}
			else {
				System.out.println("com.koreait.app.view.board BoardController imageUpload 파일 insert 실패");

			}

		System.out.println("com.koreait.app.view.board BoardController imageUpload 파일 insert 종료");

		return "redirect:main.do";
				
	}

이미지 업로드 시 MultipartFile 객체를 사용하여 업로드 한 파일의 정보를 가지고 오고,

이미지 복사의 개념이기  때문에 원본 파일명을 가져와 변수에 저장해 준다.

 

그 후 transferTo 메서드를 사용하여 업로드된 파일을 서버의 경로에 저장시킨다.

저장할 때 인자로 File 객체가 들어가는데, 경로와 + 파일명을 적어준다. ==> 이것이 파일의 경로가 된다.

 

 

 

 

파일선택을 눌러 사진을 선택하면

미리 보기가 아래에 나오게 된다.

여기서 이미지 변경을 누르면 main페이지로 가는데,

다시 해당 글을 선택하면

 

 

업로드한 이미지로 변경이 된다.

 

 

🍀 정리
main에서 board.do를 통해 게시글 상세 보기가 가능해졌고,
상세 보기에서는 updateBoard.do를 통해 데이터를 바꾸거나 추가할 수 있다.
그럼 Controller에서는 imageDTO를 받아올 수 있는데
이 과정에서 MultipartFile 이 필요하고, 이를 관리하는 Resolver가 필요하게 된다.
우리는 이 Resolver를 서블릿에 등록해줘야 한다.

Controller에서는 getFile을 통해 파일명을 알아내거나 transferTo를 통해 파일을 생성시킬 수 있다.

 

 


 

1)

여기까지 했을 때 버전 때문에 에러 나는 경우가 있다.

만약 잘 안된다면 Servers의 context.xml에 가서 

<Context allowCasualMultipartParsing="true">  를 추가해 준다.

 

 

 

2)

사진 업로드 하게 되면 변경된 이미지를 보여주기 전까지 로딩이 꽤나 걸린다....

window - Preferences - Workspace에서 즉시 새로고침 설정을 해줄 수 있다.

'Spring' 카테고리의 다른 글

[Mybatis] 마이바티스의 특징과 Eclipse 플러그인 설치  (2) 2024.11.04
[Spring] 횡단 로직 정리  (0) 2024.10.28
[Spring] 트랜잭션  (0) 2024.10.21
[Spring] 템플릿 패턴  (0) 2024.10.20
[Spring] AOP 어노테이션 사용  (0) 2024.10.16