仅用 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 程序通常需要:
- Jakarta Persistence API。
- 与该 API 版本兼容的 provider。
- 一个可连接的关系数据库,以及对应的 JDBC 驱动。
- 实体映射、持久化单元和数据库连接配置。
- 所需的数据表,或者明确配置的建表机制。
题目中的 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,并允许使用相应的数据库环境。
备注:内容仅供参考。
