Extension for Spring Data API
虽然鼓励用户将 Hibernate ORM 与 Panache 一起用于关系数据库访问,但 Quarkus 以 spring-data-jpa
扩展的形式提供了 Spring Data JPA 存储库的兼容性层。
- Prerequisites
- Solution
- Creating the Maven project
- Define the Entity
- Configure database access properties
- Prepare the data
- Define the repository
- Update the Jakarta REST resource
- Update the test
- Package and run the application
- Run the application as a native binary
- Supported Spring Data JPA functionalities
- Important Technical Note
- More Spring guides
Prerequisites
如要完成本指南,您需要:
-
Roughly 15 minutes
-
An IDE
-
安装了 JDK 17+,已正确配置
JAVA_HOME
-
Apache Maven ${proposed-maven-version}
-
如果你想使用 Quarkus CLI, 则可以选择使用
-
如果你想构建一个本机可执行文件(或如果你使用本机容器构建,则使用 Docker),则可以选择安装 Mandrel 或 GraalVM 以及 configured appropriately
Solution
我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。
克隆 Git 存储库: git clone $${quickstarts-base-url}.git
,或下载 $${quickstarts-base-url}/archive/main.zip[存档]。
解决方案位于 spring-data-jpa-quickstart
directory 中。
Creating the Maven project
首先,我们需要一个新项目。使用以下命令创建一个新项目:
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
--no-code
cd {create-app-artifact-id}
要创建一个 Gradle 项目,添加 --gradle
或 --gradle-kotlin-dsl
选项。
有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
-DprojectGroupId={create-app-group-id} \
-DprojectArtifactId={create-app-artifact-id} \
-DnoCode
cd {create-app-artifact-id}
要创建一个 Gradle 项目,添加 -DbuildTool=gradle
或 -DbuildTool=gradle-kotlin-dsl
选项。
适用于 Windows 用户:
-
如果使用 cmd,(不要使用反斜杠
\
,并将所有内容放在同一行上) -
如果使用 Powershell,将
-D
参数用双引号引起来,例如"-DprojectArtifactId={create-app-artifact-id}"
此命令生成一个 Maven 项目并导入 spring-data-jpa
扩展。
如果您已经配置了 Quarkus 项目,则可以通过在项目基本目录中运行以下命令将 spring-data-jpa
扩展添加到项目中:
./mvnw quarkus:add-extension -Dextensions="spring-data-jpa"
这会将以下内容添加到构建文件中:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-data-jpa</artifactId>
</dependency>
implementation("io.quarkus:quarkus-spring-data-jpa")
Define the Entity
在整个本指南的过程中,将使用以下 JPA 实体:
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 实例的访问。
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 将在本地运行。
实现此目的的一种非常简单的方法是使用以下 Docker 命令:
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
。
Prepare the data
为了更轻松地展示 Quarkus 上 Spring Data JPA 的一些功能,应该通过将以下内容添加到名为 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 将在应用程序启动时执行这些查询。
除 |
Define the repository
现在是时候定义将用于访问 Fruit
的存储库。以典型的 Spring Data 方式创建以下存储库:
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
,其目的是返回符合指定颜色的所有水果实体。
Update the Jakarta REST resource
在使用仓库之后,下一项工作是创建将使用 FruitRepository
的 Jakarta REST 资源。使用以下内容创建 FruitResource
:
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 操作。
Note on Spring Web
而 Jakarta REST 资源也可以替换为 Spring Web 控制器,因为 Quarkus 支持使用 Spring 控制器来定义 REST 端点。有关更多详情,请参阅 Spring Web guide 。
Update the test
要测试 FruitRepository
的功能,请继续将 FruitResourceTest
的内容更新为:
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")
);
}
}
可以通过发出以下命令轻松运行测试:
./mvnw test
./gradlew test
Package and run the application
Quarkus 开发模式适用于已定义的存储库,就像适用于任何其他 Quarkus 扩展一样,大大提高了开发周期中的生产力。可以使用以下命令像平常一样在开发模式下启动应用程序:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
Run the application as a native binary
您当然可以按照 this guide 的说明创建本机可执行文件。
Supported Spring Data JPA functionalities
Quarkus 目前支持 Spring Data JPA 功能的一个子集,即最有用的和最常用的功能。
此支持的一个重要部分是所有存储库生成都在构建时完成,从而确保所有受支持的功能在原生模式下都能正常工作。此外,开发人员在构建时知道他们的存储库方法名称是否可以转换为正确的 JPQL 查询。这也意味着,如果一个方法名称表明应该使用一个不属于实体的字段,开发人员将在构建时获得相关错误。
What is supported
以下部分描述了 Spring Data JPA 最重要的受支持功能。
Automatic repository implementation generation
扩展以下任何 Spring Data 存储库的接口将自动实现:
-
org.springframework.data.repository.Repository
-
org.springframework.data.repository.CrudRepository
-
org.springframework.data.repository.PagingAndSortingRepository
-
org.springframework.data.jpa.repository.JpaRepository
生成的存储库还注册为 Bean,以便可以注入到任何其他 Bean 中。此外,更新数据库的方法会自动使用 @Transactional
进行注释。
Fine-tuning of repository definition
这允许用户定义存储库接口从任何受支持的 Spring Data 存储库接口中挑选方法,而不必扩展这些接口。这特别有用,例如,当存储库需要使用 CrudRepository
中的一些方法时,但不希望公开该接口的所有方法列表时。
例如,假设一个 PersonRepository
不应该扩展 CrudRepository
,但想使用 save
和 findById
在该接口中定义的方法。在这种情况下,PersonRepository
将如下所示:
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 存储库方法的默认实现。最好通过一个示例来说明这一点。
存储库片段定义如下:
public interface PersonFragment {
// custom findAll
List<Person> findAll();
void makeNameUpperCase(Person person);
}
该片段的实现如下所示:
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
界面的实际情况将如下所示:
public interface PersonRepository extends JpaRepository<Person, Long>, PersonFragment {
}
Derived query methods
遵循 Spring Data 约定的资源库接口的方法可自动实现(除非属于稍后列出的不受支持的案例之一)。这意味着以下方法都会起作用:
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
注释中包含的用户提供的查询。例如,以下所有内容均可起作用:
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
注释。
在 Quarkus 中,当参数名称已编译为字节码时, |
Naming Strategies
Hibernate ORM 使用物理命名策略和隐式命名策略映射属性名称。如果你希望使用 Spring Boot 的默认命名策略,需要设置以下属性:
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 源代码中。
What is currently unsupported
-
org.springframework.data.repository.query.QueryByExampleExecutor
接口的方法 - 如果调用了其中任何一个方法,将抛出运行时异常。 -
QueryDSL 支持。不会尝试生成与 QueryDSL 相关的资源库的实现。
-
Using
org.springframework.data.jpa.repository.JpaSpecificationExecutor
-
为代码库中的所有资源库接口自定义基本资源库。
-
在 Spring Data JPA 中,这是通过注册一个扩展
org.springframework.data.jpa.repository.support.SimpleJpaRepository
的类来完成的,但在 Quarkus 中,根本不用这个类(因为所有必要的管道都在构建时完成)。将来 Quarkus 可能会增加类似的支持。
-
-
使用
java.util.concurrent.Future
和扩展它的类作为资源库方法的返回类型。 -
在使用
@Query
时使用原生和已命名查询 -
Entity State-detection Strategies via
EntityInformation
. -
The use of
org.springframework.data.jpa.repository.Lock
Quarkus 团队正在探索弥合 JPA 和 Reactive 世界之间差距的各种备选方案。
Important Technical Note
请注意,Quarkus 中的 Spring 支持不会启动 Spring 应用程序上下文,也不会运行任何 Spring 基础架构类。Spring 类和注释仅用于读取元数据和/或用作用户代码方法返回类型或参数类型。
More Spring guides
Quarkus 还有更多 Spring 兼容性功能。请参阅以下指南以获取更多详情: