Quarkus - Extension for Spring Data REST

While users are encouraged to use REST Data with Panache for the REST data access endpoints generation, Quarkus provides a compatibility layer for Spring Data REST in the form of the spring-data-rest extension.

This technology is considered preview.

In preview, backward compatibility and presence in the ecosystem is not guaranteed. Specific improvements might require to change configuration or APIs and plans to become stable are under way. Feedback is welcome on our mailing list or as issues in our GitHub issue tracker.

For a full list of possible extension statuses, check our FAQ entry.

Prerequisites

To complete this guide, you need:

  • less than 15 minutes

  • an IDE

  • JDK 11+ installed with JAVA_HOME configured appropriately

  • Apache Maven 3.8.1+

Solution

We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.

Clone the Git repository: git clone https://github.com/quarkusio/quarkus-quickstarts.git, or download an archive.

The solution is located in the spring-data-rest-quickstart directory.

Creating the Maven project

First, we need a new project. Create a new project with the following command:

mvn io.quarkus:quarkus-maven-plugin:2.1.1.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=spring-data-rest-quickstart \
    -Dextensions="spring-data-rest,resteasy-jackson,quarkus-jdbc-postgresql" \
    -DnoExamples
cd spring-data-rest-quickstart

This command generates a Maven project with the spring-data-rest extension.

If you already have your Quarkus project configured, you can add the spring-data-rest extension to your project by running the following command in your project base directory:

./mvnw quarkus:add-extension -Dextensions="spring-data-rest"

This will add the following to your pom.xml:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-spring-data-rest</artifactId>
</dependency>

For the tests you will also need REST Assured. Add it to the pom.xml:

<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <scope>test</scope>
</dependency>

Note: both resteasy-jackson and resteasy-jsonb are supported and can be interchanged.

Define the Entity

Throughout the course of this guide, the following JPA Entity will be used:

