简体   繁体   English

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

[英]Spring-Data JPA: save new entity referencing existing one

The question is basically the same as below one: 问题基本上与下面的问题相同:

JPA cascade persist and references to detached entities throws PersistentObjectException. JPA级联持久化并且对分离实体的引用会抛出PersistentObjectException。 Why? 为什么?

I'm creating a new entity that references an existing, detached one. 我正在创建一个引用现有的独立实体的新实体。 Now when I save this entity in my spring data repository an exception is thrown: 现在,当我在Spring数据存储库中保存此实体时,会抛出异常:

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

if we look at the save() method in source code of spring data JPA we see: 如果我们看一下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);
    }
}

and if we look at isNew() in AbstractEntityInformation 如果我们在AbstractEntityInformation查看isNew()

public boolean isNew(T entity) {

    return getId(entity) == null;
}

So basically if i save() a new entity (id == null) , spring data will always call persist and hence this scenario will always fail. 所以基本上如果我保存()一个新实体(id == null) ,spring数据将始终调用persist,因此这种情况总是会失败。

This seems to be a very typical use case when adding new items to collections. 在向集合添加新项目时,这似乎是一个非常典型的用例。

How can I resolve this? 我该如何解决这个问题?

EDIT 1: 编辑1:

NOTE: 注意:

This issue is NOT directly related to How to save a new entity that refers existing entity in Spring JPA? 此问题与如何保存引用Spring JPA中现有实体的新实体没有直接关系 . To elaborate assume you get the request to create the new entity over http. 详细说明假设您获得了通过http创建新实体的请求。 You then extract the information from the request and create your entity and the existing referenced one. 然后,您从请求中提取信息,并创建您的实体和现有的实体。 Hence they will always be detached. 因此,他们将永远脱离。

I had a similar issue where I was trying to save an new entity object with an already saved entity object inside. 我有一个类似的问题,我试图用一个已保存的实体对象保存一个新的实体对象。

What I did was implemented Persistable < T > and implemented isNew() accordingly. 我所做的是实现了Persistable <T>并相应地实现了isNew()。

public class MyEntity implements Persistable<Long> {

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

Or you could use AbstractPersistable and override the isNew there ofcourse. 或者您可以使用AbstractPersistable并覆盖那里的isNew。

I don't know if this will be considered a good way of handling this issue but it worked out quite good for me and besides feels very natural to do. 我不知道这是否会被认为是处理这个问题的好方法,但它对我来说非常好,而且感觉非常自然。

The best I came up with is 我想出的最好的是

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

We check if we are in the problematic situation and if yes just reload the existing entity from the database by its id and set the field of the new entity to this freshly loaded instance. 我们检查我们是否处于有问题的情况,如果是,只需通过其id从数据库重新加载现有实体,并将新实体的字段设置为这个新加载的实例。 It then will be attached. 然后它将被附加。

This requires that the service for new entity holds a reference to the service of the referenced entity. 这要求新实体的服务保存对引用实体的服务的引用。 This should not be an issue since you are using spring anyway so that service can just be added as a new @Autowired field. 这应该不是问题,因为无论如何都要使用spring,以便可以将服务添加为新的@Autowired字段。

Another issue however (in my case this behavior is actually desired) that you can't change the referenced existing entity at the same time while saving the new one. 然而,另一个问题(在我的情况下,这种行为实际上是期望的)你不能在保存新实体的同时更改引用的现有实体。 All those changes will be ignored. 所有这些变化都将被忽略。

IMPORTANT NOTE: 重要的提示:

In many and probably your cases this can be much simpler. 在许多情况下,可能是您的情况,这可能会简单得多。 You can add a reference of entity manager to your service: 您可以向服务添加实体管理器的引用:

@PersistenceContext
private EntityManager entityManager;

and in above if(){} block use 以及if(){}块使用

containable = entityManager.merge(containable);

instead of my code (untested if it works). 而不是我的代码(如果它工作未经测试)。

In my case the classes are abstract and targetEntity in @ManyToOne is hence abstract too. 在我的例子中,类是抽象的,而@ManyToOne targetEntity也是抽象的。 Calling entityManager.merge(containable) directly then leads to an exception. 直接调用entityManager.merge(可包含)然后会导致异常。 However if your classes are all concrete this should work. 但是,如果你的课程都是具体的,这应该有效。

I have the same problem with a @EmbeddedId and business data as part of the id. 我将@EmbeddedId和业务数据作为id的一部分存在同样的问题。
The only way to know if the entity is new is performing a (em.find(entity)==null)?em.persist(entity):em.merge(entity) 知道实体是否是新的唯一方法是执行(em.find(entity)==null)?em.persist(entity):em.merge(entity)

but spring-data only provides save() method and there is no way to fill the Persistable.isNew() method with a find() method. 但是spring-data只提供了save()方法,并且没有办法用find()方法填充Persistable.isNew() find()方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM