post-image

Hướng dẫn Spring Boot Upload/Download File từ Database

REST API

Trong hướng dẫn này, tôi sẽ hướng dẫn bạn cách upload và download file từ database bằng các API Spring Boot Rest. Tôi sử dụng interface Spring Web MultipartFile để handle các request HTTP multi-part.

1. Các API Spring Boot Rest để upload file từ database

Spring Boot Application sẽ cung cấp các API sau:

  • uploading File từ PostgreSQL/MySQL database.
  • download File database cùng với link.
  • get list File(file name, url, type, size).

Sử dụng Postman nhé mọi người:

MethodsUrlsActions
POST/uploadupload a File
GET/filesget List of Files (name, url, type, size)
GET/files/[fileId]download a File

Các file được tải lên sẽ được lưu trong bảng database của PostgreSQL / MySQL với các trường sau: id, name, type và data là kiểu BLOB(Binary Large Object là dùng để lưu trữ dữ liệu nhị phân như file, ảnh, audio hoặc video).

database
database

2. Technology

  • Java 8
  • Spring Boot 2 (with Spring Web MVC)
  • PostgreSQL/MySQL Database
  • Maven 3.6.1

3. Project Directory

project-directory
project directory

Tôi sẽ giải thích ngắn gọn như sau:

  • FileDB là model data, sẽ là 1 bảng trong database.
  • FileDBRepository sẽ extends Spring Data JpaRepository.

Đọc thêm bài viết này để hiểu hơn về Spring Data JPA nhé.

  • FileDBServiceImpl sẽ implement interface FileDBService, cái mà cung cấp các phương thức để save file và getAllfiles. Còn FileDBServiceImpl sẽ thực hiện các method của interface đó.
  • FilesController sử dụng FileDBService export các API: upload 1 file, get info list file, download file.
  • FileUploadExceptionAdvice xử lý ngoại lệ khi controller xử lý upload file…
  • ResponseFile chứa thông tin của file(name, url, type, size) cho HTTP response payload.
  • application.properties chứa cấu hình cho Servlet Multipart và kết nối cơ sở dữ liệu PostgreSQL / MySQL.
  • pom.xml cho Spring Boot, Spring Data JPA và dependency của trình kết nối PostgreSQL / MySQL.

4. Cài đặt project Spring Boot

Sử dụng tool Spring web hoặc development tool của bạn(Spring Tool Suite, Eclipse, Intellij) để tạo project. Có thể xem hướng dẫn tạo project của tôi ở đây nhé.

pom.xml:

<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>Code language: HTML, XML (xml)

Nếu bạn sử dụng MySQL thì thêm dependency này:

<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<scope>runtime</scope>
</dependency>Code language: HTML, XML (xml)

còn với PostgreSQL:

<dependency>
	<groupId>org.postgresql</groupId>
	<artifactId>postgresql</artifactId>
	<scope>runtime</scope>
</dependency>Code language: HTML, XML (xml)

5. Tạo Data Model

Data model này để lưu trữ File data, gồm những field sau:

  • id: automatically generated: UUID
  • name: name file
  • type: mime type
  • data: array of bytes, map to a BLOB

Tổng hợp 200+ tài liệu, sách, bài thực hành, video hướng dẫn lập trình… từ cơ bản đến nâng cao

entity/FileDB.java

Trong project này mình sử dụng Lombok.

package com.hoangducduy.airbnb.entity;

import lombok.Data;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;

@Data
@Entity
@Table(name = "fileDB")
public class FileDB {

    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private String id;

    private String name;

    private String type;

    @Lob
    private byte[] data;

    public FileDB(String name, String type, byte[] data) {
        this.name = name;
        this.type = type;
        this.data = data;
    }

    public FileDB() {

    }
}
Code language: JavaScript (javascript)

Trong đoạn code trên, dữ liệu được chú thích bằng chú thích @Lob. LOB là kiểu dữ liệu để lưu trữ dữ liệu đối tượng lớn. Có hai loại LOB: BLOB và CLOB:

  • BLOB dùng để lưu trữ dữ liệu nhị phân.
  • CLOB là để lưu trữ dữ liệu văn bản.

6. Tạo Repository