package org.acme.spring.data.rest;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Fruit {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private String color;


    public Fruit() {
    }

    public Fruit(String name, String color) {
        this.name = name;
        this.color = color;
    }

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

Configure database access properties

Add the following properties to application.properties to configure access to a local PostgreSQL instance.

quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=quarkus_test
quarkus.datasource.password=quarkus_test
quarkus.datasource.jdbc.url=jdbc:postgresql:quarkus_test
quarkus.datasource.jdbc.max-size=8
quarkus.hibernate-orm.database.generation=drop-and-create

This configuration assumes that PostgreSQL will be running locally.

A very easy way to accomplish that is by using the following Docker command:

docker run --ulimit memlock=-1:-1 -it --rm=true --memory-swappiness=0 --name quarkus_test -e POSTGRES_USER=quarkus_test -e POSTGRES_PASSWORD=quarkus_test -e POSTGRES_DB=quarkus_test -p 5432:5432 postgres:11.5

If you plan on using a different setup, please change your application.properties accordingly.

Prepare the data

To make it easier to showcase some capabilities of Spring Data REST on Quarkus, some test data should be inserted into the database by adding the following content to a new file named src/main/resources/import.sql:

INSERT INTO fruit(id, name, color) VALUES (1, 'Cherry', 'Red');
INSERT INTO fruit(id, name, color) VALUES (2, 'Apple', 'Red');
INSERT INTO fruit(id, name, color) VALUES (3, 'Banana', 'Yellow');
INSERT INTO fruit(id, name, color) VALUES (4, 'Avocado', 'Green');
INSERT INTO fruit(id, name, color) VALUES (5, 'Strawberry', 'Red');

Hibernate ORM will execute these queries on application startup.

Define the repository

It is now time to define the repository that will be used to access Fruit. In a typical Spring Data fashion, create a repository like so:

package org.acme.spring.data.rest;

import org.springframework.data.repository.CrudRepository;

public interface FruitsRepository extends CrudRepository<Fruit, Long> {
}

The FruitsRepository above extends Spring Data’s org.springframework.data.repository.CrudRepository which means that all of the latter’s methods are available to FruitsRepository.

The spring-data-jpa extension will generate an implementation for this repository. Then the spring-data-rest extension will generate a REST CRUD resource for it.

Update the test

To test the capabilities of FruitsRepository proceed to update the content of FruitsRepositoryTest to:

package org.acme.spring.data.rest;

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.core.IsNot.not;

@QuarkusTest
class FruitsRepositoryTest {

    @Test
    void testListAllFruits() {
        //List all, should have all 3 fruits the database has initially:
        given()
                .accept("application/json")
                .when().get("/fruits")
                .then()
                .statusCode(200)
                .body(
                        containsString("Cherry"),
                        containsString("Apple"),
                        containsString("Banana")
                );

        //Delete the Cherry:
        given()
                .when().delete("/fruits/1")
                .then()
                .statusCode(204);

        //List all, cherry should be missing now:
        given()
                .accept("application/json")
                .when().get("/fruits")
                .then()
                .statusCode(200)
                .body(
                        not(containsString("Cherry")),
                        containsString("Apple"),
                        containsString("Banana")
                );

        //Create a new Fruit
        given()
                .contentType("application/json")
                .accept("application/json")
                .body("{\"name\": \"Orange\", \"color\": \"Orange\"}")
                .when().post("/fruits")
                .then()
                .statusCode(201)
                .body(containsString("Orange"))
                .body("id", notNullValue())
                .extract().body().jsonPath().getString("id");

        //List all, Orange should be present now:
        given()
                .accept("application/json")
                .when().get("/fruits")
                .then()
                .statusCode(200)
                .body(
                        not(containsString("Cherry")),
                        containsString("Apple"),
                        containsString("Orange")
                );
    }
}

The test can be easily run by issuing: ./mvnw test.

Package and run the application

Quarkus dev mode works with the defined repositories just like with any other Quarkus extension, greatly enhancing your productivity during the dev cycle. The application can be started in dev mode as usual using:

./mvnw quarkus:dev

Run the application as a native binary

You can of course create a native executable following the instructions of the Building native executables guide.

Supported Spring Data REST functionalities

Quarkus currently supports a subset of Spring Data REST features, namely the most useful and most commonly used features.

What is supported

The following sections describe the most important supported features of Spring Data REST.

Automatic REST endpoint generation

Interfaces that extend any of the following Spring Data repositories get automatically generated REST endpoints:

  • org.springframework.data.repository.CrudRepository

  • org.springframework.data.repository.PagingAndSortingRepository

  • org.springframework.data.jpa.repository.JpaRepository

Endpoints generated from the above repositories expose five common REST operations:

  • GET /fruits - lists all entities or returns a page if PagingAndSortingRepository or JpaRepository is used.

  • GET /fruits/:id - returns an entity by ID.

  • POST /fruits - creates a new entity.

  • PUT /fruits/:id - updates an existing entity or creates a new one with a specified ID (if allowed by the entity definition).

  • DELETE /fruits/:id - deletes an entity by ID.

There are two supported data types: application/json and application/hal+json. The former is used by default, but it is highly recommended to specify which one you prefer with an Accept header.

Exposing many entities

If a database contains many entities, it might not be a great idea to return them all at once. PagingAndSortingRepository allows the spring-data-rest extension to access data in chunks.

Replace the CrudRepository with PagingAndSortingRepository in the FruitsRepository:

package org.acme.spring.data.rest;

import org.springframework.data.repository.PagingAndSortingRepository;

public interface FruitsRepository extends PagingAndSortingRepository<Fruit, Long> {
}

Now the GET /fruits will accept three new query parameters: sort, page and size.

Query parameter Description Default value Example values

sort

Sorts the entities that are returned by the list operation

""

?sort=name (ascending name), ?sort=name,-color (ascending name and descending color)

page

Zero indexed page number. Invalid value is interpreted as 0.

0

0, 11, 100

size

Page size. Minimal accepted value is 1. Any lower value is interpreted as 1.

20

1, 11, 100

For paged responses, spring-data-rest also returns a set of link headers that can be used to access other pages: first, previous, next and last.

Fine tuning endpoints generation

This allows user to specify which methods should be exposed and what path should be used to access them. Spring Data REST provides two annotations that can be used: @RepositoryRestResource and @RestResource. spring-data-rest extension supports the exported, path collectionResourceRel attributes of these annotations.

Assume for example that fruits repository should be accessible by a /my-fruits path and only allow GET operation. In such a case, FruitsRepository would look like so:

package org.acme.spring.data.rest;

import java.util.Optional;

import org.springframework.data.repository.CrudRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource;

@RepositoryRestResource(exported = false, path = "/my-fruits")
public interface FruitsRepository extends CrudRepository<Fruit, Long> {

    @RestResource(exported = true)
    Optional<Fruit> findById(Long id);

    @RestResource(exported = true)
    Iterable<Fruit> findAll();
}

spring-data-rest uses only a subset of the repository methods for data access. It is important to annotate the correct method in order to customize its REST endpoint:

REST operation CrudRepository PagingAndSortingRepository and JpaRepository

Get by ID

Optional<T> findById(ID id)

Optional<T> findById(ID id)

List

Iterable<T> findAll()

Page<T> findAll(Pageable pageable)

Create

<S extends T> S save(S entity)

<S extends T> S save(S entity)

Update

<S extends T> S save(S entity)

<S extends T> S save(S entity)

Delete

void deleteById(ID id)

void deleteById(ID id)

What is currently unsupported

  • Only the repository methods listed above are supported. No other standard or custom methods are supported.

  • Only the exposed, path and collectionResourceRel annotation properties are supported.

Important Technical Note

Please note that the Spring support in Quarkus does not start a Spring Application Context nor are any Spring infrastructure classes run. Spring classes and annotations are only used for reading metadata and / or are used as user code method return types or parameter types.