{getToc} $title={Table of Contents}
SPRING DATA: UPLOAD FILE POSTGRES DB DATA STORE |
Introduction
In this blog, I will do everything from scratch, such as build Spring Data JDBC as maven project to upload file and store in Postgres Database, Create Postgres Database with new server, new schema and create table to store file from Spring Data project in Postgres Database.1. Set up Spring Maven Project with Dependencies
Spring Jar Libraries:
- Java: 11
- Spring-boot-2.7.3
- Spring-boot-starter-data-jdbc-2.7.3
- Spring-jdbc-5.3.22
- Lombok-1.18.24
PostgreSQL Jar Libraries:
- Postgresql-42.3.6
Postgres Install on OS:
- Desktop pgAdmin: 6.12
- Postgresql-14.5-1-windows-x64 (Download)
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.3</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>upload-file-spring-data-jdbc</artifactId> <version>0.0.1-SNAPSHOT</version> <name>upload-file-spring-data-jdbc</name> <description>Demo project for Spring Boot</description> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
2. Prepare to check & create Schema, Table
In project package /upload-file-spring-data-jdbc/src/main/resources.
I have create schema.sql file and create script to check and
create schema & and table when project is start up.
- Schema: schema_uploadfile
- Table: tbl_uploadfile : to store updated file with file man, file type and file data as byte
create table if not exists schema_uploadfile.tbl_uploadfile( id serial primary key, file_name varchar(255) unique, file_type varchar(30), grp_data bytea );
3. Set Postgres Database Connection in Project
In project package /upload-file-spring-data-jdbc/src/main/resources.
application.properties file. I have set database properties
connection:
Database User: local_dev_user
Databsae Password: 123456
Database Url: jdbc:postgresql://localhost/local_dev_db
Schema: schema_uploadfile
I will create this Database in local Postgres Database next.
spring.profiles.active=dev spring.datasource.username=local_dev_user spring.datasource.password=123456 spring.datasource.url=jdbc:postgresql://localhost/local_dev_db spring.datasource.hikari.schema=schema_uploadfile spring.sql.init.mode=always
spring.sql.init.mode=always: is to create table when project is start
up.
4. Create Local Postgres Database
I have created local Postgres Server and Database with Schema by using pgAdmin
4.
- Local development user:
- Local development DB
- New Schema in local DB
5. Entity Class
tbl_uploadfile class
This class for get request data and save in to table when do request upload
end-point. All fields in this class are the same column name in table.
@Data @Builder public class tbl_uploadfile { @Id @Column("id") private int id; @Column("file_name") private String fileName; @Column("file_type") private String fileType; @Column("grp_data") private byte[] grpData; }
UploadFileResponse class
This class for get response fields from getting data file of get data
end-point.
@Data @Builder public class UploadFileResponse { private boolean isError; private String fileName; private String fileLink; }
6. Service
I have create on service in order to do upload file and get uploaded file
data.
public interface UploadFileService { tbl_uploadfile saveUploadFile(MultipartFile file) throws Exception; tbl_uploadfile getFileUpload(String fileName); }
7. Repository
I have created repository interface by extended CrudRepository
from Spring Data repository.
CrudRepository: Spring will create instance for you.
There are two methods:
@Repository @Transactional(readOnly = true) public interface UploadFileRepository extends CrudRepository<tbl_uploadfile, Integer> { boolean existsByfileName(String fileName); Optional<tbl_uploadfile> findByFileName(String fileName); }
1. existsByfileName: to check the existed file by file name, this
method will generated sql to select data from table by file name.
SELECT tbl_uploadfile.id FROM schema_uploadfile.tbl_uploadfile * WHERE tbl_uploadfile.file_name = ? LIMIT 1
2. findByFileName: to get data file by file name. This method will
generated sql to select data from table by file name.
SELECT...FROM schema_uploadfile.tbl_uploadfile WHERE tbl_uploadfile.file_name = ?
8. Service Implementation
I have created UploadFileServiceImpl this service class will
implemented UploadFileService method.
This service will do the implementation of save upload file to store in table
and get the uploaded file data from table. All of this action will be created
the endpoint in controller class.
In this class will be injected UploadFileRepository.
@Service @RequiredArgsConstructor @Slf4j public class UploadFileServiceImpl implements UploadFileService { private final UploadFileRepository uploadFileRepository; @Override public tbl_uploadfile saveUploadFile(MultipartFile file) throws Exception { if (uploadFileRepository.existsByfileName(file.getOriginalFilename())) { log.info("This file {} has been already existed: ", file.getOriginalFilename()); return tbl_uploadfile.builder().fileName(file.getOriginalFilename()).build(); } var uploadFile = tbl_uploadfile.builder().fileName(file.getOriginalFilename()).fileType(file.getContentType()) .grpData(file.getBytes()).build(); return uploadFileRepository.save(uploadFile); } @Override public tbl_uploadfile getFileUpload(String fileName) { return uploadFileRepository.findByFileName(fileName).orElseThrow(FileNotFoundException::new); } }
Save Upload File Operation: Before do the uploading file, we need to
check the existing file by name first. if the file already existed, then our
service will response info log and return that uploading file. This operation
will not save that file to Database.
log.info("This file {} has been already existed: ", file.getOriginalFilename());
Otherwise, service will do the upload file operation.
getFileUpload: this method will get the uploaded file data back from
table. And if the request file is not match, then it will throw the FileNotFoundException which will be implement next.
return uploadFileRepository.findByFileName(fileName).orElseThrow(FileNotFoundException::new);
9. Controller
In this controller class, I have created 3 endpoints.
1. uploadFile endpoint @PostMapping("/files/uploadfile"): to
do request save upload file.
2. getFile endpoint @GetMapping("/files/{fileName}"): to get
the uploaded file.
3. uploadMultiFiles @PostMapping("/files/multi_uploadfile"): to do multiple files upload.
@RestController @Builder @RequiredArgsConstructor @Slf4j public class UploadFileController { private final UploadFileService uploadFileService; @PostMapping("/files/uploadfile") public UploadFileResponse uploadFile(MultipartFile file) { try { var uploadFile = uploadFileService.saveUploadFile(file); return UploadFileResponse.builder().isError(false).fileName(uploadFile.getFileName()) .fileLink(creteUploadFileLink(uploadFile.getFileName())).build(); } catch (Exception e) { log.error("Upload File Failed.", e); return UploadFileResponse.builder().isError(true).fileName(file.getOriginalFilename()).build(); } } private String creteUploadFileLink(String fileName) { return ServletUriComponentsBuilder.fromCurrentRequest().replacePath("/files/" + fileName).toUriString(); } @GetMapping("/files/{fileName}") public ResponseEntity<Resource> getFile(@PathVariable String fileName) { var fileUpload = uploadFileService.getFileUpload(fileName); var grpData = new ByteArrayResource(fileUpload.getGrpData()); return ResponseEntity.ok().header(HttpHeaders.CONTENT_TYPE, fileUpload.getFileType()) /* * Alternative: * .cntentType(MediaType * .valueOf(fileUpload. * getFileType())) */ .cacheControl(CacheControl.maxAge(Duration.ofSeconds(60)).cachePrivate().mustRevalidate()) .body(grpData); } @PostMapping("/files/multi_uploadfile") public List<UploadFileResponse> uploadMultiFiles(@RequestPart List<MultipartFile> files) { return files.stream().map(this::uploadFile).collect(Collectors.toList()); } }
10. ExceptionHandler
There 2 class for this exception opteration.
1. FileNotFoundException: will be extended RuntimeException
from java.lang.
public class FileNotFoundException extends RuntimeException{ }
2. GlobalExceptionHandler: this is the custom message exception
class which will be used by FileNotFoundException class.
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(FileNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public String fileNotFoundException(FileNotFoundException e) { return "File not found.";/* Define your own message/response object */ } }
11. Start Up Project
Run project as spring boot project. And you will see this log show that
project is running on port: 8080.
Table have generated in Postgres Database.
12. Testing Project
Finally, let's try to start up project. I will use postman tool in order to
work with those endpoints above which have already implemented in controller
class.
1. Save Upload File: @PostMapping("/files/uploadfile")
2. Save Multi File Upload: @PostMapping("/files/multi_uploadfile")
3. Get File: @GetMapping("/files/{fileName}")
This endpoint will get file by file name.