跳转到主要内容

本指南向您展示如何使用Jakarta Persistence来存储和检索数据。

我们首先将简要介绍下我们想要构建的系统。接下来,我们将构建一个能够进行数据处理的RESTful web服务,使用Jakarta Persistence将其存储在数据库中,并作为REST端点提供服务。 对于不熟悉RESTful web服务的人,建议阅读我们的之前的文章

我们将构建一个处理咖啡产品数据的应用程序。该服务将处理一条JSON数据(包含产品ID、名称和价格)。以下是JSON数据的示例:

{
  "id": 1,
  "name": "Coffee-A",
  "price": "2.75"
}

应用程序将存储数据,并提供一些REST端点,用来进行基本的CRUD(创建、读取、更新和删除)操作。然后,我们可以根据需要对其进一步定制和改进。

好的,现在我们已经明确了我们的要求,您需要按照以下步骤操作:

设置您的开发环境:

  • 安装Java开发工具包(JDK)。请确保您安装了Java SE 11或更高版本(Java SE 11和Java SE 17我们都测试过)。您可以选择任何供应商的发行版,也可以从Adoptium获取。
  • 安装支持Jakarta EE的应用服务器。下载任何Jakarta EE兼容的产品
  • 安装Maven 3或更高版本。

要安装JDK和Maven,我们可以使用SDKMan。我们可以按照安装指南中提到的步骤进行操作。

如何完成本指南

在这个入门指南中,您可能会使用Eclipse Starter for Jakarta EE完成每个步骤,也可以跳过您已经知道的基础设置。您也可以从IDE开始,或从知名的Maven原型中选择项目结构。

使用Eclipse Starter for Jakarta EE创建一个新项目

要使用Eclipse Starter for Jakarta EE,我们需要执行以下步骤:

  1. 导航至https://start.jakarta.ee。此服务将为应用程序设置所有必要的依赖项。当前版本的Starter仅支持Maven。将来,我们可能能够在Gradle和Maven之间进行选择。
    Eclipse Starter for Jakarta EE表单的截图,相关选项已选定
  2. 从可用选项中选择所需的Jakarta EE版本。目前,选项包括Jakarta EE 8、Jakarta EE 9.1和Jakarta EE 10。 此外,您可以选择Jakarta EE Platform或Jakarta EE的Profile版本(Web、Core等)。
  3. 对于此项目,我们选择了Jakarta EE 10 Platform、Java SE 11和WildFly作为运行时。
  4. 选择了所需的选项后,点击生成按钮,将产生项目结构和示例代码,我们可以用以构建并运行。

探索代码结构

解压缩生成的代码,将得到一个应用程序的结构。可以在自己喜欢的IDE中打开,然后可以从命令行运行。

.
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
    └── main
        ├── java
        │   └── org
        │       └── eclipse
        │           └── jakarta
        │               └── hello
        │                   ├── Hello.java
        │                   └── HelloWorldResource.java
        └── webapp
            ├── WEB-INF
            │   └── web.xml
            ├── images
            │   └── jakartaee_logo.jpg
            └── index.html

生成的源码附带了一个嵌入式的Maven wrapper。因此 ,需要首先确保在Unix环境(Linux或Mac)中运行以下命令:

$ chmod +x mvnw

由于我们使用WildFly作为运行时,以下命令将运行应用程序:

$ ./mvnw clean package wildfly:run

另一方面,如果您安装了Maven,可以运行以下命令:

$ mvn clean package wildfly:run

对于Windows,不需要运行chmod命令;而是使用mvnw.cmd代替mvnw

现在,如果您使用以下URL在浏览器中访问,将看到结果。

http://localhost:8080/jakartaee-hello-world

我们已经在之前的文章中介绍了如何测试,不再赘述。

设置Jakarta Persistence

鉴于我们的目标是学习使用Jakarta Persistence,让我们首先了解Jakarta Persistence本质上是什么。Java Persistence API是一种Java编程语言规范,它提供了一个对象关系映射(ORM)框架,用于管理Java应用程序中的关联数据。它简化了数据库访问,并使开发人员能够使用对象和类而不是SQL语句。它曾被称为Java Persistence API,简称JPA。

接下来,我们通过创建我们的第一个实体开始相关探索。实体是一个Java类,对应于数据库中的一个表。本文将使用嵌入式H2数据库来存储我们的数据。

