1. 概述

在这篇快速教程中,我们将学习如何在 JPA 实体对象上执行 INSERT 操作

如果你对 Hibernate 更广泛的内容感兴趣,可以参考我们更全面的 Spring 与 JPA 的持久层指南Spring Data JPA 入门指南 来深入理解相关主题。

2. JPA 中的对象持久化机制

在 JPA 中,当一个实体从瞬态(transient)变为受管状态(managed)时,这个过程是由 EntityManager 自动处理的。

EntityManager 会判断该实体是否已经存在,并决定是应该插入还是更新。由于这种自动化的管理机制,JPA 本身并不直接支持 INSERT 语句,它只允许 SELECT、UPDATE 和 DELETE 操作。

在下面的示例中,我们会介绍几种管理和绕过这一限制的方法。

3. 定义通用模型

我们先定义一个简单的实体类,它将在整个教程中使用:

@Entity
public class Person {

    @Id
    private Long id;
    private String firstName;
    private String lastName;

    // 标准 getter/setter、默认构造函数和全参构造函数
}

接着,定义一个用于实现插入操作的 Repository 类:

@Repository
public class PersonInsertRepository {

    @PersistenceContext
    private EntityManager entityManager;

}

此外,我们会在方法上加上 @Transactional 注解,让 Spring 自动管理事务。这样就不用手动控制 EntityManager 的事务提交或回滚了。

4. 使用 createNativeQuery

对于手动编写的 SQL 查询,我们可以使用 EntityManager#createNativeQuery 方法。这种方式可以让我们执行任意类型的 SQL 语句,而不局限于 JPA 支持的语法。我们来给 Repository 添加一个新方法:

@Transactional
public void insertWithQuery(Person person) {
    entityManager.createNativeQuery("INSERT INTO person (id, first_name, last_name) VALUES (?,?,?)")
      .setParameter(1, person.getId())
      .setParameter(2, person.getFirstName())
      .setParameter(3, person.getLastName())
      .executeUpdate();
}

在这种方式下,我们需要手动指定列名并设置对应的值。

现在我们来测试一下这个 Repository 方法:

@Test
public void givenPersonEntity_whenInsertedTwiceWithNativeQuery_thenPersistenceExceptionExceptionIsThrown() {
    Person person = new Person(1L, "firstname", "lastname");

    assertThatExceptionOfType(PersistenceException.class).isThrownBy(() -> {
        personInsertRepository.insertWithQuery(PERSON);
        personInsertRepository.insertWithQuery(PERSON);
    });
}

在测试中,每次操作都试图插入一个新的记录。由于两次插入的 id 相同,第二次插入会抛出 PersistenceException 异常。

⚠️ 注意:如果使用 Spring Data 的 @Query 注解,原理也是一样的。

5. 使用 persist

在之前的例子中,我们虽然实现了插入功能,但每次都要手动写 SQL 语句,代码冗余且效率不高。

更好的做法是使用 EntityManager 提供的 persist 方法。

我们继续扩展 Repository 类,添加如下方法:

@Transactional
public void insertWithEntityManager(Person person) {
    this.entityManager.persist(person);
}

然后进行测试:

@Test
public void givenPersonEntity_whenInsertedTwiceWithEntityManager_thenEntityExistsExceptionIsThrown() {
    assertThatExceptionOfType(EntityExistsException.class).isThrownBy(() -> {
        personInsertRepository.insertWithEntityManager(new Person(1L, "firstname", "lastname"));
        personInsertRepository.insertWithEntityManager(new Person(1L, "firstname", "lastname"));
    });
}

✅ 与原生 SQL 查询不同的是:我们不需要手动指定列名和值,这些由 EntityManager 自动处理。

在这个测试中,预期抛出的是更具体的 EntityExistsException 而不是其父类 PersistenceException,这是 persist 方法特有的行为。

⚠️ 另外需要注意的是:每次插入必须传入一个新的 Person 实例。否则,该实体可能已经被 EntityManager 管理,导致执行的是更新操作而非插入。

6. 总结

本文介绍了在 JPA 中执行 INSERT 操作的几种方式,包括使用原生 SQL 查询以及通过 EntityManager#persist 方法实现自定义插入逻辑。

一如既往,本文中的完整代码可以在 GitHub 仓库 中找到。


原始标题:INSERT Statement in JPA