JPA 处理关系的 merge()

JPA handle merge() of relationship

我有一个单向关系 Project -> ProjectType:

@Entity
public class Project extends NamedEntity
{
    @ManyToOne(optional = false)
    @JoinColumn(name = "TYPE_ID")
    private ProjectType type;
}

@Entity
public class ProjectType extends Lookup
{
    @Min(0)
    private int progressive = 1;
}

请注意,没有级联。

现在,当我插入一个新项目时,我需要递增类型。

这是我在 EJB 中所做的,但我不确定这是最好的方法:

public void create(Project project)
{
    em.persist(project);

    /* is necessary to merge the type? */
    ProjectType type = em.merge(project.getType());

    /* is necessary to set the type again? */
    project.setType(type);

    int progressive = type.getProgressive();
    type.setProgressive(progressive + 1);
    project.setCode(type.getPrefix() + progressive);
}

我正在使用 eclipselink 2.6.0,但我想知道是否有独立于实现的最佳实践and/or如果持久性提供程序之间存在行为差异,关于这个特定场景。


更新

在进入 EJB 创建方法时阐明上下文(它由 JSF @ManagedBean 调用):

我不是在问 persist()merge() 之间的区别,我是在问

  1. if em.persist(project) 自动 "reattach" project.projectType (我想不会)
  2. 如果 调用顺序 合法:首先 em.persist(project) 然后 em.merge(projectType) 或者如果它应该反转
  3. 因为em.merge(projectType)returns不同的实例,如果需要调用project.setType(managedProjectType)

对 "why" 这在某种程度上起作用而不是另一种方式的解释也很受欢迎。

在此代码中。合并基本上将记录存储在不同的对象中,比方说 一个帐户 pojo 在那里 帐号帐号=null; 账户 = entityManager.merge(账户); 然后你可以存储这个结果。

但是在您的代码中,您使用的是合并不同的条件,例如 public无效创建(项目项目) { em.persist(项目);

/* is necessary to merge the type? */
ProjectType type = em.merge(project.getType());

} 这里 Project 和 ProjectType 是两个不同的 pojo,您可以对同一个 pojo 使用合并。 或者在你的pojo中有什么关系那么你也可以使用它。

需要合并吗?这要看情况。根据 merge() javadoc:

Merge the state of the given entity into the current persistence context

您是如何获得附加到项目的 ProjectType 实例的?如果该实例已经被管理,那么您需要做的只是

type.setProgessive(type.getProgressive() + 1)

并且 JPA 将在下一次上下文刷新时自动发布更新。

否则,如果该类型不受管理,则需要先将其合并。

虽然没有直接关系,但这个问题对持久化与合并有一些很好的见解:JPA EntityManager: Why use persist() over merge()?

有了 em.persist(project) vs em.merge(projectType) 的调用顺序,你可能应该问问自己,如果类型在数据库中消失了会发生什么?如果您首先合并类型,它将被重新插入,如果您首先保留项目并且您有 FK 约束,插入将失败(因为它不是级联的)。

您只需要 merge(...) 来创建由您的实体管理器管理的临时实体。根据 JPA 的实现(不确定 EclipseLink),merge 调用的 returned 实例可能是原始对象的不同副本。

MyEntity unmanaged = new MyEntity();
MyEntity managed = entityManager.merge(unmanaged);
assert(entityManager.contains(managed));  // true if everything worked out
assert(managed != unmanaged);  // probably true, depending on JPA impl.

如果您调用 manage(entity),其中 entity 已被管理,则不会发生任何事情。

调用 persist(entity) 也将使您的实体受到管理,但它 return 没有副本。相反,它合并了原始对象,它也可能调用 ID 生成器(例如序列),使用 merge.

时情况并非如此

有关 persistmerge 之间差异的更多详细信息,请参阅 this answer

这是我的建议:

public void create(Project project) {
    ProjectType type = project.getType(); // maybe check if null
    if (!entityManager.contains(type)) {  // type is transient
        type = entityManager.merge(type); // or load the type
        project.setType(type); // update the reference
    }

    int progressive = type.getProgressive();
    type.setProgressive(progressive + 1); // mark as dirty, update on flush

    // set "code" before persisting "project" ...
    project.setCode(type.getPrefix() + progressive);
    entityManager.persist(project);

    // ... now no additional UPDATE is required after the
    // INSERT on "project".
}

更新

if em.persist(project) automatically "reattach" project.projectType (I suppose not)

没有。你可能会得到一个异常(Hibernate 无论如何)说明你正在尝试与瞬态引用合并。

更正: 我用 Hibernate 测试了它,没有异常。该项目是使用非托管项目类型创建的(在保留项目之前先进行托管然后分离)。 但是项目类型的progression没有增加,正如预期的那样,因为它没有被管理。所以是的,在坚持项目之前管理它。

if it is legal the call order: first em.persist(project) then em.merge(projectType) or if it should be inverted

最好这样做。但是当这两个语句在同一批中执行时(在实体管理器被刷新之前)它甚至可能工作(在持久化项目之后合并类型)。在我的测试中它仍然有效。但正如我所说,最好在保留新实体之前合并实体。

since em.merge(projectType) returns a different instance, if it is required to call project.setType(managedProjectType)

是的。请参见上面的示例。持久性提供程序 可以 return 相同的引用,但这不是必需的。所以为了确定,调用 project.setType(mergedType).