一、简介

在本教程中,我们将讨论 如何使用 JPA 处理自动生成的 id 。在看实际示例之前,我们必须了解两个关键概念,即生命周期和 id 生成策略。

2.实体生命周期和ID生成

每个实体在其生命周期中有四种可能的状态。这些状态是 new*、 Manageddetached 和 *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 上找到。