Prerequisites

Introduction

This article is the second part of Low memory footprint with Spring boot 3.3 and Graalvm native image. Here, we will be building REST CRUD resources and package as a native executable within a container. We are using flyway for database versioning and springdoc library for swagger documentation with a web UI.

Flyway

Flyway is a database migration tool that helps you manage and apply database changes within your application to achieve database environment parity.

Setting up flyway for springboot project

Adding flyway dependency for postgres for maven project.

    <dependency>
        <groupId>org.flywaydb</groupId>
        <artifactId>flyway-core</artifactId>
    </dependency>
    <dependency>
        <groupId>org.flywaydb</groupId>
        <artifactId>flyway-database-postgresql</artifactId>
    </dependency>

Configurations in application.yml

spring:
  datasource:
    url: ${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/postgres}
    username: ${SPRING_DATASOURCE_USERNAME:postgres}
    password: ${SPRING_DATASOURCE_USERNAME:postgres}
    driver-class-name: org.postgresql.Driver
  jpa:
    hibernate:
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
        default_schema: inventory
  flyway:
    schemas: inventory
    enabled: true

management:
  endpoint:
    health:
      show-details: always
  health:
    diskspace:
      enabled: false

Managing database scripts

Database scripts are inside src/main/resources/db/migration and follows naming conventions V<version>__<name>.sql and flyway ensure that migrations are run in order of their versions.

Let’s create file in src/main/resources/db/migration with name V1__create_table_product.sql

CREATE TABLE product
(
    id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    product_name VARCHAR(255) NOT NULL,
    in_stock     boolean default false
);

Flyway naming pattern matters https://www.red-gate.com/blog/database-devops/flyway-naming-patterns-matter

Adding swagger web UI

Spring doc library helps to generate the API documentation for us.

   <dependency>
      <groupId>org.springdoc</groupId>
      <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
      <version>2.6.0</version>
   </dependency>

The swagger UI is accessible at http://server:port/context-path/swagger-ui.html

Let’s write boilerplate

  • create entity class

@RegisterReflectionForBinding({UUID[].class}) is needed for GraalVM native Image to specify that some hints are needed for reflection-based serialization for our entity class by hibernate.

@RegisterReflectionForBinding({UUID[].class})
@Entity
@Table(name = "product")
public class Product {

    @Id
    @GeneratedValue
    @Column(name = "id", columnDefinition = "UUID")
    private UUID id;

    @Column(name = "product_name", nullable = false)
    private String productName;


    @Column(name = "in_stock")
    private boolean inStock;
    
    //generate getters and setters or use lombok's @Data
}
  • extend jpa repository

@Repository
public interface ProductRepository extends JpaRepository<Product, UUID> {
}
  • create rest controller

@RequestMapping("/api/v1/product")
@RestController
public class ProductResource {

    private final ProductRepository productRepository;

    public ProductResource(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    @PostMapping("/create")
    public ResponseEntity<Product> createProduct(@RequestBody Product product) {
        return new ResponseEntity<>(productRepository.save(product), HttpStatus.CREATED);
    }

    @GetMapping("/list")
    public ResponseEntity<Page<Product>> getAllProducts(@RequestParam(defaultValue = "0") int pageNumber, @RequestParam(defaultValue = "10") int pageSize) {
        return new ResponseEntity<>(productRepository.findAll(PageRequest.of(pageNumber, pageSize)), HttpStatus.OK);
    }

    @PutMapping("/update/{id}")
    public ResponseEntity<Product> updateProduct(@PathVariable UUID id, @RequestBody Product productDetails) {
        Optional<Product> optionalProduct = productRepository.findById(id);
        if (optionalProduct.isPresent()) {
            Product product = optionalProduct.get();
            product.setProductName(productDetails.getProductName());
            product.setInStock(productDetails.isInStock());
            return new ResponseEntity<>(productRepository.save(product), HttpStatus.OK);
        } else {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
    }

    @DeleteMapping("/delete/{id}")
    public ResponseEntity<Void> deleteProduct(@PathVariable UUID id) {
        Optional<Product> product = productRepository.findById(id);
        if (product.isPresent()) {
            productRepository.delete(product.get());
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } else {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
    }
}

Running the project

build native OCI image

./mvn -Pnative spring-boot:build-image

create a docker network

docker network create spring-native-app-network

spin up a Postgres database

docker run --rm --name postgres-db --network spring-native-app-network -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:latest

spin up our inventory service container

docker run -d --rm --name inventory-service --network spring-native-app-network -e SPRING_DATASOURCE_URL=jdbc:postgresql://postgres-db:5432/postgres -e SPRING_DATASOURCE_USERNAME=postgres -e SPRING_DATASOURCE_PASSWORD=postgres -p 8080:8080 inventory-service:0.0.1-SNAPSHOT

goto http://localhost:8080/swagger-ui/index.html

swagger

Source

Repo url: inventory-service