Spring Boot File Upload / Download with Hibernate MySQL Database

In this tutorial, we will learn how to upload and download files in a Restful spring boot web service. The files will be stored in a MySQL database.
In my previous tutorial at Spring boot upload/download Rest API tutorial, I have shown you how to upload files and store them in the local filesystem.
The source code of this tutorial available on my GitHub Repository. 

1. Tools and Technologies Used

  • Spring Boot - 2.1.0.RELEASE
  • Hibernate - 5.0.4.Final
  • JDK - 1.8 or later
  • Spring Framework - 5.1.2 RELEASE
  • Maven - 3.2+
  • IDE - Eclipse or Spring Tool Suite (STS)
  • Database - MySQL

2. Create a Spring Boot Application

There are many ways to create a Spring Boot application. You can refer below articles to create a Spring Boot application.

3. Packaging Structure

Following is the directory structure of the complete application for your reference - 

4. The pom.xml File

<?xml version="1.0" encoding="UTF-8"?>
<project
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>net.javaguides.springboot</groupId>
    <artifactId>springboot-upload-download-file-rest-api-example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>springboot-upload-download-file-rest-api-example</name>
    <description>Demo project for Spring Boot</description>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
        <relativePath />
        <!-- lookup parent from repository -->
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>
    <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.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

5. Configuring the Database and Multipart File properties

Next, we need to configure the MySQL database url, username, and password. You can configure that in the src/main/resources/application.properties file -
spring.datasource.url= jdbc:mysql://localhost:3306/springboot_demo?useSSL=false
spring.datasource.username= root
spring.datasource.password= root

## 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
# 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

6. Create JPA Entity - DatabaseFile

Let’s create a DatabaseFile entity to model the file attributes that will be stored in the database:
package net.javaguides.springboot.fileuploaddownload.model;

import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;

@Entity
@Table(name = "files")
public class DatabaseFile {
    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private String id;

    private String fileName;

    private String fileType;

    @Lob
    private byte[] data;

    public DatabaseFile() {

    }

    public DatabaseFile(String fileName, String fileType, byte[] data) {
        this.fileName = fileName;
        this.fileType = fileType;
        this.data = data;
    }

    public String getId() {
        return id;
    }

    public String getFileName() {
        return fileName;
    }

    public String getFileType() {
        return fileType;
    }

    public byte[] getData() {
        return data;
    }

    public void setId(String id) {
        this.id = id;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public void setFileType(String fileType) {
        this.fileType = fileType;
    }

    public void setData(byte[] data) {
        this.data = data;
    }
}
Note that, the file’s contents will be stored as a byte array in the database.

7. Create a Spring Data Repository - DatabaseFileRepository.java

Next, we need to create a repository to save files in the database and retrieve them back:
package net.javaguides.springboot.fileuploaddownload.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import net.javaguides.springboot.fileuploaddownload.model.DatabaseFile;

@Repository
public interface DatabaseFileRepository extends JpaRepository<DatabaseFile, String> {

}

8. Service Layer - DatabaseFileService

The following DatabaseFileService contains methods to store and retrieve files to/from the database:
package net.javaguides.springboot.fileuploaddownload.service;

import java.io.IOException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import net.javaguides.springboot.fileuploaddownload.exception.FileNotFoundException;
import net.javaguides.springboot.fileuploaddownload.exception.FileStorageException;
import net.javaguides.springboot.fileuploaddownload.model.DatabaseFile;
import net.javaguides.springboot.fileuploaddownload.repository.DatabaseFileRepository;

@Service
public class DatabaseFileService {

    @Autowired
    private DatabaseFileRepository dbFileRepository;

    public DatabaseFile 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);
            }

            DatabaseFile dbFile = new DatabaseFile(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 DatabaseFile getFile(String fileId) {
        return dbFileRepository.findById(fileId)
            .orElseThrow(() - > new FileNotFoundException("File not found with id " + fileId));
    }
}

9. Writing APIs for File Upload and Download

File Upload Rest API

Let’s now write the REST APIs for uploading single as well as multiple files. Create a new controller class called FileUploadController inside net.javaguides.springboot.fileuploaddownload.controller package and add following code to it -
package net.javaguides.springboot.fileuploaddownload.controller;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import net.javaguides.springboot.fileuploaddownload.model.DatabaseFile;
import net.javaguides.springboot.fileuploaddownload.payload.Response;
import net.javaguides.springboot.fileuploaddownload.service.DatabaseFileService;

