Spring-Data JPA:保存引用现有实体的新实体
Posted
技术标签:
【中文标题】Spring-Data JPA:保存引用现有实体的新实体【英文标题】:Spring-Data JPA: save new entity referencing existing one 【发布时间】:2013-05-09 16:36:24 【问题描述】:问题与下面的基本相同:
JPA cascade persist and references to detached entities throws PersistentObjectException. Why?
我正在创建一个引用现有分离实体的新实体。现在,当我将此实体保存在我的 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);
如果我们查看 AbstractEntityInformation
中的 isNew()
public boolean isNew(T entity)
return getId(entity) == null;
所以基本上如果我保存()一个新实体(id == null)
,spring data 将始终调用persist,因此这种情况将始终失败。
这似乎是向集合添加新项目时非常典型的用例。
我该如何解决这个问题?
编辑 1:
注意:
此问题与How to save a new entity that refers existing entity in Spring JPA? 没有直接关系。详细说明假设您收到通过 http 创建新实体的请求。然后,您从请求中提取信息并创建您的实体和现有的引用实体。因此,它们将永远是分离的。
【问题讨论】:
卡在相同的场景中.. 有什么解决方案吗? 不,不是真的...当然你可以处理异常,然后先创建(持久化)新对象,并在持久化新对象后添加引用。但这并不适用于所有情况...... 另一种选择是确定我们是否处于这种情况:entity.getId() == null && entity.getReferencedEntity().getId() != null
,如果为真,则从数据库中加载引用的实体。
这似乎是 Spring JPA 的基本失败。仅仅因为您保存的实体是新的,它不应该暗示 persist()
将适用于其所有引用的实体...
【参考方案1】:
我有一个类似的问题,我试图保存一个新的实体对象,其中包含一个已经保存的实体对象。
我所做的是实现了Persistable
public class MyEntity implements Persistable<Long>
public boolean isNew()
return null == getId() &&
subEntity.getId() == null;
或者您当然可以使用AbstractPersistable 并覆盖那里的isNew。
我不知道这是否会被认为是处理这个问题的好方法,但它对我来说非常好,而且感觉很自然。
【讨论】:
如果子实体恰好是 List<...> 与 @OneToMany 映射【参考方案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);
而不是我的代码(如果有效,则未经测试)。
在我的例子中,类是抽象的,@ManyToOne
中的targetEntity
因此也是抽象的。直接调用 entityManager.merge(containable) 会导致异常。但是,如果您的课程都是具体的,这应该可以工作。
【讨论】:
而不是重新加载实体,您可以使用“打开”锁进行重新连接:entityManager.lock(containable.getCompound(),LockModeType.NONE);
这将使您的分离实体持久而无需锁定或重新加载它,并且您将能够按预期进行保存。【参考方案3】:
@EmbeddedId
和作为 id 一部分的业务数据存在同样的问题。
知道实体是否是新实体的唯一方法是执行(em.find(entity)==null)?em.persist(entity):em.merge(entity)
但是spring-data只提供save()
方法,没有办法用find()
方法填充Persistable.isNew()
方法。
【讨论】:
以上是关于Spring-Data JPA:保存引用现有实体的新实体的主要内容,如果未能解决你的问题,请参考以下文章
Spring-Boot,无法使用spring-data JPA在MySql中保存unicode字符串
Spring-Boot,无法使用 spring-data JPA 在 MySql 中保存 unicode 字符串
如何使用 Spring Data JPA 保存具有手动分配标识符的实体?