33

问题与下面的问题基本相同:

JPA 级联持续存在并且对分离实体的引用会引发 PersistentObjectException。为什么?

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

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

如果我们查看 spring data 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() 在AbstractEntityInformation

public boolean isNew(T entity) {

    return getId(entity) == null;
}

所以基本上,如果我 save() 一个新实体(id == null),spring data 将始终调用persist,因此这种情况总是会失败。

在向集合中添加新项目时,这似乎是一个非常典型的用例。

我该如何解决这个问题?

编辑1:

笔记:

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

4

3 回答 3

13

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

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

public class MyEntity implements Persistable<Long> {

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

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

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

于 2014-08-06T07:58:06.487 回答
9

我想出的最好的是

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);

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

在我的例子中,类是抽象的,因此targetEntityin@ManyToOne也是抽象的。直接调用 entityManager.merge(containable) 会导致异常。但是,如果您的课程都是具体的,那么这应该可以。

于 2013-07-11T09:20:00.283 回答
4

@EmbeddedId作为 id 的一部分,我对 a 和业务数据有同样的问题。
了解实体是否为新实体的唯一方法是执行(em.find(entity)==null)?em.persist(entity):em.merge(entity)

但是spring-data只提供save()方法,没有办法用方法填充Persistable.isNew()方法find()

于 2015-06-17T07:06:54.697 回答