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.
先决条件
完成这个指南,你需要:
-
大概15分钟
-
编辑器
-
JDK 17+ installed with
JAVA_HOME
configured appropriately -
Apache Maven 3.9.9
-
如果你愿意的话,还可以选择使用Quarkus CLI
-
如果你想构建原生可执行程序,可以选择安装Mandrel或者GraalVM,并正确配置(或者使用Docker在容器中进行构建)
解决方案
我们建议您按照下一节的说明逐步创建应用程序。然而,您可以直接转到已完成的示例。
克隆 Git 仓库: git clone https://github.com/quarkusio/quarkus-quickstarts.git
,或下载一个 存档 。
The solution is located in the spring-data-rest-quickstart
directory.
创建Maven项目
首先,我们需要一个新的工程项目。用以下命令创建一个新项目:
For Windows users:
-
If using cmd, (don’t use backward slash
\
and put everything on the same line) -
If using Powershell, wrap
-D
parameters in double quotes e.g."-DprojectArtifactId=spring-data-rest-quickstart"
This command generates a 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:
quarkus extension add spring-data-rest
./mvnw quarkus:add-extension -Dextensions='spring-data-rest'
./gradlew addExtension --extensions='spring-data-rest'
这会将以下内容添加到你的构建文件中:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-data-rest</artifactId>
</dependency>
implementation("io.quarkus:quarkus-spring-data-rest")
Furthermore, the following dependency needs to be added
For the tests you will also need REST Assured. Add it to the build file:
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
testImplementation("io.rest-assured:rest-assured")
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 jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.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 -it --rm=true --name quarkus_test -e POSTGRES_USER=quarkus_test -e POSTGRES_PASSWORD=quarkus_test -e POSTGRES_DB=quarkus_test -p 5432:5432 postgres:14.1
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 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
./gradlew test
打包并运行该应用程序
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:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
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 ifPagingAndSortingRepository
orJpaRepository
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.
So, you can extend the PagingAndSortingRepository
:
package org.acme.spring.data.rest;
import org.springframework.data.repository.PagingAndSortingRepository;
public interface FruitsRepository extends CrudRepository<Fruit, Long>, PagingAndSortingRepository<Fruit, Long> {
}
Now the GET /fruits
will accept three new query parameters: sort
, page
and size
.
Query parameter | 描述 | Default value | Example values |
---|---|---|---|
|
Sorts the entities that are returned by the list operation |
"" |
|
|
Zero indexed page number. Invalid value is interpreted as 0. |
0 |
0, 11, 100 |
|
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.
Additionally, rather than extending both PagingAndSortingRepository
and CrudRepository
, you can use JpaRepository
, which is a higher-level abstraction tailored for JPA. Since JpaRepository
already extends both PagingAndSortingRepository
and CrudRepository
, it can replace CrudRepository
directly.
package org.acme.spring.data.rest;
import org.springframework.data.repository.PagingAndSortingRepository;
public interface FruitsRepository extends JpaRepository<Fruit, Long> {
}
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 |
|
|
List |
|
|
Create |
|
|
Update |
|
|
Delete |
|
|
Securing endpoints
This extension will automatically use the Security annotations within the package jakarta.annotation.security
that are defined on your resource interfaces:
import jakarta.annotation.security.DenyAll;
import jakarta.annotation.security.RolesAllowed;
@DenyAll
public interface FruitResource extends CrudRepository<Fruit, Long> {
@RolesAllowed("superuser")
Iterable<Fruit> findAll();
}
Note that this feature is provided by the REST Data with Panache extension that this extension is using under the hood. So, pure Spring Boot applications might not behave the same way.