【问题标题】:Insert new entity: Spring Data JPA vs. Hibernate's EntityManager插入新实体:Spring Data JPA vs. Hibernate EntityManager
【发布时间】:2026-02-17 16:00:01
【问题描述】:

请看下面两个代码示例,我将在我的Spring Boot 项目中使用它们。他们都只是做同样的事情——在users 表中添加一个新对象,由User 实体表示,username 定义为@Idunique 约束施加在email 列上(还有一些其他的列,但为简洁起见,此处未显示)。注意:我不能简单地使用来自CrudRepositorysave() 方法,因为如果它们都具有相同的username 值,它会将现有记录与新对象合并。相反,我需要插入一个带有适当异常的新对象,以实现重复数据持久性。

我的问题是应该支持哪个选项。有了EntityManager,就不用构造SQL语句了。除了这个明显的观察结果之外,一种方法比另一种方法有什么优势(特别是在性能和​​资源消耗方面)?

另外,当我在Spring Boot 阅读有关数据持久性的最新书籍和教程时,他们主要关注Spring Data JPA。例如,第 5 版“Spring in Action” 没有关于 Hibernate 的 EntityMnager 的消息。这是否意味着直接与Hibernate打交道可以被视为一种“老派”,在现代项目中通常应该避免?

选项 #1:Hibernate 的 EntityManager

@RestController
@RequestMapping(path = "/auth/register", produces = "application/json")
@Transactional
public class RegistrationController {

    @PersistenceContext
    EntityManager entityManager;

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Map<String, String> registerNewUser(@RequestBody @Valid User newUser) {

        try {
            entityManager.persist(newUser);
            entityManager.flush();
        } catch (PersistenceException ex) {
            // parse exception to find out which constraints have been 
            // broken - either it's duplicate username, email or both
            String message = parseExceptionForConstraintNames(ex);
            throw new ResponseStatusException(HttpStatus.CONFLICT, messsage);
        }
        
        return Collections.singletonMap("message", "Success..."); 
    }

}

选项 #2:来自 CrudRepository 的自定义 @Query

@RestController
@RequestMapping(path = "/auth/register", produces = "application/json")
public class RegistrationController {

    private final UsersRepository usersRepository;

    @Autowired
    public RegistrationController(UsersRepository usersRepository) {
        this.usersRepository = usersRepository;
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Map<String, String> registerNewUser(@RequestBody @Valid User newUser) {

        try {
            usersRepository.insert(newUser);
        } catch (DataIntegrityViolationException ex) {
            // parse exception to find out which constraints have been 
            // broken - either it's duplicate username, email or both
            String message = parseExceptionForConstraintNames(ex);
            throw new ResponseStatusException(HttpStatus.CONFLICT, message);
        }
        
        return Collections.singletonMap("message", "Success..."); 
    }

}
public interface UsersRepository extends CrudRepository<User, String> {

    @Modifying
    @Transactional
    @Query(nativeQuery = true, value = "INSERT INTO users (username, email) " +
            "VALUES (:#{#user.username}, :#{#user.email})")
    void insert(@Param("user") User newUser);

}

【问题讨论】:

  • 一个问题的问题太多了。

标签: java spring spring-boot hibernate spring-data-jpa


【解决方案1】:

请参阅 this 答案以了解使用 JPA 存储库与实体管理器。

最佳做法是不要直接使用存储库。在controllerrepository之间使用服务层,您可以通过使用findByUsername(String username);检查数据库中是否已经存在记录来实现重复条目​​的逻辑如果它已经存在则抛出异常否则save()数据库中的对象

【讨论】:

  • 我同意,添加服务层通常应该被认为是“必须的”。
  • 至于findByUsername() 然后save() 方法...这是我实际上试图避免的,因为这种 check-then-act 逻辑可能会导致并发问题。想象一下,两个并行请求正在尝试将“jeremy”添加到数据库中。线程 A 找不到具有该名称的用户,然后调用 save(),但在线程 B 之间设法保存其“jeremy”... 结果,来自线程 A 的 save() 未引发异常并将其“jeremy”与线程 B 刚刚添加的一个。这将需要额外的锁定来管理这种情况。
  • @escudero380 所以你需要乐观锁定 (@Version) 来解决这个问题。
  • 好的。这听起来很合理,我也可以用 @Retryable(ObjectOptimisticLockingFailureException.class) 注释来包装整个 check-then-act 事务,这样可以让我免于弄清楚究竟违反了哪个约束,因为我真的不喜欢什么在我的帖子中的两个代码示例中,我必须解析 DataIntegrityViolationExceptionResponseStatusException 的原因,以找到我们的哪个约束被完全违反。另一方面,如果我想使用乐观锁定,我需要在表中添加一个特殊的version 字段。
  • @escudero380 我总是让可编辑的实体从注释为 MappedSuperclass 的类扩展而来,这些实体具有 id 字段和版本字段
【解决方案2】:

根据给定的要求,在实体中提交的username 永远不符合@Id 的条件。

为什么你不能为 id 字段添加一个带有一些序列生成器的显式 id 字段,而只保留标记为 unique 约束的username

【讨论】:

  • 我同意你的看法。我应该从一开始就选择这个选项 :) 但是让我们假设一下,我需要处理现有数据库中无法在此过程中更改的 users 表。
  • 如果你不能改变现有的表然后实现Persistable,创建一个瞬态 isNew() 方法,它默认返回 true。还添加一个用@PostLoad 注释的方法,将isNew 的返回值设置为false。因此,在调用 Spring Data JPA save() 方法期间,所有新创建的实体都将被视为新对象:它们将被持久化。从他的数据库加载的所有实体都将被视为现有对象,因此当您将它们传递给 save() 方法时它们将被合并。
最近更新 更多