Spring-Data JPA:保存引用现有实体的新实体

问题基本上与以下问题相同:

JPA 级联持久化,对分离实体的引用会引发 PersistentObjectException。为什么?

我正在创建一个引用现有分离实体的新实体。现在,当我将此实体保存在我的春季数据存储库中时,会引发异常:

org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist

如果我们看一下 spring 数据 JPA 源代码中的 save() 方法,我们会看到:

public <S extends T> S save(S entity) {

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

如果我们看一下 isNew() inAbstractEntityInformation

public boolean isNew(T entity) {

    return getId(entity) == null;
}

所以基本上,如果我保存()一个新的实体,spring数据将始终调用持久化,因此这种情况总是会失败。(id == null)

这似乎是向集合添加新项时非常典型的用例。

如何解决此问题?

编辑1:

注意:

此问题与如何在Spring JPA中保存引用现有实体的新实体没有直接关系。为了详细说明,假设您收到通过http创建新实体的请求。然后,从请求中提取信息,并创建实体和现有引用的实体。因此,它们将永远是超然的。


答案 1

我遇到了一个类似的问题,我试图保存一个新的实体对象,里面有一个已经保存的实体对象。

我所做的是实现 Persistable< T >并相应地实现了 isNew()。

public class MyEntity implements Persistable<Long> {

    public boolean isNew() {
        return null == getId() &&
            subEntity.getId() == null;
    }

或者你可以使用 AbstractPersistable 并覆盖 isNew 那里当然。

我不知道这是否会被认为是处理这个问题的好方法,但它对我来说效果很好,而且感觉很自然。


答案 2

我想出的最好的是

public final T save(T containable) {
    // if entity containable.getCompound already exists, it
    // must first be reattached to the entity manager or else
    // an exception will occur (issue in Spring Data JPA ->
    // save() method internal calls persists instead of merge)
    if (containable.getId() == null
            && containable.getCompound().getId() != null){
        Compound compound = getCompoundService()
                .getById(containable.getCompound().getId());
        containable.setCompound(compound);   
    }
    containable = getRepository().save(containable);
    return containable; 
}

我们检查我们是否处于有问题的情况,如果是,只需通过其id从数据库中重新加载现有实体,并将新实体的字段设置为此新加载的实例。然后,它将被附加。

这要求新实体的服务保留对被引用实体的服务的引用。这应该不是一个问题,因为无论如何您都在使用spring,以便可以将服务添加为新字段。@Autowired

但是,另一个问题(在我的情况下,这种行为实际上是需要的),即在保存新实体的同时,您无法同时更改引用的现有实体。所有这些更改都将被忽略。

重要提示:

在许多情况下,这可能是您的情况,这可能要简单得多。您可以将实体管理器的引用添加到您的服务中:

@PersistenceContext
private EntityManager entityManager;

和在上面的块使用if(){}

containable = entityManager.merge(containable);

而不是我的代码(如果它有效,则未经测试)。

在我的例子中,类是抽象的,因此也是抽象的。然后直接调用entityManager.merge(containable)会导致异常。但是,如果你的类都是具体的,这应该有效。targetEntity@ManyToOne