본문 바로가기
[ Programming ] Project/Fileboard with Thymeleaf

[스프링부트, 타임리프(Thymeleaf)로 파일게시판 만들기] #8 파일 업로드, 다운로드 게시판

by the_little_coder 2020. 3. 27.

스프링부트, 타임리프(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를 설정하지 않았기 때문입니다. 다음 글에서 이 문제를 해결하고, 다운로드를 완성하도록 하겠습니다.







댓글