SPRING DATA JDBC API CRUD: Upload File and Store using Postgres Database

{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)


Generate Spring MNV from start.spring.io


POM file Maven Dependencies


<?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:



Postgres local DEV USER Creation




- Local development DB



Postgres local DEV DB Creation



- New Schema in local DB



schema creation





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.


project log after start up running on port 8080



Table have generated in Postgres Database.


Generated Table after init project




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")


save upload single file


2. Save Multi File Upload: @PostMapping("/files/multi_uploadfile")


multi file upload


3. Get File: @GetMapping("/files/{fileName}")

This endpoint will get file by file name.


get file by name


4. Check data store in table


uploaded file data store in table



Conclusion

Hope this article was helpful.








Previous Post Next Post