org.eclipse.jakarta.model.entity包中创建一个Coffee类。

package org.eclipse.jakarta.model.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import java.io.Serializable;

@Entity
public class Coffee implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String price;

    //getter和setter
}

正如您所看到的,我们的实体类包含几个注解。@Entity注解将一个Java类指定为实体类,代表一个数据库表,其中类字段对应于表列。@Id注解将实体类中的字段或属性标记为主键。此外,您可以使用@GeneratedValue注解与@Id来表示主键值应该自动生成。

Jakarta Persistence背后的主要思想是允许Java程序员使用对象。他们可以创建这些实体的实例,Jakarta Persistence将把它们转换成数据并存储在数据库中。当需要一个对象时,Jakarta Persistence检索数据并将其转换为一个易于使用的对象。

如何将实体连接到DataSource?

在使用Jakarta Persistence之前,首先需要创建一个名为persistence.xml的Jakarta Persistence配置文件,用以配置数据库连接。这个文件可以放在我们项目的resources/META-INF文件夹中。

persistence.xml文件允许指定JDBC连接设置或Datasource JNDI1 名称。例如,我们可以在persistence.xml中指定使用WildFly默认的H2数据库

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_1.xsd"
             version="3.0">
    
    <persistence-unit name="coffees">
        <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
        <properties>
            <property name="jakarta.persistence.schema-generation.database.action" value="drop-and-create" />
            <property name="jakarta.persistence.sql-load-script-source" value="META-INF/initial-data.sql" />
            <property name="eclipselink.logging.level.sql" value="FINE" />
            <property name="eclipselink.logging.parameters" value="true" />
            <property name="hibernate.show_sql" value="true" />
        </properties>
    </persistence-unit>
</persistence>

resources/META-INF文件夹中创建一个persistence.xml文件,并复制上面的内容。

这个persistence.xml文件设置了一个名为"coffees"的持久性单元(persistence unit )。文件中指定了JDBC数据源的JNDI名称,用于数据库连接管理。对于H2数据库,默认情况下在WildFly中是"java:jboss/datasources/ExampleDS"。JNDI名称的配置通常位于WILDFLY_HOME/standalone/configuration目录中的standalone.xml文件中,其中WILDFLY_HOME是WildFly的安装目录。然而,对于这个例子,不需要额外的配置,因为我们使用了默认的JNDI名称。其他运行时可能有不同的配置,可以在其文档中找到。

<properties>元素包含几个属性,用以配置Jakarta Persistence提供者(provider)行为。例如,persistence.xml文件中的"jakarta.persistence.schema-generation.database.action"属性指定Jakarta Persistence提供者在生成数据库模式(schema) 时要采取的操作。其他的选项包括:

  • none:Jakarta Persistence提供者不会生成数据库模式。
  • create:Jakarta Persistence提供者将创建数据库模式。
  • drop:Jakarta Persistence提供者将删除数据库模式。
  • drop-and-create:Jakarta Persistence提供者将删除现有数据库模式并创建一个新的。

对于这个属性,还有其他可用的选项,如"create-only"、“drop-and-create-script"和"create-script”。选项的选择取决于用例和应用程序的具体要求。在本例中,在persistence.xml文件中选择了"drop-and-create"选项,它在开发环境中每次运行应用程序时都会删除现有数据库模式并创建一个新的。

“jakarta.persistence.sql-load-script-source"属性指定在持久性单元初始化时要执行的SQL脚本的位置。在这里,脚本位于META-INF/initial-data.sql文件中。因此,创建一个名为initial-data.sql的文件,并在其中输入以下SQL语句。

INSERT INTO coffee (name, price) VALUES ('Coffee-A', 2.75);
INSERT INTO coffee (name, price) VALUES ('Coffee-B', 1.99);
INSERT INTO coffee (name, price) VALUES ('Coffee-C', 3.25);
INSERT INTO coffee (name, price) VALUES ('Coffee-D', 2.99);

这个脚本的目的是确保当服务器启动时,数据库中就有一些数据可以用于测试或演示目的。

其他属性用于配置Jakarta Persistence提供者的日志记录,例如"eclipselink.logging.level.sql"和"eclipselink.logging.parameters”。最后,“hibernate.show_sql"属性用于启用Hibernate Jakarta Persistence提供者2 的SQL查询日志记录。

设置Jakarta Persistence仓库