repository/FileDBRepository.java

package com.hoangducduy.airbnb.repository;

import com.hoangducduy.airbnb.entity.FileDB;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface FileDBRepository extends JpaRepository<FileDB, String> {
}
Code language: CSS (css)

7. Tạo Service cho File

service.FileDBService.java:

  • store (file): nhận đối tượng MultipartFile, chuyển đổi thành đối tượng FileDB và lưu nó vào database.
  • getAllFiles (): trả về tất cả các tệp được lưu trữ dưới dạng danh sách FileDB.
package com.hoangducduy.airbnb.service;

import com.hoangducduy.airbnb.entity.FileDB;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.stream.Stream;

public interface FileDBService {
    FileDB store(MultipartFile file) throws IOException;

    Stream<FileDB> getAllFiles();
}
Code language: JavaScript (javascript)

service.impl.FileDBServiceImpl.java:

package com.hoangducduy.airbnb.service.impl;

import com.hoangducduy.airbnb.entity.FileDB;
import com.hoangducduy.airbnb.repository.FileDBRepository;
import com.hoangducduy.airbnb.service.FileDBService;
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;
import java.util.stream.Stream;

@Service
public class FileDBServiceImpl implements FileDBService {

    @Autowired
    private FileDBRepository fileDBRepository;

    @Override
    public FileDB store(MultipartFile file) throws IOException {
        String fileName = StringUtils.cleanPath(file.getOriginalFilename());
        FileDB image = new FileDB(fileName, file.getContentType(), file.getBytes());

        return fileDBRepository.save(image);
    }

    @Override
    public Stream<FileDB> getAllFiles() {
        return fileDBRepository.findAll().stream();
    }
}
Code language: JavaScript (javascript)

8. Define Response Information Classes

Hãy tạo 2 class trong package response, Controller sẽ sử dụng các class này để gửi response qua HTTP.

FileDBResponse: contains nameurltypesize.

MessageRessponse: lưu trữ thông báo và thông tin message.

dto.response.FileDBResponse.java:

package com.hoangducduy.airbnb.dto.response;

import lombok.Data;

@Data
public class FileDBResponse {
    private String name;
    private String url;
    private String type;
    private long size;

    public FileDBResponse(String name, String url, String type, long size) {
        this.name = name;
        this.url = url;
        this.type = type;
        this.size = size;
    }
}
Code language: JavaScript (javascript)

dto.response.MessageRessponse.java:

package com.hoangducduy.airbnb.dto.response;

import lombok.Data;

@Data
public class MessageResponse {
    private String message;

    public MessageResponse(String message) {
        this.message = message;
    }
}
Code language: JavaScript (javascript)

9. Tạo Controller để upload và download Files từ Database

controller/FileDBController.java:

package com.hoangducduy.airbnb.controller;

import com.hoangducduy.airbnb.dto.response.FileDBResponse;
import com.hoangducduy.airbnb.dto.response.MessageResponse;
import com.hoangducduy.airbnb.entity.FileDB;
import com.hoangducduy.airbnb.repository.FileDBRepository;
import com.hoangducduy.airbnb.service.FileDBService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

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

@RestController
@RequestMapping("/api/auth")
@CrossOrigin(origins = "*", maxAge = 3600)
public class FileDBController {

    @Autowired
    private FileDBRepository fileDBRepository;

    @Autowired
    private FileDBService fileDBService;

