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 仓库 中找到。