现在我们将创建一个CafeRepository类,负责处理Coffee实体的创建、读取、更新和删除(CRUD)操作。

package org.eclipse.jakarta.model;

import jakarta.ejb.Stateless;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.eclipse.jakarta.model.entity.Coffee;

import java.lang.invoke.MethodHandles;
import java.util.List;
import java.util.Optional;
import java.util.logging.Logger;

@Stateless
public class CafeRepository {
    private static final Logger logger = Logger.getLogger(MethodHandles.lookup().lookupClass().getName());

    @PersistenceContext
    private EntityManager em;

    public Coffee create(Coffee coffee) {
        logger.info("Creating coffee " + coffee.getName());
        em.persist(coffee);

        return coffee;
    }

    public List<Coffee> findAll() {
        logger.info("Getting all coffee");
        return em.createQuery("SELECT c FROM Coffee c", Coffee.class).getResultList();
    }

    public Optional<Coffee> findById(Long id) {
        logger.info("Getting coffee by id " + id);
        return Optional.ofNullable(em.find(Coffee.class, id));
    }

    public void delete(Long id) {
        logger.info("Deleting coffee by id " + id);
        var coffee = findById(id)
            .orElseThrow(() -> new IllegalArgumentException("Invalid coffee Id:" + id));
        em.remove(coffee);
    }

    public Coffee update(Coffee coffee) {
        logger.info("Updating coffee " + coffee.getName());
        return em.merge(coffee);
    }
}

该类使用了@Stateless注解,使其成为一个无状态session bean。 无状态session bean是企业级Jakarta bean(EJB)的一种,用于在Jakarta EE应用程序中实现业务逻辑。 无状态session bean被设计用于不需要在方法调用之间与客户端保持任何对话状态的场景。换句话说,无状态session bean不会记住任何客户端特定的数据。

让我们一步一步地分解:

EntityManager:

EntityManager是Jakarta Persistence中用于管理实体的主要接口。它使用@PersistenceContext注解,这将自动将EntityManager的实例注入到类中。

create():

此方法以Coffee对象为参数,记录一条信息性(informational)消息,并使用EntityManager持久化对象。返回已持久化的Coffee对象。

findAll():

此方法从数据库中检索所有Coffee实体。它创建一个Jakarta Persistence查询,执行后,会返回Coffee对象的列表。

findById():

此方法以长整型ID为参数,记录一条信息性消息,并使用EntityManager搜索指定ID的Coffee实体。如果找到,它返回一个包含实体的Optional<Coffee>;否则,返回一个空的Optional

delete():

此方法以长整型ID为参数,记录一条信息性消息,并尝试查找指定ID的Coffee实体。如果找到,它使用EntityManager移除实体。如果未找到实体,则抛出一个带有无效ID消息的IllegalArgumentException

update():

此方法以Coffee对象为参数,记录一条信息性消息,并使用EntityManager更新数据库中现有的Coffee实体。返回更新后的Coffee对象。

为CRUD方法添加REST端点

最后,我们将添加一个REST端点,以便能够从远程REST客户端访问我们的Coffee服务。

package org.eclipse.jakarta.rest;

import jakarta.inject.Inject;
import jakarta.persistence.PersistenceException;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.Response;
import org.eclipse.jakarta.model.CafeRepository;
import org.eclipse.jakarta.model.entity.Coffee;

import java.lang.invoke.MethodHandles;
import java.util.List;
import java.util.logging.Logger;

@Path("coffees")
public class CafeResource {
    private final Logger logger = Logger.getLogger(MethodHandles.lookup().lookupClass().getName());

    @Inject
    private CafeRepository cafeRepository;

    @GET
    @Path("{id}")
    @Produces("application/json")
    public Coffee findCoffee(@PathParam("id") Long id) {
        logger.info("Getting coffee by id " + id);
        return cafeRepository.findById(id)
            .orElseThrow(() -> new WebApplicationException(Response.Status.NOT_FOUND));
    }

    @GET
    @Produces("application/json")
    public List<Coffee> findAll() {
        logger.info("Getting all coffee");
        return cafeRepository.findAll();
    }

    @POST
    @Consumes("application/json")
    @Produces("application/json")
    public Coffee create(Coffee coffee) {
        logger.info("Creating coffee " + coffee.getName());
        try {
            return cafeRepository.create(coffee);
        } catch (PersistenceException ex) {
            logger.info("Error creating coffee " + coffee.getName());
            throw new WebApplicationException(Response.Status.BAD_REQUEST);
        }
    }

