Extension for Spring Data API
虽然鼓励用户将 Hibernate ORM 与 Panache 一起用于关系数据库访问,但 Quarkus 以 spring-data-jpa
扩展的形式提供了 Spring Data JPA 存储库的兼容性层。
While users are encouraged to use Hibernate ORM with Panache for Relational Database access, Quarkus provides a compatibility layer for
Spring Data JPA repositories in the form of the spring-data-jpa
extension.
Prerequisites
Unresolved directive in spring-data-jpa.adoc - include::{includes}/prerequisites.adoc[]
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.
克隆 Git 存储库: git clone {quickstarts-clone-url}
,或下载 {quickstarts-archive-url}[存档]。
Clone the Git repository: git clone {quickstarts-clone-url}
, or download an {quickstarts-archive-url}[archive].
解决方案位于 spring-data-jpa-quickstart
directory 中。
The solution is located in the spring-data-jpa-quickstart
directory.
Creating the Maven project
首先,我们需要一个新项目。使用以下命令创建一个新项目:
First, we need a new project. Create a new project with the following command:
Unresolved directive in spring-data-jpa.adoc - include::{includes}/devtools/create-app.adoc[]
此命令生成一个 Maven 项目并导入 spring-data-jpa
扩展。
This command generates a Maven project and imports the spring-data-jpa
extension.
如果您已经配置了 Quarkus 项目,则可以通过在项目基本目录中运行以下命令将 spring-data-jpa
扩展添加到项目中:
If you already have your Quarkus project configured, you can add the spring-data-jpa
extension
to your project by running the following command in your project base directory:
./mvnw quarkus:add-extension -Dextensions="spring-data-jpa"
这会将以下内容添加到构建文件中:
This will add the following to your build file:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-data-jpa</artifactId>
</dependency>
implementation("io.quarkus:quarkus-spring-data-jpa")
Define the Entity
在整个本指南的过程中,将使用以下 JPA 实体:
Throughout the course of this guide, the following JPA Entity will be used:
package org.acme.spring.data.jpa;
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
将以下属性添加到 application.properties
,以配置对本地 PostgreSQL 实例的访问。
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.datasource.jdbc.min-size=2
quarkus.hibernate-orm.database.generation=drop-and-create
此配置假定 PostgreSQL 将在本地运行。
This configuration assumes that PostgreSQL will be running locally.
实现此目的的一种非常简单的方法是使用以下 Docker 命令:
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
如果您计划使用其他设置,请相应地更改 application.properties
。
If you plan on using a different setup, please change your application.properties
accordingly.
Prepare the data
为了更轻松地展示 Quarkus 上 Spring Data JPA 的一些功能,应该通过将以下内容添加到名为 src/main/resources/import.sql
的新文件中,将一些测试数据插入数据库:
To make it easier to showcase some capabilities of Spring Data JPA 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 将在应用程序启动时执行这些查询。
Hibernate ORM will execute these queries on application startup.
除 |
Users can also use a file named |
Define the repository
现在是时候定义将用于访问 Fruit
的存储库。以典型的 Spring Data 方式创建以下存储库:
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.jpa;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
public interface FruitRepository extends CrudRepository<Fruit, Long> {
List<Fruit> findByColor(String color);
}
上述 FruitRepository
扩展了 Spring Data 的 org.springframework.data.repository.CrudRepository
,这意味着后者中的所有方法都对 FruitRepository
可用。另外,我们定义了 findByColor
,其目的是返回符合指定颜色的所有水果实体。
The FruitRepository
above extends Spring Data’s org.springframework.data.repository.CrudRepository
which means that all the latter’s methods are
available to FruitRepository
.
Additionally findByColor
is defined whose purpose is to return all Fruit entities that match the specified color.
Update the Jakarta REST resource
在使用仓库之后,下一项工作是创建将使用 FruitRepository
的 Jakarta REST 资源。使用以下内容创建 FruitResource
:
With the repository in place, the next order of business is to create the Jakarta REST resource that will use the FruitRepository
.
Create FruitResource
with the following content:
package org.acme.spring.data.jpa;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import java.util.List;
import java.util.Optional;
@Path("/fruits")
public class FruitResource {
private final FruitRepository fruitRepository;
public FruitResource(FruitRepository fruitRepository) {
this.fruitRepository = fruitRepository;
}
@GET
public Iterable<Fruit> findAll() {
return fruitRepository.findAll();
}
@DELETE
@Path("{id}")
public void delete(long id) {
fruitRepository.deleteById(id);
}
@POST
@Path("/name/{name}/color/{color}")
public Fruit create(String name, String color) {
return fruitRepository.save(new Fruit(name, color));
}
@PUT
@Path("/id/{id}/color/{color}")
public Fruit changeColor(Long id, String color) {
Optional<Fruit> optional = fruitRepository.findById(id);
if (optional.isPresent()) {
Fruit fruit = optional.get();
fruit.setColor(color);
return fruitRepository.save(fruit);
}
throw new IllegalArgumentException("No Fruit with id " + id + " exists");
}
@GET
@Path("/color/{color}")
public List<Fruit> findByColor(String color) {
return fruitRepository.findByColor(color);
}
}
FruitResource
现在提供了一些 REST 端点,这些端点可用于对 Fruit
执行 CRUD 操作。
FruitResource
now provides a few REST endpoints that can be used to perform CRUD operation on Fruit
.
Note on Spring Web
而 Jakarta REST 资源也可以替换为 Spring Web 控制器,因为 Quarkus 支持使用 Spring 控制器来定义 REST 端点。有关更多详情,请参阅 Spring Web guide 。
The Jakarta REST resource can also be substituted with a Spring Web controller as Quarkus supports REST endpoint definition using Spring controllers. See the Spring Web guide for more details.
Update the test
要测试 FruitRepository
的功能,请继续将 FruitResourceTest
的内容更新为:
To test the capabilities of FruitRepository
proceed to update the content of FruitResourceTest
to:
package org.acme.spring.data.jpa;
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.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.core.IsNot.not;
@QuarkusTest
class FruitResourceTest {
@Test
void testListAllFruits() {
//List all, should have all 3 fruits the database has initially:
given()
.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()
.when().get("/fruits")
.then()
.statusCode(200)
.body(
not(containsString("Cherry")),
containsString("Apple"),
containsString("Banana")
);
//Create a new Fruit
given()
.when().post("/fruits/name/Orange/color/Orange")
.then()
.statusCode(200)
.body(containsString("Orange"))
.body("id", notNullValue())
.extract().body().jsonPath().getString("id");
//List all, Orange should be present now:
given()
.when().get("/fruits")
.then()
.statusCode(200)
.body(
not(containsString("Cherry")),
containsString("Apple"),
containsString("Orange")
);
}
@Test
void testFindByColor() {
//Find by color that no fruit has
given()
.when().get("/fruits/color/Black")
.then()
.statusCode(200)
.body("size()", is(0));
//Find by color that multiple fruits have
given()
.when().get("/fruits/color/Red")
.then()
.statusCode(200)
.body(
containsString("Apple"),
containsString("Strawberry")
);
//Find by color that matches
given()
.when().get("/fruits/color/Green")
.then()
.statusCode(200)
.body("size()", is(1))
.body(containsString("Avocado"));
//Update color of Avocado
given()
.when().put("/fruits/id/4/color/Black")
.then()
.statusCode(200)
.body(containsString("Black"));
//Find by color that Avocado now has
given()
.when().get("/fruits/color/Black")
.then()
.statusCode(200)
.body("size()", is(1))
.body(
containsString("Black"),
containsString("Avocado")
);
}
}
可以通过发出以下命令轻松运行测试:
The test can be easily run by issuing:
Unresolved directive in spring-data-jpa.adoc - include::{includes}/devtools/test.adoc[]
Package and run the application
Quarkus 开发模式适用于已定义的存储库,就像适用于任何其他 Quarkus 扩展一样,大大提高了开发周期中的生产力。可以使用以下命令像平常一样在开发模式下启动应用程序:
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:
Unresolved directive in spring-data-jpa.adoc - include::{includes}/devtools/dev.adoc[]
Run the application as a native binary
您当然可以按照 this guide 的说明创建本机可执行文件。
You can of course create a native executable following the instructions of this guide.
Supported Spring Data JPA functionalities
Quarkus 目前支持 Spring Data JPA 功能的一个子集,即最有用的和最常用的功能。
Quarkus currently supports a subset of Spring Data JPA’s features, namely the most useful and most commonly used features.
此支持的一个重要部分是所有存储库生成都在构建时完成,从而确保所有受支持的功能在原生模式下都能正常工作。此外,开发人员在构建时知道他们的存储库方法名称是否可以转换为正确的 JPQL 查询。这也意味着,如果一个方法名称表明应该使用一个不属于实体的字段,开发人员将在构建时获得相关错误。
An important part of this support is that all repository generation is done at build time thus ensuring that all supported features work correctly in native mode. Moreover, developers know at build time whether their repository method names can be converted to proper JPQL queries. This also means that if a method name indicates that a field should be used that is not part of the Entity, developers will get the relevant error at build time.
What is supported
以下部分描述了 Spring Data JPA 最重要的受支持功能。
The following sections described the most important supported features of Spring Data JPA.
Automatic repository implementation generation
扩展以下任何 Spring Data 存储库的接口将自动实现:
Interfaces that extend any of the following Spring Data repositories are automatically implemented:
-
org.springframework.data.repository.Repository
-
org.springframework.data.repository.CrudRepository
-
org.springframework.data.repository.PagingAndSortingRepository
-
org.springframework.data.jpa.repository.JpaRepository
生成的存储库还注册为 Bean,以便可以注入到任何其他 Bean 中。此外,更新数据库的方法会自动使用 @Transactional
进行注释。
The generated repositories are also registered as beans so they can be injected into any other bean.
Furthermore, the methods that update the database are automatically annotated with @Transactional
.
Fine-tuning of repository definition
这允许用户定义存储库接口从任何受支持的 Spring Data 存储库接口中挑选方法,而不必扩展这些接口。这特别有用,例如,当存储库需要使用 CrudRepository
中的一些方法时,但不希望公开该接口的所有方法列表时。
This allows user defined repository interfaces to cherry-pick methods from any of the supported Spring Data repository interfaces without having to extend those interfaces.
This is particularly useful when for example a repository needs to use some methods from CrudRepository
but it’s undesirable to expose the full list of methods of said interface.
例如,假设一个 PersonRepository
不应该扩展 CrudRepository
,但想使用 save
和 findById
在该接口中定义的方法。在这种情况下,PersonRepository
将如下所示:
Assume for example that a PersonRepository
that shouldn’t extend CrudRepository
but would like to use save
and findById
methods which are defined in said interface.
In such a case, PersonRepository
would look like so:
package org.acme.spring.data.jpa;
import org.springframework.data.repository.Repository;
public interface PersonRepository extends Repository<Person, Long> {
Person save(Person entity);
Optional<Person> findById(Person entity);
}
Customizing individual repositories using repository fragments
可以为存储库添加其他功能或覆盖受支持 Spring Data 存储库方法的默认实现。最好通过一个示例来说明这一点。
Repositories can be enriched with additional functionality or override the default implementation of methods of the supported Spring Data repositories. This is best shown with an example.
存储库片段定义如下:
A repository fragment is defined as so:
public interface PersonFragment {
// custom findAll
List<Person> findAll();
void makeNameUpperCase(Person person);
}
该片段的实现如下所示:
The implementation of that fragment looks like this:
import java.util.List;
import io.quarkus.hibernate.orm.panache.runtime.JpaOperations;
public class PersonFragmentImpl implements PersonFragment {
@Override
public List<Person> findAll() {
// do something here
return (List<Person>) JpaOperations.findAll(Person.class).list();
}
@Override
public void makeNameUpperCase(Person person) {
person.setName(person.getName().toUpperCase());
}
}
然后用于 PersonRepository
界面的实际情况将如下所示:
Then the actual PersonRepository
interface to be used would look like:
public interface PersonRepository extends JpaRepository<Person, Long>, PersonFragment {
}
Derived query methods
遵循 Spring Data 约定的资源库接口的方法可自动实现(除非属于稍后列出的不受支持的案例之一)。这意味着以下方法都会起作用:
Methods of repository interfaces that follow the Spring Data conventions can be automatically implemented (unless they fall into one of the unsupported cases listed later on). This means that methods like the following will all work:
public interface PersonRepository extends CrudRepository<Person, Long> {
List<Person> findByName(String name);
Person findByNameBySsn(String ssn);
Optional<Person> findByNameBySsnIgnoreCase(String ssn);
boolean existsBookByYearOfBirthBetween(Integer start, Integer end);
List<Person> findByName(String name, Sort sort);
Page<Person> findByNameOrderByJoined(String name, Pageable pageable);
List<Person> findByNameOrderByAge(String name);
List<Person> findByNameOrderByAgeDesc(String name, Pageable pageable);
List<Person> findByAgeBetweenAndNameIsNotNull(int lowerAgeBound, int upperAgeBound);
List<Person> findByAgeGreaterThanEqualOrderByAgeAsc(int age);
List<Person> queryByJoinedIsAfter(Date date);
Collection<Person> readByActiveTrueOrderByAgeDesc();
Long countByActiveNot(boolean active);
List<Person> findTop3ByActive(boolean active, Sort sort);
Stream<Person> findPersonByNameAndSurnameAllIgnoreCase(String name, String surname);
}
User defined queries
@Query
注释中包含的用户提供的查询。例如,以下所有内容均可起作用:
User supplied queries contained in the @Query
annotation. For example things like the following all work:
public interface MovieRepository extends CrudRepository<Movie, Long> {
Movie findFirstByOrderByDurationDesc();
@Query("select m from Movie m where m.rating = ?1")
Iterator<Movie> findByRating(String rating);
@Query("from Movie where title = ?1")
Movie findByTitle(String title);
@Query("select m from Movie m where m.duration > :duration and m.rating = :rating")
List<Movie> withRatingAndDurationLargerThan(@Param("duration") int duration, @Param("rating") String rating);
@Query("from Movie where title like concat('%', ?1, '%')")
List<Object[]> someFieldsWithTitleLike(String title, Sort sort);
@Modifying
@Query("delete from Movie where rating = :rating")
void deleteByRating(@Param("rating") String rating);
@Modifying
@Query("delete from Movie where title like concat('%', ?1, '%')")
Long deleteByTitleLike(String title);
@Modifying
@Query("update Movie m set m.rating = :newName where m.rating = :oldName")
int changeRatingToNewName(@Param("newName") String newName, @Param("oldName") String oldName);
@Modifying
@Query("update Movie set rating = null where title =?1")
void setRatingToNullForTitle(String title);
@Query("from Movie order by length(title)")
Slice<Movie> orderByTitleLength(Pageable pageable);
}
用 @Modifying
注释的所有方法都会自动用 @Transactional
注释。
All methods that are annotated with @Modifying
will automatically be annotated with @Transactional
.
在 Quarkus 中,当参数名称已编译为字节码时, |
In Quarkus, |
Naming Strategies
Hibernate ORM 使用物理命名策略和隐式命名策略映射属性名称。如果你希望使用 Spring Boot 的默认命名策略,需要设置以下属性:
Hibernate ORM maps property names using a physical naming strategy and an implicit naming strategy. If you wish to use Spring Boot’s default naming strategies, the following properties need to be set:
quarkus.hibernate-orm.physical-naming-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
quarkus.hibernate-orm.implicit-naming-strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
More examples
可以在 integration tests 目录中查看大量示例,该目录位于 Quarkus 源代码中。
An extensive list of examples can be seen in the integration tests directory which is located inside the Quarkus source code.
What is currently unsupported
-
Methods of the
org.springframework.data.repository.query.QueryByExampleExecutor
interface - if any of these are invoked, a Runtime exception will be thrown. -
QueryDSL support. No attempt will be made to generate implementations of the QueryDSL related repositories.
-
Using
org.springframework.data.jpa.repository.JpaSpecificationExecutor
-
Customizing the base repository for all repository interfaces in the code base.
-
In Spring Data JPA this is done by registering a class that extends
org.springframework.data.jpa.repository.support.SimpleJpaRepository
however in Quarkus this class is not used at all (since all the necessary plumbing is done at build time). Similar support might be added to Quarkus in the future.
-
-
Using
java.util.concurrent.Future
and classes that extend it as return types of repository methods. -
Native and named queries when using
@Query
-
Entity State-detection Strategies via
EntityInformation
. -
The use of
org.springframework.data.jpa.repository.Lock
Quarkus 团队正在探索弥合 JPA 和 Reactive 世界之间差距的各种备选方案。
The Quarkus team is exploring various alternatives to bridging the gap between the JPA and Reactive worlds.
Important Technical Note
请注意,Quarkus 中的 Spring 支持不会启动 Spring 应用程序上下文,也不会运行任何 Spring 基础架构类。Spring 类和注释仅用于读取元数据和/或用作用户代码方法返回类型或参数类型。
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.
More Spring guides
Quarkus 还有更多 Spring 兼容性功能。请参阅以下指南以获取更多详情:
Quarkus has more Spring compatibility features. See the following guides for more details: