PAYATHON 2026

仅用 Jakarta Persistence API 能否执行 EntityManager CRUD?

技术老齐

当前限制下,无法执行实际的 EntityManager CRUD

题目只允许添加 jakarta.persistence-api,同时禁止使用任何 JPA provider 和数据库。在这些限制下,无法完成持久化操作。

问题答案
只有 API 依赖,能创建可用的 EntityManagerFactory 吗?在没有其他运行时组件的独立 Java 程序中,不能
没有 provider,能执行 persist()、find()、remove() 和 JPQL 吗?不能,API 包没有提供这些操作的持久化实现
完全没有数据库,能完成常规 JPA CRUD 吗?不能,标准的关系数据库持久化需要数据库作为存储
最低需要什么?API、兼容的 provider、数据库及其 JDBC 驱动,以及持久化单元配置

如果练习严格禁止所有数据库,也不提供 provider,可以学习实体映射、编写 DAO 接口和调用代码,或做模拟测试,但无法验证实际的 JPA 持久化行为。

Jakarta Persistence API、provider 和数据库分别负责什么?

jakarta.persistence-api 提供编程时使用的类型,例如:

@Entity
@Id
EntityManager
EntityManagerFactory
EntityTransaction

它也包含 Persistence 这样的引导类。API 包本身不负责对象与数据库记录之间的转换,也不实现 SQL 执行、实体状态管理和 JPQL 查询。

Hibernate ORM、EclipseLink 等 provider 实现了这些功能。应用通过 EntityManager 接口调用 provider,provider 再根据映射信息访问数据库。

在常见的关系数据库场景中,调用关系是:

应用代码
    → Jakarta Persistence API
    → JPA provider
    → JDBC 驱动
    → 数据库

JDBC 驱动负责与相应数据库通信,数据库负责保存记录和执行数据库事务。添加 Hibernate 依赖不会自动得到数据库,添加 JDBC 驱动也不会自动启动数据库。

实体上的注解提供映射信息:

@Entity
@Table(name = "bankaccount")

这些注解本身不会创建存储空间或启动数据库。

为什么 createEntityManagerFactory() 不能只靠 API 工作?

调用以下代码会启动 provider 的发现和工厂创建流程:

EntityManagerFactory emf =
        Persistence.createEntityManagerFactory("BankPU");

BankPU 是持久化单元名称,通常对应 META-INF/persistence.xml 中的配置。

API 包中的引导代码需要找到能处理该持久化单元的 provider。如果运行时只有 API 包,这一步就会失败,无法得到可用的 EntityManagerFactory。通常会抛出 jakarta.persistence.PersistenceException,提示找不到对应的持久化 provider,具体异常信息取决于所用版本。即使补上 persistence.xml,也仍然需要 provider 提供实际实现。

Maven 直接依赖与完整运行环境需要分开看。如果应用服务器已经提供了 provider,项目可以只声明 API 依赖,此时运行时使用的是服务器提供的 provider。

持久化上下文为什么不能代替数据库?

EntityManager 管理的持久化上下文保存着受管理的实体及其状态,但它不是可独立使用的内存数据库。

例如:

  • find() 可能直接返回持久化上下文中已有的实体。
  • persist() 会使新实体进入受管理状态,数据库写入可能发生在后续阶段。
  • 修改受管理实体的字段后,provider 可以在同步时生成更新操作。

因此,某次方法调用未必立即执行 SQL,完整的写入、查询和提交仍需要 provider 及其配置的存储环境。

题目中的主键策略也依赖数据库:

@GeneratedValue(strategy = GenerationType.IDENTITY)

IDENTITY 使用数据库的 identity 列生成主键。改为手动设置主键,可以取消自动生成主键的需求,但仍然缺少 provider 和数据库。

允许数据库后,最低需要怎样配置?

独立运行的 Java SE 程序通常需要:

  1. Jakarta Persistence API。
  2. 与该 API 版本兼容的 provider。
  3. 一个可连接的关系数据库,以及对应的 JDBC 驱动。
  4. 实体映射、持久化单元和数据库连接配置。
  5. 所需的数据表,或者明确配置的建表机制。

