In the tutorial, we show you how to build a SpringBoot RestAPIs to upload multiple files to MySQL database with Thymeleaf frontend, and Spring JPA Audit feature.
Related posts:
– Spring Data JPA Auditing Example – SpringBoot RestAPIs + MySQL
– SpringBoot RestAPIs + Thymeleaf – Upload/Download Multiple Files to FileSystem Example
Technologies
– Java 8
– Maven 3.6.1
– SpringBoot
– Thymeleaf
– Bootstrap 4
Goal
– We create a SpringBoot project as below:
– Upload Multiple Files ->
– Download Form ->
Practice
SpringBoot Backend Implementation
Create SpringBoot project
We use SpringToolSuite to create a SpringBoot project with below dependencies:
org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-jpa mysql mysql-connector-java runtime com.fasterxml.jackson.datatype jackson-datatype-jsr310
– jackson-datatype-jsr310
is used to support Java 8 Date/Time for JPA Audit feature.
Implement DateAudit
– Configure JPA Auditing:
package com.ozenero.uploadmultifiles.config; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @Configuration @EnableJpaAuditing public class JpaAuditingConfig {}
– Implement DateAudit
->
package com.ozenero.uploadmultifiles.model.audit; import java.time.Instant; import javax.persistence.Column; import javax.persistence.EntityListeners; import javax.persistence.MappedSuperclass; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @MappedSuperclass @EntityListeners(AuditingEntityListener.class) @JsonIgnoreProperties( value = {"createdAt", "updatedAt"}, allowGetters = true ) public abstract class DateAudit { @CreatedDate @Column(nullable = false, updatable = false) private Instant createdAt; @LastModifiedDate @Column(nullable = false) private Instant updatedAt; public Instant getCreatedAt() { return createdAt; } public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; } public Instant getUpdatedAt() { return updatedAt; } public void setUpdatedAt(Instant updatedAt) { this.updatedAt = updatedAt; } }
– @EntityListeners
annotation is used to specify callback listener classes. We use the JPA entity listener class of Spring Data AuditingEntityListener
.
– @MappedSuperClass
annotation is used to move the properties to a base class DateAudit which would be extended by all your audited entities.
Build File Model
– FileModel.java
->
package com.ozenero.uploadmultifiles.model; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Lob; import javax.persistence.Table; import com.ozenero.uploadmultifiles.model.audit.DateAudit; @Entity @Table(name="file_model") public class FileModel extends DateAudit { @Id @GeneratedValue @Column(name = "id") private Long id; @Column(name = "name") private String name; @Column(name = "mimetype") private String mimetype; @Lob @Column(name="pic") private byte[] pic; public FileModel(){} public FileModel(String name, String mimetype, byte[] pic){ this.name = name; this.mimetype = mimetype; this.pic = pic; } public Long getId(){ return this.id; } public void setId(Long id){ this.id = id; } public String getName(){ return this.name; } public void setName(String name){ this.name = name; } public String getMimetype(){ return this.mimetype; } public void setMimetype(String mimetype){ this.mimetype = mimetype; } public byte[] getPic(){ return this.pic; } public void setPic(byte[] pic){ this.pic = pic; } }
Define File Repository
– FileRepository.java
->
package com.ozenero.uploadmultifiles.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.transaction.annotation.Transactional; import com.ozenero.uploadmultifiles.model.FileModel; @Transactional public interface FileRepository extends JpaRepository{ public FileModel findByName(String name); }
Upload Multiple Files Controller
– UploadFileController.java
->
package com.ozenero.uploadmultifiles.controller; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; import com.ozenero.uploadmultifiles.model.FileModel; import com.ozenero.uploadmultifiles.repository.FileRepository; @Controller public class UploadFileController { @Autowired FileRepository fileRepository; @GetMapping("/") public String index() { return "uploadform"; } @PostMapping("/") public String uploadMultipartFile(@RequestParam("files") MultipartFile[] files, Model model) { ListfileNames = new ArrayList (); try { List storedFile = new ArrayList (); for(MultipartFile file: files) { FileModel fileModel = fileRepository.findByName(file.getOriginalFilename()); if(fileModel != null) { // update new contents fileModel.setPic(file.getBytes()); }else { fileModel = new FileModel(file.getOriginalFilename(), file.getContentType(), file.getBytes()); } fileNames.add(file.getOriginalFilename()); storedFile.add(fileModel); } // Save all Files to database fileRepository.saveAll(storedFile); model.addAttribute("message", "Files uploaded successfully!"); model.addAttribute("files", fileNames); } catch (Exception e) { model.addAttribute("message", "Fail!"); model.addAttribute("files", fileNames); } return "uploadform"; } }
– We can limit the size of uploaded file by using below configuration in application.properties
->
spring.servlet.multipart.max-file-size=500KB spring.servlet.multipart.max-request-size=500KB
Download Files Controller
– Firstly, create FileInfo.java
->
package com.ozenero.uploadmultifiles.model; public class FileInfo { private String filename; private String url; public FileInfo(String filename, String url) { this.filename = filename; this.url = url; } public String getFilename() { return this.filename; } public void setFilename(String filename) { this.filename = filename; } public String getUrl() { return this.url; } public void setUrl(String url) { this.url = url; } }
– Implement download controller DownloadFileController.java
->
package com.ozenero.uploadmultifiles.controller; import java.util.List; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import com.ozenero.uploadmultifiles.model.FileInfo; import com.ozenero.uploadmultifiles.model.FileModel; import com.ozenero.uploadmultifiles.repository.FileRepository; @Controller public class DownloadFileController { @Autowired FileRepository fileRepository; /* * Retrieve Files' Information */ @GetMapping("/files") public String getListFiles(Model model) { ListfileInfos = fileRepository.findAll().stream().map( fileModel -> { String filename = fileModel.getName(); String url = MvcUriComponentsBuilder.fromMethodName(DownloadFileController.class, "downloadFile", fileModel.getName().toString()).build().toString(); return new FileInfo(filename, url); } ) .collect(Collectors.toList()); model.addAttribute("files", fileInfos); return "listfiles"; } /* * Download Files */ @GetMapping("/files/{filename}") public ResponseEntity downloadFile(@PathVariable String filename) { FileModel file = fileRepository.findByName(filename); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getName() + "\"") .body(file.getPic()); } }
Application Configuration
– application.properties
->
spring.datasource.url=jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false spring.datasource.username=root spring.datasource.password=12345 spring.jpa.generate-ddl=true spring.jpa.hibernate.ddl-auto=create-drop spring.servlet.multipart.max-file-size=500KB spring.servlet.multipart.max-request-size=500KB ## Hibernate Logging logging.level.org.hibernate.SQL= DEBUG ## Jackson Properties spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS= false spring.jackson.time-zone= UTC
Main Class Implementation
– Set the default timezone for our application to UTC
.
package com.ozenero.uploadmultifiles; import java.util.TimeZone; import javax.annotation.PostConstruct; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters; import org.springframework.transaction.annotation.EnableTransactionManagement; @SpringBootApplication @EntityScan(basePackageClasses = { SpringBootUploadMultiFileToMySqlApplication.class, Jsr310JpaConverters.class }) @EnableTransactionManagement public class SpringBootUploadMultiFileToMySqlApplication { @PostConstruct void init() { TimeZone.setDefault(TimeZone.getTimeZone("UTC")); } public static void main(String[] args) { SpringApplication.run(SpringBootUploadMultiFileToMySqlApplication.class, args); } }
Thymeleaf Frontend Implementation
Upload Form
– uploadform.html
->
Upload Multiple Files Upload Multiple Files to MySQL
Download Form
– listfiles.html
->
Upload Multiple Files to MySQL
Run & Check Results
Run the SpringBoot project,
– Upload Multiples files:
-> Results:
– Change text in ozenero-about-us.txt
then upload 3 files again, results ->
– Download files:
-> results:
SourceCode
SpringBootUploadMultiFileToMySQL