@RestController
public class FileUploadController {

    @Autowired
    private DatabaseFileService fileStorageService;

    @PostMapping("/uploadFile")
    public Response uploadFile(@RequestParam("file") MultipartFile file) {
        DatabaseFile fileName = fileStorageService.storeFile(file);

        String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
            .path("/downloadFile/")
            .path(fileName.getFileName())
            .toUriString();

        return new Response(fileName.getFileName(), fileDownloadUri,
            file.getContentType(), file.getSize());
    }

    @PostMapping("/uploadMultipleFiles")
    public List < Response > uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) {
        return Arrays.asList(files)
            .stream()
            .map(file -> uploadFile(file))
            .collect(Collectors.toList());
    }
}

File Download Rest API

Let’s now write the REST API for downloading a file. Create a new controller class called FileDownloadController inside net.javaguides.springboot.fileuploaddownload.controller package and add following code to it -
package net.javaguides.springboot.fileuploaddownload.controller;

import javax.servlet.http.HttpServletRequest;

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;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import net.javaguides.springboot.fileuploaddownload.model.DatabaseFile;
import net.javaguides.springboot.fileuploaddownload.service.DatabaseFileService;

@RestController
public class FileDownloadController {

    @Autowired
    private DatabaseFileService fileStorageService;

    @GetMapping("/downloadFile/{fileName:.+}")
    public ResponseEntity < Resource > downloadFile(@PathVariable String fileName, HttpServletRequest request) {
        // Load file as Resource
        DatabaseFile databaseFile = fileStorageService.getFile(fileName);

        return ResponseEntity.ok()
            .contentType(MediaType.parseMediaType(databaseFile.getFileType()))
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + databaseFile.getFileName() + "\"")
            .body(new ByteArrayResource(databaseFile.getData()));
    }
}

10. Payloads - Response.java

This Response class used to return response from the /uploadFile and /uploadMultipleFiles APIs. Create Response class inside net.javaguides.springboot.fileuploaddownload.payload package with the following contents -
package net.javaguides.springboot.fileuploaddownload.payload;


public class Response {
    private String fileName;
    private String fileDownloadUri;
    private String fileType;
    private long size;

    public Response(String fileName, String fileDownloadUri, String fileType, long size) {
        this.fileName = fileName;
        this.fileDownloadUri = fileDownloadUri;
        this.fileType = fileType;
        this.size = size;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public String getFileDownloadUri() {
        return fileDownloadUri;
    }

    public void setFileDownloadUri(String fileDownloadUri) {
        this.fileDownloadUri = fileDownloadUri;
    }

    public String getFileType() {
        return fileType;
    }

    public void setFileType(String fileType) {
        this.fileType = fileType;
    }

    public long getSize() {
        return size;
    }

    public void setSize(long size) {
        this.size = size;
    }
}

11. Custom Exception Classes

The DatabaseFileService class throws some exceptions in case of unexpected situations. Following are the definitions of those exception classes (All the exception classes go inside the package net.javaguides.springboot.fileuploaddownload.exception).

FileNotFoundException

package net.javaguides.springboot.fileuploaddownload.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class FileNotFoundException extends RuntimeException {

    private static final long serialVersionUID = 1 L;

    public FileNotFoundException(String message) {
        super(message);
    }

    public FileNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
}

FileStorageException

package net.javaguides.springboot.fileuploaddownload.exception;

public class FileStorageException extends RuntimeException {

    private static final long serialVersionUID = 1 L;

    public FileStorageException(String message) {
        super(message);
    }

    public FileStorageException(String message, Throwable cause) {
        super(message, cause);
    }
}

11. Running the Application and Testing the APIs via Postman

We’re done developing our backend APIs. Let’s run the application and test the APIs via Postman.
This spring boot application has an entry point Java class called SpringbootUploadDownloadFileApplication.java with the public static void main(String[] args) method, which you can run to start the application.
import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootUploadDownloadFileApplication {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
The main() method uses Spring Boot’s SpringApplication.run() method to launch an application.
Or type the following command to run the application -
mvn spring-boot:run
Once started, the application can be accessed at http://localhost:8080

12. Testing the APIs via Postman

Upload Single File Demo

Upload Multiple File Demo

Download File Demo


The source code of this tutorial available on my GitHub Repository.

Comments