📂 Spring Boot 파일 업로드 + DB 저장 정리
사용자가 업로드한 파일을 서버 디렉토리에 저장하고, 파일 정보(이름/크기 등)를 DB에 기록하는 기능
1. 기본준비
🔽 사용한 sql문
더보기
더보기
drop sequence file_seq;
create sequence file_seq;
drop table file_info;
CREATE TABLE file_info (
id NUMBER PRIMARY KEY,
name varchar2(20),
filename VARCHAR2(255) NOT NULL,
file_size NUMBER NOT NULL
);
col id for 99
col name for a10
col filename for a20
select * from file_info order by id;
🔽 application.properties에 작성 -- db연결
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:orcl
spring.datasource.username=sqlid
spring.datasource.password=sqlpw
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
mybatis.mapper-locations=classpath:mapper/*.xml
🔽 <properties> 아래에 추가
<repositories>
<repository>
<id>oracle</id>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
</repositories>
🔽 <dependency> 아래에 추가 + 유효성검증 <dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
🌈 2. 파일 업로드 기본 구조 흐름
Controller → Service → Interface(Mapper) → Mapper(XML, SQL)
- Controller → 요청 받고 검증 후 Service 호출
- Service → 파일 저장 + DB 저장 로직 처리
- Interface (Mapper) → 자바 ↔ MyBatis 연결 다리
- Mapper(XML) → 실제 SQL 실행
🌈 3. 작성순서
1. DB 테이블 & 시퀀스 생성
→ file_info(id, name, filename, file_size)
2. DTO 작성 (FileInfo)
→ 업로드 정보 담을 객체
3. Mapper 인터페이스 & XML 작성
→ SQL 정의 및 연결
4. Service 작성
→ 파일 저장 + DB 저장 로직
5. Controller 작성
→ 요청 수신, 검증, Service 호출
6. View 작성 (form.html, list.html)
→ 업로드 폼 & 업로드 목록 출력
🌈 4. 파일 업로드 전체코드
📁 controller
더보기
더보기
package com.example.Ex02.controller;
import com.example.Ex02.dto.FileInfo;
import com.example.Ex02.service.FileService;
import jakarta.validation.Valid;
import org.apache.catalina.connector.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import javax.imageio.IIOException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@Controller
public class FileUpLoadController {
@Autowired
FileService fileService;
public static final String UPLOAD_DIR = System.getProperty("user.dir") + "/uploads/";
@GetMapping(value = "/form")
public String form(Model model){
model.addAttribute("fileInfo", new FileInfo());
return "form";
}
// 업로드 post처리
@PostMapping("/upload")
public String upload(@Valid FileInfo fileInfo, BindingResult br, Model model){
// 파일 유효성 검사 ==> 파일은 수동으로 체크 필요
if(fileInfo.getFile()==null || fileInfo.getFile().isEmpty()){
br.rejectValue("file", "file.empty", "파일을 선택하세요");
}
if(br.hasErrors()){
model.addAttribute("fileInfo", fileInfo);
model.addAttribute("message", br.getAllErrors().get(0).getDefaultMessage());
return "form";
}
// controller - service - interface - mapper
// 파일 저장 시도
try {
fileService.uploadFile(fileInfo);
model.addAttribute("message", "업로드 성공" + fileInfo.getFilename());
}catch (IOException e){
model.addAttribute("message", "업로드 실패");
}
return "form";
}
// 이미지 띄우기 a태그 --> 전체 이미지 보기
@GetMapping(value = "/images")
public String images(Model model){
List<FileInfo> lists = fileService.getAllFiles();
System.out.println("lists.size()" + lists.size()); // 넣은 값 개수 확인
model.addAttribute("lists",lists);
return "list";
}
// 이미지 띄우기 get방식 요청
// {filename:.+} → 확장자(.) 포함된 파일명도 PathVariable로 받기 가능
@GetMapping(value="/images/{filename:.+}")
public ResponseEntity images(@PathVariable String filename) throws MalformedURLException {
Path file = Paths.get(UPLOAD_DIR).resolve(filename).normalize(); // UPLOAD_DIR 경로에 filename에 접근
Resource resource = new UrlResource(file.toUri());
// inline → 브라우저에서 바로 열림
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + filename + "\"")
.body(resource);
}
}
📁 fileInfo (dto)
더보기
더보기
package com.example.Ex02.dto;
import jakarta.validation.constraints.NotBlank;
import org.springframework.web.multipart.MultipartFile;
public class FileInfo {
private int id;
@NotBlank(message = "이름을 입력하세요")
private String name;
private String filename;
private int fileSize;
public MultipartFile getFile() {
return file;
}
public void setFile(MultipartFile file) {
this.file = file;
}
private MultipartFile file; // 파일(데이터) 생성 ==> 업로드된 파일 자체가 들어오는 필드
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public int getFileSize() {
return fileSize;
}
public void setFileSize(int fileSize) {
this.fileSize = fileSize;
}
}
📁 FileMapper(mapper)
더보기
더보기
package com.example.Ex02.mapper;
import com.example.Ex02.dto.FileInfo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface FileMapper {
void saveFileInfo(FileInfo fileInfo);
List<FileInfo> getAllFiles();
}
📁 Service
더보기
더보기
package com.example.Ex02.service;
import com.example.Ex02.dto.FileInfo;
import com.example.Ex02.mapper.FileMapper;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@Service
public class FileService {
@Autowired
private FileMapper fileMapper;
// user.dir 유저 현재 프로젝트
@Value("${upload.dir:${user.dir}/uploads}")
private String uploadDir; // c:Ex09/uploads
@PostConstruct
public void init() throws IOException {
System.out.println("init");
// 폴더 자동생성
Files.createDirectories(Paths.get(uploadDir));
}
public void uploadFile(FileInfo fileInfo) throws IOException {
String name = fileInfo.getName();
MultipartFile file = fileInfo.getFile();
String name2 = file.getName();
String original = file.getOriginalFilename();
System.out.println("name :" + name); // 사용자가 작성한 이름
System.out.println("name2 :" + name2); // HTML 폼에서 <input type="file" name="file"> 의 name="file" 속성
System.out.println("original :" + original); // 사용자가 업로드한 파일의 원래 파일명
Path target = Paths.get(uploadDir, original); // 올릴경로, 올릴파일
file.transferTo(target.toFile()); // 전송 (폴더 업로드)
fileInfo.setFilename(original);
fileInfo.setFileSize((int)file.getSize());
fileMapper.saveFileInfo(fileInfo);
}
public List<FileInfo> getAllFiles() {
return fileMapper.getAllFiles();
}
}
🗂️Resources
↳ 📁FileMapper.xml
더보기
더보기
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.Ex02.mapper.FileMapper">
<!-- <resultMap id="fileResultMap" type="com.example.Ex02.dto.FileInfo">
<id property="id" column="id"></id>
<result property="name" column="name"></result>
<result property="filename" column="filename"></result>
<result property="fileSize" column="file_size"></result>
</resultMap>-->
<!--업로드된 파일 메타데이터 저장-->
<insert id="saveFileInfo" parameterType="com.example.Ex02.dto.FileInfo">
insert into file_info (id, name, filename, file_size)
values(file_seq.nextval, #{name}, #{filename}, #{fileSize})
</insert>
<!--이미지 조회-->
<select id="getAllFiles" parameterType="com.example.Ex02.dto.FileInfo">
select id, name, filename, file_size as fileSize <!--일치하지 않은 컬럼만 as 별칭-->
from file_info
order by id desc
</select>
</mapper>
🗂️templates
↳ 📁form.html
더보기
더보기
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>form</title>
</head>
<body>
form.html<br>
<h1>파일 업로드</h1>
<form th:action="@{/upload}" method="post" th:object="${fileInfo}" enctype="multipart/form-data">
<input type="text" th:field="*{name}" placeholder="이름을 입력하세요"><br>
<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}" style="color:red;"></p>
<input type="file" th:field="*{file}"><br>
<p th:if="${#fields.hasErrors('file')}" th:errors="*{file}" style="color:red;"></p>
<br>
<button type="submit">업로드</button>
</form>
message : <p th:text="${message}"></p> <!--컨트롤러 에러메시지 model로 보냄-->
<a th:href="@{/images}">업로드된 이미지 보기</a>
</body>
</html>
↳ 📁list.html
더보기
더보기
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>list</title>
</head>
<body>
list.html<br>
<table border="1">
<tr>
<th>번호</th>
<th>이름</th>
<th>파일이름</th>
<th>파일 사이즈</th>
<th>이미지 보기</th>
</tr>
<tr th:each="info:${lists}">
<td th:text="${info.id}"></td>
<td th:text="${info.name}"></td>
<td th:text="${info.filename}"></td>
<td th:text="${info.fileSize}"></td>
<!--컨트롤러에서 getMapping-->
<td><img th:src="@{'/images/' + ${info.filename}}" style="max-height:50px"></td>
</tr>
</table>
</body>
</html>
🌈 5. 코드 분석 및 작성순서
1️⃣ DTO (Data Transfer Object) 작성
- id → DB 구분자 (PK)
- name → 업로드한 사람 이름 (사용자 입력)
- filename → 저장된 파일명 (DB 저장용) 예) abc.jpg
- fileSize → 저장된 파일 크기 (DB 저장용)
- file → 실제 업로드된 파일 데이터 (폼에서 넘어옴, 서버 저장에 사용)
public class FileInfo {
private int id;
@NotBlank(message = "이름을 입력하세요")
private String name;
private String filename; // 저장될 파일명
private int fileSize; // 파일 크기
private MultipartFile file; // 실제 업로드된 파일
}
🌰 MultipartFile
: 일반적인 자바 타입이(int, String 등) 아닌, 스프링에서 제공하는 특별한 타입
private MultipartFil file;
🔽 MultipartFile이 특이한 이유
1. 스프링 전용 타입(인터페이스)
- Spring MVC가 업로드 요청을 처리할 때 자동으로 채워주는 객체
2. 폼에서 <input type="file"> 로 넘어오는 "파일" 자체를 담는 컨테이너
✅ 파일(데이터) 생성 ==> 업로드된 파일 자체가 들어오는 필드
'기초 및 언어 > ▶ Spring' 카테고리의 다른 글
| 11. Spring 쇼핑몰_파일첨부와_이미지출력 (0) | 2025.09.30 |
|---|---|
| 10. Spring Boot_fetch(중복 검사) (0) | 2025.09.28 |
| 08. Spring&Java 어노테이션 정리 (1) | 2025.09.25 |
| 07. Spring_마이바티스(MyBatis) + 페이지 설정 (0) | 2025.09.24 |
| 06. Spring_마이바티스(MyBatis) (2) | 2025.09.23 |