    @DELETE
    @Path("{id}")
    public void delete(@PathParam("id") Long id) {
        logger.info("Deleting coffee by id " + id);
        try {
            cafeRepository.delete(id);
        } catch (IllegalArgumentException e) {
            logger.info("Error deleting coffee by id " + id);
            throw new WebApplicationException(Response.Status.NOT_FOUND);
        }
    }

    @PUT
    @Consumes("application/json")
    @Produces("application/json")
    public Coffee update(Coffee coffee) {
        logger.info("Updating coffee " + coffee.getName());
        try {
            return cafeRepository.create(coffee);
        } catch (PersistenceException ex) {
            logger.info("Error updating coffee " + coffee.getName());
            throw new WebApplicationException(Response.Status.BAD_REQUEST);
        }
    }
}

CafeResource类是一个RESTful web服务,用于使用Jakarta RESTful Web Services管理Coffee实体。该类暴露了各种HTTP端点,用于执行CRUD操作,如创建、检索、更新和删除Coffee对象。

  1. @Path("coffees"):此注解将web服务的基础路径设置为"/coffees"。此类中的所有HTTP端点都将相对于此路径。
  2. @Inject:此注解用于注入CafeRepository类的实例,该类处理Coffee实体的持久化操作。
  3. @GET@POST@PUT@DELETE:这些注解定义了类中相应方法的HTTP方法(例如,findCoffee()findAll()create()delete()update())。它们将Java方法映射到HTTP操作。
  4. @Path("{id}"):此注解与@GET@DELETE注解结合使用,以指定通过其ID检索或删除Coffee实体的路径参数“id”。
  5. @Produces@Consumes:这些注解用于定义方法可以(作为响应)生成或(作为输入)消耗的媒体类型(例如,"application/json")。在本例中,方法接受并返回Coffee实体的JSON表示(representations)。

该类还使用Logger记录信息性消息,并通过抛出WebApplicationException处理异常,使用适当的HTTP状态码(例如,Response.Status.NOT_FOUNDResponse.Status.BAD_REQUEST)处理错误。

让我们测试一下

在将上述类添加到您的项目后,执行以下命令以构建并运行应用程序:

$ mvn clean package wildfly:run

此命令将清除任何以前的构建,编译和打包应用程序,然后将其部署到WildFly应用服务器。

如果一切设置正确,将可以通过Web浏览器或REST客户端访问应用程序。您可以使用cURL作为命令行REST客户端与服务交互。

以下是使用给定的基础URL和JSON有效载荷在CafeResource类中进行不同CRUD操作的cURL命令:

创建新的Coffee实体(HTTP POST):

curl -X POST \
 http://localhost:8080/jakartaee-hello-world/rest/coffees \
 -H 'Content-Type: application/json' \
 -d '{\n "id": 1,\n "name": "Coffee-A",\n "price": "2.75"\n}'

检索所有Coffee实体(HTTP GET):

curl -X GET \
 http://localhost:8080/jakartaee-hello-world/rest/coffees \
 -H 'Content-Type: application/json'

通过ID检索特定的Coffee实体(HTTP GET):

curl -X GET \
 http://localhost:8080/jakartaee-hello-world/rest/coffees/{id} \
 -H 'Content-Type: application/json'

更新现有的Coffee实体(HTTP PUT):

curl -X PUT \
 http://localhost:8080/jakartaee-hello-world/rest/coffees \
 -H 'Content-Type: application/json' \
 -d '{\n "id": 1,\n "name": "Coffee-A",\n "price": "2.75"\n}'

通过ID删除Coffee实体(HTTP DELETE):

curl -X DELETE \
 http://localhost:8080/jakartaee-hello-world/rest/coffees/{id} \
 -H 'Content-Type: application/json'

使用检索或删除特定Coffee实体的cURL命令时,请确保将{id}占位符替换为实际的ID值。

结语

恭喜您!您刚刚学习了如何使用Jakarta Persistence开发web服务。


  1. Java命名和目录接口(JNDI)是一种Java API,用于一种目录服务,它允许Java软件客户端通过名称发现和查找数据和对象。
  2. Jakarta Persistence是一种具有多个兼容实现的Java ORM规范,例如EclipseLink 3.0.0和Hibernate ORM 6.0.0.Final。

返回顶部