Your second Quarkus application

本教程向你展示如何创建一个向数据库中写入和读取数据的应用程序。你将使用 Dev Services,因此你实际上不必自己下载、配置,甚至启动数据库。你还可以使用 Panache,即 Hibernate ORM 之上的一层,以 упростить чтение和写入数据。

This tutorial shows you how to create an application which writes to and reads from a database. You will use Dev Services, so you will not actually download, configure, or even start the database yourself. You will also use Panache, a layer on top of Hibernate ORM, to make reading and writing data easier.

本指南可以帮助你:

This guide helps you:

  • Read and write objects to a database

  • Develop and test against services with zero configuration

Prerequisites

Unresolved directive in getting-started-dev-services.adoc - include::{includes}/prerequisites.adoc[]

本教程基于你编写 your first Quarkus application 时学到的知识。你不需要该应用程序的代码,但务必理解这些概念。

This tutorial builds on what you learned writing your first Quarkus application. You will not need the code from that application, but make sure you understand the concepts.

Solution

我们建议你按照 Bootstrapping the project 及后续章节中的说明逐步创建应用程序。

We recommend that you follow the instructions from Bootstrapping the project onwards to create the application step by step.

但是,你可以直接转到已完成的示例。

However, you can go right to the completed example.

下载 {quickstarts-archive-url}[归档] 或克隆 git 存储库:

Download an {quickstarts-archive-url}[archive] or clone the git repository:

git clone {quickstarts-clone-url}

解决方案位于 getting-started-dev-services {quickstarts-tree-url}/getting-started-dev-services[目录] 中。

The solution is located in the getting-started-dev-services {quickstarts-tree-url}/getting-started-dev-services[directory].

Outline steps

  • Bootstrap the application

  • Update the application to read user input

  • Create a Panache Entity

  • Read and write the entity

  • Configure an external database using a profile

Setting up an interactive application

Bootstrapping the project

创建新 Quarkus 项目的最简单方法是打开一个终端并运行以下命令:

The easiest way to create a new Quarkus project is to open a terminal and run the following command:

Unresolved directive in getting-started-dev-services.adoc - include::{includes}/devtools/create-app.adoc[]

有关在生成的应用程序中的内容的说明,请参阅 First application guide.

For an explanation of what’s in the generated application, see the First application guide.

Running the application

在 dev 模式下启动应用程序

Launch the application in dev mode

Unresolved directive in getting-started-dev-services.adoc - include::{includes}/devtools/dev.adoc[]

应用程序启动后,访问 [role="bare"][role="bare"]http://localhost:8080/hello. 它应该显示消息“Hello from Quarkus REST”。

Once the application is up, visit [role="bare"]http://localhost:8080/hello. It should show a "Hello from Quarkus REST" message.

Accepting user input

让我们使应用程序更具交互性。在 IDE 中打开项目,然后导航到 src/main/java/org/acme/GreetingResource.java.在 hello 方法中添加一个查询参数。(org.jboss.resteasy.reactive.RestQuery 注释就像 Jakarta REST `@QueryParam`注释,只是您不需要重复参数名称。)

Let’s make the application a bit more interactive. Open the project in your IDE and navigate to src/main/java/org/acme/GreetingResource.java Add a query param in the hello method. (The org.jboss.resteasy.reactive.RestQuery annotation is like the Jakarta REST @QueryParam annotation, except you don’t need to duplicate the parameter name.)

public String hello(@RestQuery String name) {
    return "Hello " + name;
}

访问 [role="bare"][role="bare"]http://localhost:8080/hello?name=Bloom.

您应该看到一条个性化消息:Hello Bloom.

You should see a personalised message: Hello Bloom.

Fixing the tests

在您的 Quarkus 终端中,键入“r”来运行测试。您应该看到您的应用程序更改中断了测试!

In your Quarkus terminal, type 'r' to run the tests. You should see that your application changes broke the tests!

