Upload plików z użyciem Spring Boot i bazy danych MySQL
Upload plików z użyciem Spring Boot i bazy danych MySQL
W tym wpisie dowiesz się w jaki sposób za pomocą Spring Boota i bazy MySQL zaprojektować aplikację do przechowywania plików w bazie danych. Pliki będą dodawane asynchronicznie czyli bez konieczności przeładowania strony. Wpis bazuje na projekcie – http://www.callicoder.com/spring-boot-file-upload-download-jpa-hibernate-mysql-database-example/ gdzie dodatkowo opisane jest w jaki sposób dodawać wiele plików jednocześnie (paczkami). Do dzieła – tworzymy nowy projekt Spring Boot – plik pom.xml – niezbędne zależności:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies>
Konfigurujemy połączenie do bazy danych MySQL oraz reguły dotyczące rozmiaru przesyłanych plików:
spring.datasource.url = spring.datasource.username = spring.datasource.password = spring.jpa.show-sql = true spring.jpa.database-platform = org.hibernate.dialect.MySQL5InnoDBDialect spring.jpa.hibernate.ddl-auto = create ## Hibernate Logging logging.level.org.hibernate.SQL = DEBUG ## MULTIPART (MultipartProperties) # Enable multipart uploads spring.servlet.multipart.enabled = true # Threshold after which files are written to disk. spring.servlet.multipart.file-size-threshold = 2KB # Max file size. spring.servlet.multipart.max-file-size = 200MB # Max Request Size spring.servlet.multipart.max-request-size = 215MB
dla spring boota w wersji 1.5:
spring: http: multipart: max-file-size: 200MB max-request-size: 215MB
Warto dodatkowo z poziomu bazy danych MySQL ustawić parametr który zwiększy domyślny rozmiar pakietu przesyłanych danych:
SET GLOBAL max_allowed_packet=1073741824;
Tworzymy model – plik DBFile – korzystając z biblioteki Lombok:
@Entity @Table(name = "files") @Getter @Setter 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; } }
Model dla odpowiedzi – po przesłaniu pliku:
@Getter @Setter @AllArgsConstructor public class UploadFileResponse { private String fileName; private String fileDownloadUri; private String fileType; private long size; }
Repozytorium do operacji na bazie danych:
@Repository public interface DBFileRepository extends JpaRepository<DBFile, String> { }
Teraz cza na serwis:
@Service public class DBFileStorageService { @Autowired private DBFileRepository dbFileRepository; public DBFile storeFile(MultipartFile file) { String fileName = StringUtils.cleanPath(file.getOriginalFilename()); try { 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)); } }
W powyższym serwisie wykorzystywane są dwie klasy wyjątków:
- Błąd zapisu – FileStorageException:
public class FileStorageException extends RuntimeException { public FileStorageException(String message) { super(message); } public FileStorageException(String message, Throwable cause) { super(message, cause); } }
- Plik nie istnieje – MyFileNotFoundException:
@ResponseStatus(httptatus.NOT_FOUND) public class MyFileNotFoundException extends RuntimeException { public MyFileNotFoundException(String message) { super(message); } public MyFileNotFoundException(String message, Throwable cause) { super(message, cause); } }
RestController w którym definiujemy metody odpowiednio do zapisu i odczytu pliku:
@RestController public class FileController { private static final Logger logger = LoggerFactory.getLogger(FileController.class); @Autowired private pl.javaleader.fileupload.service.DBFileStorageService DBFileStorageService; @PostMapping("/uploadFile") public UploadFileResponse uploadFile(@RequestParam("file") MultipartFile file) { DBFile dbFile = DBFileStorageService.storeFile(file); String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath() .path("/downloadFile/") .path(dbFile.getId()) .toUriString(); return new UploadFileResponse(dbFile.getFileName(), fileDownloadUri, file.getContentType(), file.getSize()); } @GetMapping("/downloadFile/{fileId}") public ResponseEntity<Resource> downloadFile(@PathVariable String fileId) { DBFile dbFile = DBFileStorageService.getFile(fileId); return ResponseEntity.ok() .contentType(MediaType.parseMediaType(dbFile.getFileType())) .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + dbFile.getFileName() + "\"") .body(new ByteArrayResource(dbFile.getData())); } }
Plik JS w którym za pomocą asynchronicznego obiektu JavaScript – XMLHttpRequest dokonywany jest zapis wybranego przez użytkownika pliku. Tworzony jest również adres URL za pomocą którego zapisany plik może zostać pobrany na dysk:
'use strict'; var singleUploadForm = document.querySelector('#singleUploadForm'); var singleFileUploadInput = document.querySelector('#singleFileUploadInput'); var singleFileUploadError = document.querySelector('#singleFileUploadError'); var singleFileUploadSuccess = document.querySelector('#singleFileUploadSuccess'); function uploadSingleFile(file) { var formData = new FormData(); formData.append("file", file); var xhr = new XMLHttpRequest(); xhr.open("POST", "/uploadFile"); xhr.onload = function() { var response = JSON.parse(xhr.responseText); if(xhr.status == 200) { singleFileUploadError.style.display = "none"; singleFileUploadSuccess.innerHTML = "<p>File uploaded successfully.</p><p>download : <a href='" + response.fileDownloadUri + "' target='_blank'>" + response.fileDownloadUri + "</a></p>"; singleFileUploadSuccess.style.display = "block"; } else { singleFileUploadSuccess.style.display = "none"; singleFileUploadError.innerHTML = (response.message) || "an error occurred"; } } xhr.send(formData); } singleUploadForm.addEventListener('submit', function(event){ var files = singleFileUploadInput.files; if(files.length === 0) { singleFileUploadError.innerHTML = "Please select a file"; singleFileUploadError.style.display = "block"; } uploadSingleFile(files[0]); event.preventDefault(); }, true);
Plik widoku – index.html:
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0"> <title>JavaLeader.pl - upload files using MySQL</title> <link rel="stylesheet" href="/css/main.css" /> </head> <body> <noscript> <h2>JS support error</h2> </noscript> <div class="upload-container"> <div class="upload-header"> <h2>JavaLeader.pl - upload files using MySQL</h2> </div> <div class="upload-content"> <div class="single-upload"> <h3>Upload Single File</h3> <form id="singleUploadForm" name="singleUploadForm"> <input id="singleFileUploadInput" type="file" name="file" class="file-input" required /> <button type="submit" class="primary submit-btn">Submit</button> </form> <div class="upload-response"> <div id="singleFileUploadError"></div> <div id="singleFileUploadSuccess"></div> </div> </div> </div> </div> <script src="/js/main.js" ></script> </body> </html>
Aplikacja po dodaniu pliku stylów:
wynik z bazy danych:
Leave a comment