题目中的 4.0.0-M7 是里程碑版本。选择 provider 时,要核对它支持的 Jakarta Persistence 版本和 Java 版本,不能假定任意 Hibernate 或 EclipseLink 版本都能与它搭配。

配置持久化单元

使用 Maven 标准目录结构时,文件通常放在:

src/main/resources/META-INF/persistence.xml

下面的 Hibernate 配置模板采用 Persistence 3.0 XML 描述符格式。使用前,应确认所选 provider 支持该格式,并按其兼容要求选择 API 依赖版本。

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
             version="3.0">

    <persistence-unit name="BankPU"
                      transaction-type="RESOURCE_LOCAL">

        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <class>com.example.AccountEntityClas</class>

        <properties>
            <property name="jakarta.persistence.jdbc.driver"
                      value="YOUR_JDBC_DRIVER_CLASS"/>
            <property name="jakarta.persistence.jdbc.url"
                      value="YOUR_JDBC_URL"/>
            <property name="jakarta.persistence.jdbc.user"
                      value="YOUR_DATABASE_USER"/>
            <property name="jakarta.persistence.jdbc.password"
                      value="YOUR_DATABASE_PASSWORD"/>
        </properties>
    </persistence-unit>
</persistence>

使用时,需要替换实体的完整类名和连接参数,并添加对应的 provider、JDBC 驱动依赖。模板没有配置自动建表,数据库中需要已有与实体映射一致的表,包括支持 IDENTITY 的主键列。

RESOURCE_LOCAL 对应题目中的事务写法:

em.getTransaction().begin();
em.getTransaction().commit();

如果运行环境使用 JTA 或容器管理事务,应采用相应的事务管理方式。

执行持久化操作

配置好依赖、数据库和持久化单元后,就可以执行以下持久化代码:

EntityManagerFactory emf =
        Persistence.createEntityManagerFactory("BankPU");

try {
    EntityManager em = emf.createEntityManager();

    try {
        EntityTransaction tx = em.getTransaction();

        try {
            tx.begin();

            AccountEntityClas account =
                    new AccountEntityClas("Alice", 100.0, "123456789");

            em.persist(account);

            tx.commit();
        } catch (RuntimeException ex) {
            if (tx.isActive()) {
                tx.rollback();
            }
            throw ex;
        }
    } finally {
        em.close();
    }
} finally {
    emf.close();
}

EntityManagerFactory 通常由应用创建并复用,在应用结束时关闭。上例将创建和关闭放在一起,以展示完整的资源生命周期。

查询可以写成:

AccountEntityClas account =
        em.find(AccountEntityClas.class, id);

JPQL 查询使用实体名和 Java 属性名:

List<AccountEntityClas> accounts = em.createQuery(
        "select a from AccountEntityClas a where a.balance >= :minimum",
        AccountEntityClas.class
    )
    .setParameter("minimum", 100.0)
    .getResultList();

这里默认实体名为 AccountEntityClas。bankaccount 是用于 SQL 映射的数据库表名,不能直接用它替换 JPQL 中的实体名。

删除时,应在事务中操作受管理的实体:

em.getTransaction().begin();

AccountEntityClas account =
        em.find(AccountEntityClas.class, id);

if (account != null) {
    em.remove(account);
}

em.getTransaction().commit();

这段删除代码省略了异常处理,实际使用时应像前面的示例一样,在失败后回滚事务。

更新通常通过修改受管理实体的属性完成。provider 会在同步时检测变化,无须额外调用 update() 方法。

如果练习坚持禁止数据库,可以做到哪一步?

可以编写实体、DAO 接口和调用 EntityManager 的代码,也可以用测试替身检查 DAO 是否调用了预期方法。例如,检查保存账户时是否调用了 persist(account)。

这类测试无法证明实体映射正确、JPQL 能执行、主键能生成,或事务能真正提交。用 Map 保存对象可以练习 Java 层面的增删改查,但它没有实现 Jakarta Persistence 的持久化语义。

练习需要明确验收范围:如果只要求编写使用 API 的代码,可以完成;如果要求实际运行 EntityManager CRUD 并验证存储结果,就需要提供 provider,并允许使用相应的数据库环境。

备注:内容仅供参考。