若要修复测试,请打开 `src/test/java/org/acme/GreetingResourceTest.java`并替换

To fix the tests, open src/test/java/org/acme/GreetingResourceTest.java and replace

             .body(is("Hello from Quarkus REST"));

带有

with

             .body(containsString("Hello"));

这仍然验证了 HTTP 端点,但它对预期的输出更灵活。您应该在终端中看到测试现在通过了。

This still validates the HTTP endpoint, but it’s more flexible about the expected output. You should see in your terminal that the tests are now passing.

Adding persistence

Creating a Panache Entity

  1. To add the persistence libraries, run

Unresolved directive in getting-started-dev-services.adoc - include::{includes}/devtools/extension-add.adoc[]

应用程序将记录它问候的人的姓名。通过创建一个 Greeting.java 类来定义一个实体。添加以下内容:

The application will record the names of people it greets. Define an Entity by creating a Greeting.java class. Add the following content:

import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Entity;

@Entity
public class Greeting extends PanacheEntity {
    public String name;
}

实体使用 Panache,它是 Hibernate ORM 上的一层。扩展 PanacheEntity 引入了用于读取、写入和查找数据的一系列方法。因为所有数据访问方法都在 Greeting 实体上,而不是在单独的数据访问类上,所以这是活动记录模式的示例。

The entity makes use of Panache, a layer on top of Hibernate ORM. Extending PanacheEntity brings in a range of methods for reading, writing, and finding data. Because all the data access methods are on the Greeting entity, rather than on a separate data access class, this is an example of the active record pattern.

Greeting 表将有一列,一个名为 name 的字段。

The Greeting table will have one column, a field called name.

Writing data

要使用新的实体,请更新 hello 方法以开始写入一些数据。

To use the new entity, update the hello method to start writing some data.

将该方法更改为以下内容:

Change the method to the following:

@GET
@Transactional
@Produces(MediaType.TEXT_PLAIN)
public String hello(@QueryParam("name") String name) {
   Greeting greeting = new Greeting();
   greeting.name = name;
   greeting.persist();
   return "Hello " + name;
}

别忘记 @Transactional 注释,它确保写入包装在一个事务中。

Don’t forget the @Transactional annotation, which ensures writes are wrapped in a transaction.

GETs should not change application state.

通常,你不应该在 GET REST 方法中进行状态更新,但这里可以让尝试变得更简单。我们假设正在编写的是一个日志“副作用”,而不是一个有意义的状态更改!

GETs should not change application state.

Generally, you shouldn’t do state updates in a GET REST method, but here it makes trying things out simpler. Let’s assume what’s being written is a logging "side effect", rather than a meaningful state changes!

通过访问 [role="bare"][role="bare"]http://localhost:8080/hello?name=Bloom 尝试更新的端点。你应该看见“Hello Bloom”消息。

Try out the updated endpoint by visiting [role="bare"]http://localhost:8080/hello?name=Bloom. You should see a "Hello Bloom" message.

Reading data

尽管新持久性代码看起来运行时没有任何错误,但你如何知道正在向数据库写入任何内容?

Although the new persistence code seems to be working without errors, how do you know anything is being written to the database?

GreetingResource 添加第二个 REST 方法。

Add a second REST method to GreetingResource.

@GET
@Path("names")
@Produces(MediaType.TEXT_PLAIN)
public String names() {
    List<Greeting> greetings = Greeting.listAll();
    String names = greetings.stream().map(g-> g.name)
       .collect(Collectors.joining (", "));
    return "I've said hello to " + names;
}

要试用它,请访问 [role="bare"][role="bare"]http://localhost:8080/hello?name=Bloom,然后 [role="bare"][role="bare"]http://localhost:8080/hello/names。

To try it out, visit [role="bare"]http://localhost:8080/hello?name=Bloom, and then [role="bare"]http://localhost:8080/hello/names.

你应该会看到以下信息:“我已向 Bloom 说你好”。

