diff --git a/Readme.md b/Readme.md index e66b5bf..a6b3318 100644 --- a/Readme.md +++ b/Readme.md @@ -2,6 +2,8 @@ **Tutorial**: [Uploading an Downloading files with Spring Boot](https://www.callicoder.com/spring-boot-file-upload-download-rest-api-example/) +> **This branch demonstrates how to store the files in MySQL database.** + ## Steps to Setup **1. Clone the repository** @@ -10,7 +12,17 @@ git clone https://github.com/callicoder/spring-boot-file-upload-download-rest-api-example.git ``` -**2. Run the app using maven** +**2. Configure MySQL database** + +Create a MySQL database named `file_demo`, and change the username and password in `src/main/resources/application.properties` as per your MySQL +installation - + +```properties +spring.datasource.username= +spring.datasource.password= +``` + +**3. Run the app using maven** ```bash cd spring-boot-file-upload-download-rest-api-example diff --git a/pom.xml b/pom.xml index 8d3bbf6..3d1c600 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ org.springframework.boot spring-boot-starter-parent - 2.0.0.RELEASE + 2.2.1.RELEASE @@ -30,6 +30,17 @@ spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-data-jpa + + + + mysql + mysql-connector-java + runtime + + org.springframework.boot spring-boot-starter-test diff --git a/src/main/java/com/example/filedemo/FileDemoApplication.java b/src/main/java/com/example/filedemo/FileDemoApplication.java index 0d0ce38..2bff3ec 100644 --- a/src/main/java/com/example/filedemo/FileDemoApplication.java +++ b/src/main/java/com/example/filedemo/FileDemoApplication.java @@ -1,14 +1,9 @@ package com.example.filedemo; -import com.example.filedemo.property.FileStorageProperties; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.context.properties.EnableConfigurationProperties; @SpringBootApplication -@EnableConfigurationProperties({ - FileStorageProperties.class -}) public class FileDemoApplication { public static void main(String[] args) { diff --git a/src/main/java/com/example/filedemo/controller/FileController.java b/src/main/java/com/example/filedemo/controller/FileController.java index 8f7f227..2767803 100644 --- a/src/main/java/com/example/filedemo/controller/FileController.java +++ b/src/main/java/com/example/filedemo/controller/FileController.java @@ -1,10 +1,12 @@ package com.example.filedemo.controller; +import com.example.filedemo.model.DBFile; import com.example.filedemo.payload.UploadFileResponse; -import com.example.filedemo.service.FileStorageService; +import com.example.filedemo.service.DBFileStorageService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -13,8 +15,6 @@ import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -25,18 +25,18 @@ public class FileController { private static final Logger logger = LoggerFactory.getLogger(FileController.class); @Autowired - private FileStorageService fileStorageService; + private DBFileStorageService dbFileStorageService; @PostMapping("/uploadFile") public UploadFileResponse uploadFile(@RequestParam("file") MultipartFile file) { - String fileName = fileStorageService.storeFile(file); + DBFile dbFile = dbFileStorageService.storeFile(file); String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath() .path("/downloadFile/") - .path(fileName) + .path(dbFile.getId()) .toUriString(); - return new UploadFileResponse(fileName, fileDownloadUri, + return new UploadFileResponse(dbFile.getFileName(), fileDownloadUri, file.getContentType(), file.getSize()); } @@ -48,28 +48,15 @@ public List uploadMultipleFiles(@RequestParam("files") Multi .collect(Collectors.toList()); } - @GetMapping("/downloadFile/{fileName:.+}") - public ResponseEntity downloadFile(@PathVariable String fileName, HttpServletRequest request) { - // Load file as Resource - Resource resource = fileStorageService.loadFileAsResource(fileName); - - // Try to determine file's content type - String contentType = null; - try { - contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath()); - } catch (IOException ex) { - logger.info("Could not determine file type."); - } - - // Fallback to the default content type if type could not be determined - if(contentType == null) { - contentType = "application/octet-stream"; - } + @GetMapping("/downloadFile/{fileId}") + public ResponseEntity downloadFile(@PathVariable String fileId) { + // Load file from database + DBFile dbFile = dbFileStorageService.getFile(fileId); return ResponseEntity.ok() - .contentType(MediaType.parseMediaType(contentType)) - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"") - .body(resource); + .contentType(MediaType.parseMediaType(dbFile.getFileType())) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + dbFile.getFileName() + "\"") + .body(new ByteArrayResource(dbFile.getData())); } } diff --git a/src/main/java/com/example/filedemo/model/DBFile.java b/src/main/java/com/example/filedemo/model/DBFile.java new file mode 100644 index 0000000..725bfa3 --- /dev/null +++ b/src/main/java/com/example/filedemo/model/DBFile.java @@ -0,0 +1,63 @@ +package com.example.filedemo.model; + +import org.hibernate.annotations.GenericGenerator; + +import javax.persistence.*; + +@Entity +@Table(name = "files") +public class DBFile { + @Id + @GeneratedValue(generator = "uuid") + @GenericGenerator(name = "uuid", strategy = "uuid2") + private String id; + + private String fileName; + + private String fileType; + + @Lob + private byte[] data; + + public DBFile() { + + } + + public DBFile(String fileName, String fileType, byte[] data) { + this.fileName = fileName; + this.fileType = fileType; + this.data = data; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getFileType() { + return fileType; + } + + public void setFileType(String fileType) { + this.fileType = fileType; + } + + public byte[] getData() { + return data; + } + + public void setData(byte[] data) { + this.data = data; + } +} diff --git a/src/main/java/com/example/filedemo/property/FileStorageProperties.java b/src/main/java/com/example/filedemo/property/FileStorageProperties.java deleted file mode 100644 index 57ac340..0000000 --- a/src/main/java/com/example/filedemo/property/FileStorageProperties.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.example.filedemo.property; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "file") -public class FileStorageProperties { - private String uploadDir; - - public String getUploadDir() { - return uploadDir; - } - - public void setUploadDir(String uploadDir) { - this.uploadDir = uploadDir; - } -} diff --git a/src/main/java/com/example/filedemo/repository/DBFileRepository.java b/src/main/java/com/example/filedemo/repository/DBFileRepository.java new file mode 100644 index 0000000..63f9386 --- /dev/null +++ b/src/main/java/com/example/filedemo/repository/DBFileRepository.java @@ -0,0 +1,10 @@ +package com.example.filedemo.repository; + +import com.example.filedemo.model.DBFile; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface DBFileRepository extends JpaRepository { + +} diff --git a/src/main/java/com/example/filedemo/service/DBFileStorageService.java b/src/main/java/com/example/filedemo/service/DBFileStorageService.java new file mode 100644 index 0000000..c993986 --- /dev/null +++ b/src/main/java/com/example/filedemo/service/DBFileStorageService.java @@ -0,0 +1,41 @@ +package com.example.filedemo.service; + +import com.example.filedemo.exception.FileStorageException; +import com.example.filedemo.exception.MyFileNotFoundException; +import com.example.filedemo.model.DBFile; +import com.example.filedemo.repository.DBFileRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; + +@Service +public class DBFileStorageService { + + @Autowired + private DBFileRepository dbFileRepository; + + public DBFile storeFile(MultipartFile file) { + // Normalize file name + String fileName = StringUtils.cleanPath(file.getOriginalFilename()); + + try { + // Check if the file's name contains invalid characters + if(fileName.contains("..")) { + throw new FileStorageException("Sorry! Filename contains invalid path sequence " + fileName); + } + + DBFile dbFile = new DBFile(fileName, file.getContentType(), file.getBytes()); + + return dbFileRepository.save(dbFile); + } catch (IOException ex) { + throw new FileStorageException("Could not store file " + fileName + ". Please try again!", ex); + } + } + + public DBFile getFile(String fileId) { + return dbFileRepository.findById(fileId) + .orElseThrow(() -> new MyFileNotFoundException("File not found with id " + fileId)); + } +} diff --git a/src/main/java/com/example/filedemo/service/FileStorageService.java b/src/main/java/com/example/filedemo/service/FileStorageService.java deleted file mode 100644 index c3b6e2c..0000000 --- a/src/main/java/com/example/filedemo/service/FileStorageService.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.example.filedemo.service; - -import com.example.filedemo.exception.FileStorageException; -import com.example.filedemo.exception.MyFileNotFoundException; -import com.example.filedemo.property.FileStorageProperties; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.Resource; -import org.springframework.core.io.UrlResource; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; -import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; -import java.net.MalformedURLException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; - -@Service -public class FileStorageService { - - private final Path fileStorageLocation; - - @Autowired - public FileStorageService(FileStorageProperties fileStorageProperties) { - this.fileStorageLocation = Paths.get(fileStorageProperties.getUploadDir()) - .toAbsolutePath().normalize(); - - try { - Files.createDirectories(this.fileStorageLocation); - } catch (Exception ex) { - throw new FileStorageException("Could not create the directory where the uploaded files will be stored.", ex); - } - } - - public String storeFile(MultipartFile file) { - // Normalize file name - String fileName = StringUtils.cleanPath(file.getOriginalFilename()); - - try { - // Check if the file's name contains invalid characters - if(fileName.contains("..")) { - throw new FileStorageException("Sorry! Filename contains invalid path sequence " + fileName); - } - - // Copy file to the target location (Replacing existing file with the same name) - Path targetLocation = this.fileStorageLocation.resolve(fileName); - Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING); - - return fileName; - } catch (IOException ex) { - throw new FileStorageException("Could not store file " + fileName + ". Please try again!", ex); - } - } - - public Resource loadFileAsResource(String fileName) { - try { - Path filePath = this.fileStorageLocation.resolve(fileName).normalize(); - Resource resource = new UrlResource(filePath.toUri()); - if(resource.exists()) { - return resource; - } else { - throw new MyFileNotFoundException("File not found " + fileName); - } - } catch (MalformedURLException ex) { - throw new MyFileNotFoundException("File not found " + fileName, ex); - } - } -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 885382d..2cc234e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,18 @@ +## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) +spring.datasource.url= jdbc:mysql://localhost:3306/file_demo?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false +spring.datasource.username= root +spring.datasource.password= callicoder + +## Hibernate Properties + +# The SQL dialect makes Hibernate generate better SQL for the chosen database +spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect +spring.jpa.hibernate.ddl-auto = update + +## Hibernate Logging +logging.level.org.hibernate.SQL= DEBUG + + ## MULTIPART (MultipartProperties) # Enable multipart uploads spring.servlet.multipart.enabled=true @@ -8,5 +23,3 @@ spring.servlet.multipart.max-file-size=200MB # Max Request Size spring.servlet.multipart.max-request-size=215MB -## File Storage Properties -file.upload-dir=./uploads