    @PostMapping("/upload")
    public ResponseEntity<MessageResponse> uploadFile(@RequestParam("file") MultipartFile file) {
        String message = "";
        try {
            fileDBService.store(file);

            message = "Uploaded the file successfully: " + file.getOriginalFilename();
            return ResponseEntity.status(HttpStatus.OK).body(new MessageResponse(message));
        } catch (Exception e) {
            message = "Could not upload the file: " + file.getOriginalFilename() + "!";
            return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new MessageResponse(message));
        }
    }

    @GetMapping("/files")
    public ResponseEntity<List<FileDBResponse>> getListFiles() {
        List<FileDBResponse> files = fileDBService.getAllFiles().map(dbFile -> {
            String fileDownloadUri = ServletUriComponentsBuilder
                    .fromCurrentContextPath()
                    .path("/api/auth/files/")
                    .path(dbFile.getId().toString())
                    .toUriString();

            return new FileDBResponse(
                    dbFile.getName(),
                    fileDownloadUri,
                    dbFile.getType(),
                    dbFile.getData().length);
        }).collect(Collectors.toList());

        return ResponseEntity.status(HttpStatus.OK).body(files);
    }

    @GetMapping("/files/{id}")
    public ResponseEntity<byte[]> getFile(@PathVariable String id) {
        Optional<FileDB> optionalFileDB = fileDBRepository.findById(id);

        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + optionalFileDB.get().getName() + "\"")
                .body(optionalFileDB.get().getData());
    }
}Code language: JavaScript (javascript)
  • @CrossOrigin: để cho phép config origins.
  • @RestController: là một composed annotation được kết từ annotation @Controller và @ResponseBody.
  • @RequestMapping: được sử dụng để map request với class hoặc method xử lý request đó.

Chúng ta sử dụng @Autowired để inject implementation của bean FileDBService vào biến local.

10. Configure Spring Datasource, JPA, Hibernate

application.properties đối với MySQL:

spring.datasource.url= jdbc:mysql://localhost:3306/testdb?useSSL=false
spring.datasource.username= root
spring.datasource.password= 123456

spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQL5InnoDBDialect

# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto= updateCode language: PHP (php)

còn với PostgreSQL:

spring.datasource.url= jdbc:postgresql://localhost:5432/testdb
spring.datasource.username= postgres
spring.datasource.password= 123

spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation= true
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.PostgreSQLDialect

# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto= updateCode language: PHP (php)
  • spring.datasource.username & spring.datasource.password: thuộc tính cài đặt database.
  • Spring Boot sử dụng Hibernate cho JPA implementation, tôi configure MySQL5InnoDBDialect cho MySQL hoặc PostgreSQLDialect cho PostgreSQL
  • spring.jpa.hibernate.ddl-auto là để tự động khởi tạo database. Tôi set value là update để một bảng có thể tự động tạo cơ sở dữ liệu tương ứng với model data đã được define. Bất kỳ thay đổi nào đối với model cũng sẽ update lại database. Đối với sản xuất thì thuộc tính này nên được validate.

11. Cấu Multipart File cho Servlet

application.properties:

spring.servlet.multipart.max-file-size=2MB
spring.servlet.multipart.max-request-size=2MB
  • spring.servlet.multipart.max-file-size: max size cho mỗi request.
  • spring.servlet.multipart.max-request-size: max request size cho 1 multipart/form-data.

12. Handle File Upload Exception

exception/FileUploadExceptionAdvice.java

package com.hoangducduy.airbnb.exception;

import com.hoangducduy.airbnb.dto.response.MessageResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice
public class FileUploadExceptionAdvice extends ResponseEntityExceptionHandler {

    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public ResponseEntity<MessageResponse> handleMaxSizeException(MaxUploadSizeExceededException exc) {
        return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new MessageResponse("File too large!"));
    }

}
Code language: CSS (css)

13. Run và Test

Chạy câu lệnh sau để run Spring Boot application:

mvn spring-boot:runCode language: CSS (css)

Chúng ta sử dụng Postman để thực hiện các request này.

  • upload file:
  • upload với file lớn hơn 2MB:

Kiểm tra file trong database nhé:

  • get toàn bộ file đã upload:

Bây giờ chúng ta có thể download file bằng url kia nhé.

Ví dụ: http://localhost:8080/api/auth/files/3fd01a70-9429-4233-9bc8-d1b3c4cd5a85

Kết Luận

Trong bài viết này, chúng ta đã học cách tạo được ứng dụng Spring Boot để upload file và get toàn bộ file ra qua Restful API.

Source code ở đây nhé: Github.

Tham khảo các bài viết khác:

https://hocspringboot.net/2021/08/03/gioi-thieu-ve-spring-data-jpa/

https://hocspringboot.net/2021/08/01/spring-data-jpa/

Tham khảo khóa học lập trình tại: https://codegym.vn/

Leave a Reply

Your email address will not be published. Required fields are marked *