You should see the following message: "I’ve said hello to Bloom".

Example 1. a container runtime is required.

请务必记住,你需要备有可用的容器运行时,否则此时你将开始在 Quarkus 日志中看到错误。

Don’t forget that you need to have a container runtime available, or you will start seeing failures in the Quarkus logs at this point.

Dev services

读写数据库似乎工作正常,但这是有点意外的。PostgreSQL 数据库哪来的?你没有设置任何东西。

Reading and writing to the database seems to be working well, but that’s a bit unexpected. Where did a PostgreSQL database come from? You didn’t set anything up.

该数据库正在使用 Dev Services 进行管理。Dev Services 会负责停止和启动你的应用程序所需的各种服务。由于你包含了 jdbc-postgresql 依赖关系,所以数据库将是容器化的 PostgreSQL 数据库。如果你改用 jdbc-mysql,你的数据库将成为容器化的 MySQL 数据库。

The database is being managed using Dev Services. Dev Services take care of stopping and starting services needed by your application. Because you included the jdbc-postgresql dependency, the database is a containerised PostgreSQL database. If you’d added jdbc-mysql instead, you would have gotten a containerised MySQL database.

如果你愿意,可以使用你的容器工具查看正在运行的容器。例如,如果你正在使用 Docker,请运行 docker ps;如果你正在使用 podman,请运行 podman ps。你应该会看到类似以下内容的输出:

If you like, use your container tool to see what containers are running. For example, if you’re using Docker, run docker ps, and for podman, run podman ps. You should see something like the following:

ff88dcedd899  docker.io/library/postgres:14  postgres -c fsync...  20 minutes ago  Up 20 minutes  0.0.0.0:34789->5432/tcp  nostalgic_bassi

停止 Quarkus 并再次运行 docker ps。你应该看不到任何正在运行的内容(容器关闭可能需要几分钟时间)。Quarkus 应用程序停止后,该容器会自动停止。

Stop Quarkus and run docker ps again. You should see nothing running (it may take a few moments for containers to shut down). Quarkus will automatically stop the container when your application stops.

Initialising services

如果你对你的代码进行更多调整,你可能会注意到,有时在进行应用程序更改之后,[role="bare"][role="bare"]http://localhost:8080/hello/names 不会列出任何名称。出了什么事?默认情况下,在 dev 模式下,使用 Dev Services 数据库时,Quarkus 会将 Hibernate ORM 数据库生成配置为 drop-and-create。有关更多详情,请参阅 Hibernate configuration reference。如果某个代码更改触发了应用程序重新启动,则数据库表将被删除并重新创建。

If you play with your code some more, you may notice that sometimes, after making an application change, [role="bare"]http://localhost:8080/hello/names doesn’t list any names. What’s going on? By default, in dev mode, with a Dev Services database, Quarkus configures Hibernate ORM database generation to be drop-and-create. See the Hibernate configuration reference for more details. If a code change triggers an application restart, the database tables will be dropped (deleted) and then re-created.

这很方便,但是如果你更希望数据库始终包含内容,那该怎么办呢?这样会使测试变得更容易。如果你提供了一个 import.sql 文件,Quarkus 会使用它在每次启动时对数据库进行初始化。

This is convenient, but what if you’d prefer the database to always have content? That would make testing easier. If you provide an import.sql file, Quarkus will use that to initialise the database on each start.

  1. Make a src/main/resources/import.sql file in your project

  2. Add the following SQL statements:

INSERT INTO Greeting(id, name)
VALUES (nextval('Greeting_SEQ'), 'Alice');
INSERT INTO Greeting(id, name)
VALUES (nextval('Greeting_SEQ'), 'Bob');

然后在你的 dev 模式会话中点击 s 以强制进行完整重启。然后访问 [role="bare"][role="bare"]http://localhost:8080/hello/names。

Now, hit s in your dev mode session, to force a full restart. Then visit [role="bare"]http://localhost:8080/hello/names.

