一、简介
在本教程中,我们将讨论 如何使用 JPA 处理自动生成的 id 。在看实际示例之前,我们必须了解两个关键概念,即生命周期和 id 生成策略。
2.实体生命周期和ID生成
每个实体在其生命周期中有四种可能的状态。这些状态是 new*、 Managed 、 detached 和 *removed 。我们的重点将放在 新的 和 受管理的 状态上。 在对象创建期间,实体处于 新 状态 。因此, EntityManager 不知道该对象。调用 EntityManager 上的 persist 方法,对象 从新 状态转换为 托管 状态。此方法需要活跃的事务。
JPA 定义了四种 id 生成策略。我们可以将这四种策略分为两类:
- ID 是预先分配的,并且在 提交 之前可供 EntityManager 使用
- 事务 提交 后分配 ID
有关每种 id 生成策略的更多详细信息,请参阅我们的文章JPA 何时设置主键。
3. 问题陈述
返回对象的 id 可能会成为一项繁琐的任务。我们需要了解上一节提到的原理,才能避免出现问题。 根据 JPA 配置,服务可能返回 id 为零(或 null)的对象 。重点将放在服务类实现以及不同的修改如何为我们提供解决方案上。
我们将使用 JPA 规范和 Hibernate 作为其实现来创建一个 Maven 模块。为简单起见,我们将使用 H2 内存数据库。
让我们首先创建一个域实体并将其映射到数据库表。对于此示例,我们将创建一个具有一些基本属性的 User 实体:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String username;
private String password;
//...
}
在域类之后,我们将创建 一个 UserService 类。这个简单的服务将引用 EntityManager 以及将 User 对象保存到数据库的方法:
public class UserService {
EntityManager entityManager;
public UserService(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Transactional
public long saveUser(User user){
entityManager.persist(user);
return user.getId();
}
}
这种设置是我们之前提到的一个常见陷阱。我们可以通过测试证明 saveUser 方法的返回值为零:
@Test
public void whenNewUserIsPersisted_thenEntityHasNoId() {
User user = new User();
user.setUsername("test");
user.setPassword(UUID.randomUUID().toString());
long index = service.saveUser(user);
Assert.assertEquals(0L, index);
}
在接下来的部分中,我们将回过头来了解为什么会发生这种情况,以及如何解决它。
4. 手动事务控制
创建对象后,我们的 User 实体处于 新 状态。在 saveUser 方法中调用 persist 方法后,实体状态更改为 托管 状态。我们从回顾部分记得, 托管对象在事务 提交 后获得一个 id 。由于 saveUser 方法仍在运行,因此 @Transactional 注解创建的事务尚未提交。当 saveUser 完成执行时,我们的托管实体会获得一个 id。
一种可能的解决方案是 手动调用 EntityManager 上的 flush 方法 。另一方面,我们可以 手动控制事务 并保证我们的方法正确返回id。我们可以使用 EntityManager 来做到这一点:
@Test
public void whenTransactionIsControlled_thenEntityHasId() {
User user = new User();
user.setUsername("test");
user.setPassword(UUID.randomUUID().toString());
entityManager.getTransaction().begin();
long index = service.saveUser(user);
entityManager.getTransaction().commit();
Assert.assertEquals(2L, index);
}
5. 使用 ID 生成策略
到目前为止,我们使用的是第二类,其中 id 分配发生在事务 提交 之后。 预分配策略可以在事务 提交之前为我们提供 id, 因为它们在内存中保留了少量 id 。此选项并不总是可以实现,因为并非所有数据库引擎都支持所有生成策略。将策略更改为 GenerationType.SEQUENCE 可以解决我们的问题。此策略使用数据库序列,而不是像 GenerationType.IDENTITY 中那样使用自动递增列。
要更改策略,我们编辑域实体类:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
//...
}
六,结论
在本文中,我们介绍了 JPA 中的 id 生成技术。首先,我们回顾一下 id 生成的最重要的关键方面。然后我们介绍了 JPA 中使用的常见配置及其优点和缺点。本文引用的所有代码都可以在 GitHub 上找到。