스프링부트, 타임리프(Thymeleaf)로 파일게시판 만들기
#8 파일 업로드, 다운로드 게시판
파일업로드 구현
지난번까지 작성한 게시판에 파일첨부 및 다운로드 기능을 추가하겠습니다. 대략적인 흐름은 이렇습니다. 생성을 담당하는 insert.html에 파일첨부기능을 담당하는 <input>태그를 작성하고, <form>태그의 th:action속성값으로 요청을 한 후, 요청을 받은 곳에서 파일 저장을 해야합니다. 하지만 컨트롤러에서 요청을 수행할 때 그냥 저장하는 코드를 작성하는 것이 아니라, 파일저장을 도와주는 객체(VO, Entity)와 Database에서 Table을 생성한 후 이를 통해 저장해야 합니다. 이제 작성하겠습니다.
insert.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>파일게시판 - 글작성</title>
</head>
<body>
<div>
<div style="padding: 10px; text-align: center;">
<h1><a th:href="@{/fileBoard/list}" style="text-decoration: none;">게시글작성</a></h1>
</div>
<div>
<form role="form" th:object=${fileBoardVO} th:action="@{/fileBoard/insertProc}"
method="post" enctype="multipart/form-data">
...
<div>
<input type="file" name="files">
</div>
...
</form>
</div>
<br/><br/><br/>
</div>
</body>
</html>
지난번에 글을 작성했을 때 모르고 선반영했네요. 이제 컨트롤러를 작성하기 전에, 파일저장용 객체, DB 테이블, Mapper 및 Service에 메서드 추가, MyBatis 통한 쿼리문을 작성하겠습니다.
FileVO.java
package com.example.demo.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class FileVO {
private int f_no; //파일번호
private int b_no; //게시판 번호와 동기화
private String filename; //저장할 때
private String fileoriginname; //받아올 때 파일 이름
private String fileurl; //저장 및 불러올 경로
}
mysql> create table file(
-> f_no int primary key auto_increment,
-> b_no int not null,
-> filename varchar(200),
-> fileOriginName varchar(300),
-> fileUrl varchar(500),
-> foreign key (b_no) references file_board(b_no) on delete cascade
-> );
Query OK, 0 rows affected (0.02 sec)
mysql>
FileBoardMapper.java
package com.example.demo.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.example.demo.bean.FileBoardVO;
import com.example.demo.bean.FileVO;
@Mapper
public interface FileBoardMapper {
List<FileBoardVO> getFileBoardList();
...
//파일 업로드 메서드 추가
int fileInsert(FileVO file);
}
FileBoardService.java
package com.example.demo.service;
import java.util.List;
import com.example.demo.bean.FileBoardVO;
import com.example.demo.bean.FileVO;
public interface FileBoardService {
List<FileBoardVO> getFileBoardList();
...
//파일 업로드 메서드 추가
int fileInsert(FileVO file);
}
FileBoardServiceImpl.java
package com.example.demo.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.demo.bean.FileBoardVO;
import com.example.demo.bean.FileVO;
import com.example.demo.mapper.FileBoardMapper;
@Service
public class FileBoardServiceImpl implements FileBoardService {
@Autowired
FileBoardMapper fileboardmapper;
@Override
public List<FileBoardVO> getFileBoardList() {
return fileboardmapper.getFileBoardList();
}
...
//파일 업로드 추가
@Override
public int fileInsert(FileVO file) {
return fileboardmapper.fileInsert(file);
}
}
FileBoardMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTDMapper3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.FileBoardMapper">
<select id="getFileBoardList" resultType="com.example.demo.bean.FileBoardVO">
SELECT * FROM file_board
ORDER BY b_no
</select>
...
<!-- 파일 업로드 내용 추가 -->
<insert id="fileInsert" parameterType="com.example.demo.bean.FileVO">
<selectKey keyProperty="b_no" resultType="int" order="BEFORE">
SELECT MAX(b_no)
FROM file_board
</selectKey>
INSERT INTO file(b_no, filename, fileoriginname, fileurl)
VALUES(#{b_no}, #{filename}, #{fileoriginname}, #{fileurl})
</insert>
</mapper>
이제 컨트롤러를 작성하겠습니다. 아, 컨트롤러를 작성하기 이전에 pom.xml에서 dependency를 추가하겠습니다. <dependencies>와 </dependencies> 사이에 아래 내용을 아무곳에나 추가한 후 저장해주세요.
pom.xml
<dependencies>
...
<!--Multipart File Up&Download -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependencies>
이제 컨트롤러를 작성하겠습니다. @RequestMapping("/insertProc") 인 부분에 다음 내용을 작성해주세요.
FileBoardController.java
package com.example.demo.controller;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
import com.example.demo.bean.FileBoardVO;
import com.example.demo.bean.FileVO;
import com.example.demo.service.FileBoardService;
@Controller
@RequestMapping("/fileBoard")
public class FileBoardController {
@Autowired
FileBoardService fboardService;
...
@RequestMapping("/insertProc")
private String fileBoardInsertProc(@ModelAttribute FileBoardVO board, @RequestPart MultipartFile
files, HttpServletRequest request) throws IllegalStateException, IOException, Exception {
if(files.isEmpty()) {
fboardService.fileBoardInsert(board);
} else {
String fileName = files.getOriginalFilename(); // 사용자 컴에 저장된 파일명 그대로
String fileNameExtension = FilenameUtils.getExtension(fileName).toLowerCase();
File destinationFile; // DB에 저장할 파일 고유명
String destinationFileName;
//절대경로 설정 안해주면 지 맘대로 들어가버려서 절대경로 박아주었습니다.
String fileUrl = "업로드한 파일을 저장할 절대경로를 넣어주세요";
do { //우선 실행 후
//고유명 생성
destinationFileName = RandomStringUtils.randomAlphanumeric(32) + "." + fileNameExtension;
destinationFile = new File(fileUrl + destinationFileName); //합쳐주기
} while (destinationFile.exists());
destinationFile.getParentFile().mkdirs(); //디렉토리
files.transferTo(destinationFile);
fboardService.fileBoardInsert(board);
FileVO file = new FileVO();
file.setB_no(board.getB_no());
file.setFilename(destinationFileName);
file.setFileoriginname(fileName);
file.setFileurl(fileUrl);
fboardService.fileInsert(file);
}
return "forward:/fileBoard/list"; //객체 재사용
}
...
}
이제 localhost:8080/fileBoard/List를 열어서 잘 되는지 확인해보세요.
잘 되는 것 같은데 저장한 첨부파일이 보이지 않습니다. Database에는 잘 들어갔는지 확인해보겠습니다.
mysql> select * from file_board;
+------+-------------------------+---------------------------------+-----------+---------------------+
| b_no | title | content | writer | reg_date |
+------+-------------------------+---------------------------------+-----------+---------------------+
| 1 | 안녕하세요11 | 별 내용 없어요11111수정했다 | 운영자 | 2020-03-25 22:42:00 |
| 2 | 반가워요 | 안녕안녕? | 작성자2 | 2020-03-25 23:12:06 |
| 4 | 파일 하나 첨부해봅니다 | 될까? | 운영자3 | 2020-03-27 00:14:08 |
+------+-------------------------+---------------------------------+-----------+---------------------+
3 rows in set (0.00 sec)
mysql> select * from file;
+------+------+---------------------------------------+--------------------+-------------------------------+
| f_no | b_no | filename | fileOriginName | fileUrl |
+------+------+---------------------------------------+--------------------+-------------------------------+
| 1 | 4 | 0aDCWXS23Dc0hWxSgOwnTDFM31YH1pYC.jpeg | 2nd_Thumbnail.jpeg | /Users/shinjake/Desktop/down/ |
+------+------+---------------------------------------+--------------------+-------------------------------+
1 row in set (0.00 sec)
mysql>
컨트롤러에서 작성한대로 Database에 잘 들어갔습니다. 그렇다면 왜 Detail 페이지에서 보이지 않는걸까요? detail.html 페이지로 보낼 model을 작성하지 않았고, model을 작성하지 않았다는 것은 해당 파일을 Database에서 조회 할 mapper를 설정하지 않았기 때문입니다. 다음 글에서 이 문제를 해결하고, 다운로드를 완성하도록 하겠습니다.
댓글