你会看到 Alice 和 Bob 始终包含在名称列表中。

You’ll see that Alice and Bob are always included in the list of names.

Controlling Dev Services

Using an external database instead

如果你更愿意使用由你自己管理的外部数据库,该怎么办呢?将以下内容添加到 src/main/resources/application.properties

What if you’d rather use an external database that you manage yourself? Add the following to src/main/resources/application.properties:

# configure your datasource
quarkus.datasource.db-kind = postgresql
quarkus.datasource.username = leopold
quarkus.datasource.password = bloom
quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:5432/mydatabase

这会告诉 Quarkus 不启用 Dev Service,因为你拥有自己的数据库。你不必担心启动数据库,因为你只是想看看如何更改配置。

This tells Quarkus that you don’t want it to start a Dev Service, because you have your own database. You don’t need to worry about starting the database, because you’re just seeing how to change the configuration.

访问 [role="bare"][role="bare"]http://localhost:8080/hello/names. 您将收到红色错误屏幕,而不是名称列表。在运行 Quarkus 的终端中,您将看到以下堆栈错误消息:

Visit [role="bare"]http://localhost:8080/hello/names. Instead of a list of names, you’ll get a red error screen. In the terminal where Quarkus is running. you’ll see the following stack error message:

2023-06-28 19:18:22,880 ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (executor-thread-1) HTTP Request to /hello?name=fred failed, error id: 4f9b5ce6-3b08-41c5-af36-24eee4d1dd2b-2: org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection [Connection to localhost:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.] [n/a]
        at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:98)
        at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:56)
...

这十分合理;您已禁用数据库开发服务,但尚未启动您自己的数据库。

This makes sense; you’ve disabled the database Dev Service, but you haven’t started your own database.

Using profiles

除非您想要这样做,否则不必担心设置一个外部数据库来解决此连接错误。相反,您可以返回使用开发服务。它让生活变得更加容易!

Unless you want to, don’t worry about setting up an external database to resolve the connection error. Instead, you will go back to using the Dev Service. It made life easy!

但生产呢?您不会希望在生产中使用开发服务。事实上,Quarkus 仅在开发和测试模式中启动开发服务。

But what about production? You won’t want to use Dev Services in production. In fact, Quarkus only starts Dev Services in dev and test modes.

配置一个外部数据库并让它 only 在生产中使用,同时您也可以在其余时间使用开发服务,这难道不是很好吗?

Wouldn’t it be nice to configure an external database, but have it only used in production, so you could still use dev services the rest of the time?

在数据库配置中添加一个 %prod. 前缀。这意味着该配置仅适用于 prod profile

Add a %prod. prefix to the database configuration. This means the configuration only applies to the prod profile

该配置应如下所示:

The configuration should look like this:

# configure your datasource
%prod.quarkus.datasource.db-kind = postgresql
%prod.quarkus.datasource.username = leopold
%prod.quarkus.datasource.password = bloom
%prod.quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:5432/mydatabase

现在外部数据库将在生产模式中使用,而开发服务将在开发和测试模式中使用。

Now the external database will be used in prod mode, and Dev Services will be used in dev and test modes.

检查 [role="bare"][role="bare"]http://localhost:8080/hello/names. 它应该可以再次工作了,因为开发服务已被重新启用。请注意,对于所有这些更改,无需重新启动 Quarkus。

Check [role="bare"]http://localhost:8080/hello/names. It should be working again, because the Dev Services have been re-enabled. Notice that there was no need to restart Quarkus for any of these changes.

Summary

您已经将一个简单的 REST 应用程序更新为使用 Hibernate ORM 和 Panache 编写和读取数据库中的数据。数据已持久到一个“真实”的数据库,而您无需配置任何内容。

You’ve taken a simple REST application and updated it to write and read data from a database, using Hibernate ORM and Panache. The data was persisted to a 'real' database, without you having to configure anything.