JPA EntityManager:为什么使用 persist() 而不是 merge()?

发布于 2024-07-26 11:20:29 字数 94 浏览 12 评论 0原文

EntityManager.merge() 可以插入新对象并更新现有对象。

为什么要使用persist()(它只能创建新对象)?

EntityManager.merge() can insert new objects and update existing ones.

Why would one want to use persist() (which can only create new objects)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

盗心人 2024-08-02 11:20:29

无论哪种方式都会将实体添加到 PersistenceContext 中,区别在于您随后对实体执行的操作。

Persist 获取一个实体实例,将其添加到上下文中并管理该实例(即,将跟踪该实体的未来更新)。

合并返回与状态合并的托管实例。 它确实返回 PersistenceContext 中存在的内容或创建实体的新实例。 在任何情况下,它都会从提供的实体复制状态,并返回托管副本。 您传入的实例将不会被管理(您所做的任何更改都不会成为事务的一部分 - 除非您再次调用合并)。 尽管您可以使用返回的实例(托管实例)。

也许代码示例会有所帮助。

MyEntity e = new MyEntity();

// scenario 1
// tran starts
em.persist(e); 
e.setSomeField(someValue); 
// tran ends, and the row for someField is updated in the database

// scenario 2
// tran starts
e = new MyEntity();
em.merge(e);
e.setSomeField(anotherValue); 
// tran ends but the row for someField is not updated in the database
// (you made the changes *after* merging)
      
// scenario 3
// tran starts
e = new MyEntity();
MyEntity e2 = em.merge(e);
e2.setSomeField(anotherValue); 
// tran ends and the row for someField is updated
// (the changes were made to e2, not e)

场景 1 和 3 大致相同,但在某些情况下您需要使用场景 2。

Either way will add an entity to a PersistenceContext, the difference is in what you do with the entity afterwards.

Persist takes an entity instance, adds it to the context and makes that instance managed (i.e. future updates to the entity will be tracked).

Merge returns the managed instance that the state was merged with. It does return something that exists in PersistenceContext or creates a new instance of your entity. In any case, it will copy the state from the supplied entity, and return a managed copy. The instance you pass in will not be managed (any changes you make will not be part of the transaction - unless you call merge again). Though you can use the returned instance (managed one).

Maybe a code example will help.

MyEntity e = new MyEntity();

// scenario 1
// tran starts
em.persist(e); 
e.setSomeField(someValue); 
// tran ends, and the row for someField is updated in the database

// scenario 2
// tran starts
e = new MyEntity();
em.merge(e);
e.setSomeField(anotherValue); 
// tran ends but the row for someField is not updated in the database
// (you made the changes *after* merging)
      
// scenario 3
// tran starts
e = new MyEntity();
MyEntity e2 = em.merge(e);
e2.setSomeField(anotherValue); 
// tran ends and the row for someField is updated
// (the changes were made to e2, not e)

Scenario 1 and 3 are roughly equivalent, but there are some situations where you'd want to use Scenario 2.

醉梦枕江山 2024-08-02 11:20:29

坚持和合并有两个不同的目的(它们根本不是替代品)。

(编辑以扩展差异信息)

persist:

  • 将新寄存器插入数据库
  • 将对象附加到实体管理器。

merge:

  • 找到具有相同id的附加对象并更新它。
  • 如果存在则更新并返回已附加的对象。
  • 如果不存在,则将新寄存器插入数据库。

persist() 效率:

  • 将新寄存器插入数据库可能比 merge() 更有效。
  • 它不会复制原始对象。

persist() 语义:

  • 它确保您插入而不是错误更新。

示例:

{
    AnyEntity newEntity;
    AnyEntity nonAttachedEntity;
    AnyEntity attachedEntity;

    // Create a new entity and persist it        
    newEntity = new AnyEntity();
    em.persist(newEntity);

    // Save 1 to the database at next flush
    newEntity.setValue(1);

    // Create a new entity with the same Id than the persisted one.
    AnyEntity nonAttachedEntity = new AnyEntity();
    nonAttachedEntity.setId(newEntity.getId());

    // Save 2 to the database at next flush instead of 1!!!
    nonAttachedEntity.setValue(2);
    attachedEntity = em.merge(nonAttachedEntity);

    // This condition returns true
    // merge has found the already attached object (newEntity) and returns it.
    if(attachedEntity==newEntity) {
            System.out.print("They are the same object!");
    }

    // Set 3 to value
    attachedEntity.setValue(3);
    // Really, now both are the same object. Prints 3
    System.out.println(newEntity.getValue());

    // Modify the un attached object has no effect to the entity manager
    // nor to the other objects
    nonAttachedEntity.setValue(42);
}

这种方式对于实体管理器中的任何寄存器仅存在 1 个附加对象。

对于具有 id 的实体,merge() 类似于:

AnyEntity myMerge(AnyEntity entityToSave) {
    AnyEntity attached = em.find(AnyEntity.class, entityToSave.getId());
    if(attached==null) {
            attached = new AnyEntity();
            em.persist(attached);
    }
    BeanUtils.copyProperties(attached, entityToSave);

    return attached;
}

虽然如果连接到 MySQL merge() 可能与使用 ON DUPLICATE KEY UPDATE 选项调用 INSERT 的 persist() 一样高效,但 JPA 是一种非常高级的编程,您不能假设到处都是这种情况。

Persist and merge are for two different purposes (they aren't alternatives at all).

(edited to expand differences information)

persist:

  • Insert a new register to the database
  • Attach the object to the entity manager.

merge:

  • Find an attached object with the same id and update it.
  • If exists update and return the already attached object.
  • If doesn't exist insert the new register to the database.

persist() efficiency:

  • It could be more efficient for inserting a new register to a database than merge().
  • It doesn't duplicates the original object.

persist() semantics:

  • It makes sure that you are inserting and not updating by mistake.

Example:

{
    AnyEntity newEntity;
    AnyEntity nonAttachedEntity;
    AnyEntity attachedEntity;

    // Create a new entity and persist it        
    newEntity = new AnyEntity();
    em.persist(newEntity);

    // Save 1 to the database at next flush
    newEntity.setValue(1);

    // Create a new entity with the same Id than the persisted one.
    AnyEntity nonAttachedEntity = new AnyEntity();
    nonAttachedEntity.setId(newEntity.getId());

    // Save 2 to the database at next flush instead of 1!!!
    nonAttachedEntity.setValue(2);
    attachedEntity = em.merge(nonAttachedEntity);

    // This condition returns true
    // merge has found the already attached object (newEntity) and returns it.
    if(attachedEntity==newEntity) {
            System.out.print("They are the same object!");
    }

    // Set 3 to value
    attachedEntity.setValue(3);
    // Really, now both are the same object. Prints 3
    System.out.println(newEntity.getValue());

    // Modify the un attached object has no effect to the entity manager
    // nor to the other objects
    nonAttachedEntity.setValue(42);
}

This way only exists 1 attached object for any register in the entity manager.

merge() for an entity with an id is something like:

AnyEntity myMerge(AnyEntity entityToSave) {
    AnyEntity attached = em.find(AnyEntity.class, entityToSave.getId());
    if(attached==null) {
            attached = new AnyEntity();
            em.persist(attached);
    }
    BeanUtils.copyProperties(attached, entityToSave);

    return attached;
}

Although if connected to MySQL merge() could be as efficient as persist() using a call to INSERT with ON DUPLICATE KEY UPDATE option, JPA is a very high level programming and you can't assume this is going to be the case everywhere.

仙女 2024-08-02 11:20:29

如果您使用指定的生成器,则使用 merge 而不是 persist 可能会导致冗余 SQL 语句,从而影响性能。

此外,为托管实体调用合并也是一个错误,因为托管实体由 Hibernate 自动管理,并且在刷新持久性上下文时通过脏检查机制将它们的状态与数据库记录同步。

要理解这一切是如何工作的,您首先应该知道 Hibernate 将开发人员的思维方式从 SQL 语句转变为实体状态转换。

一旦实体由 Hibernate 主动管理,所有更改都将自动传播到数据库。

Hibernate 监视当前附加的实体。 但要使实体成为托管实体,它必须处于正确的实体状态。

为了更好地理解 JPA 状态转换,您可以可视化下图:

JPA 实体状态转换

或者如果您使用 Hibernate 特定的API:

Hibernate 实体状态转换

如上图所示,实体可以处于以下四种状态之一:

  • 新(瞬态)

从未与 Hibernate Session (又名 Persistence Context)关联且未映射到任何数据库表行的新创建的对象被认为是处于新(瞬态)状态。

要持久化,我们需要显式调用 EntityManager#persist 方法或使用传递持久化机制。

  • 持久(托管)

    持久实体已与数据库表行关联,并且由当前运行的持久上下文管理。 对此类实体所做的任何更改都将被检测到并传播到数据库(在会话刷新期间)。
    使用 Hibernate,我们不再需要执行 INSERT/UPDATE/DELETE 语句。 Hibernate 采用事务性后写工作方式,并且在当前 Session 刷新时间期间的最后一个负责时刻同步更改。

  • 分离

一旦当前运行的持久性上下文关闭,所有先前管理的实体就会分离。 将不再跟踪连续的更改,并且不会发生自动数据库同步。

要将分离的实体关联到活动的 Hibernate 会话,您可以选择以下选项之一:

  • 重新附加

    Hibernate(但不包括 JPA 2.1)支持通过 Session#update 方法重新附加。

    Hibernate Session 只能为给定的数据库行关联一个实体对象。 这是因为持久性上下文充当内存中缓存(一级缓存),并且只有一个值(实体)与给定键(实体类型和数据库标识符)关联。

    仅当没有其他 JVM 对象(与同一数据库行匹配)已与当前 Hibernate 会话关联时,才能重新附加实体。

  • 合并

    合并会将分离的实体状态(源)复制到托管实体实例(目标)。 如果合并实体在当前会话中没有等效项,将从数据库中获取一个。

    即使在合并操作之后,分离的对象实例也将继续保持分离状态。

  • 删除

    虽然 JPA 要求只允许删除托管实体,但 Hibernate 也可以删除分离的实体(但只能通过 Session#delete 方法调用)。

    删除的实体仅计划删除,实际的数据库 DELETE 语句将在会话刷新期间执行。

If you're using the assigned generator, using merge instead of persist can cause a redundant SQL statement, therefore affecting performance.

Also, calling merge for managed entities is also a mistake since managed entities are automatically managed by Hibernate, and their state is synchronized with the database record by the dirty checking mechanism upon flushing the Persistence Context.

To understand how all this works, you should first know that Hibernate shifts the developer mindset from SQL statements to entity state transitions.

Once an entity is actively managed by Hibernate, all changes are going to be automatically propagated to the database.

Hibernate monitors currently attached entities. But for an entity to become managed, it must be in the right entity state.

To understand the JPA state transitions better, you can visualize the following diagram:

JPA entity state transitions

Or if you use the Hibernate specific API:

Hibernate entity state transitions

As illustrated by the above diagrams, an entity can be in one of the following four states:

  • New (Transient)

A newly created object that hasn’t ever been associated with a Hibernate Session (a.k.a Persistence Context) and is not mapped to any database table row is considered to be in the New (Transient) state.

To become persisted we need to either explicitly call the EntityManager#persist method or make use of the transitive persistence mechanism.

  • Persistent (Managed)

    A persistent entity has been associated with a database table row and it’s being managed by the currently running Persistence Context. Any change made to such an entity is going to be detected and propagated to the database (during the Session flush-time).
    With Hibernate, we no longer have to execute INSERT/UPDATE/DELETE statements. Hibernate employs a transactional write-behind working style and changes are synchronized at the very last responsible moment, during the current Session flush-time.

  • Detached

Once the currently running Persistence Context is closed all the previously managed entities become detached. Successive changes will no longer be tracked and no automatic database synchronization is going to happen.

To associate a detached entity to an active Hibernate Session, you can choose one of the following options:

  • Reattaching

    Hibernate (but not JPA 2.1) supports reattaching through the Session#update method.

    A Hibernate Session can only associate one Entity object for a given database row. This is because the Persistence Context acts as an in-memory cache (first level cache) and only one value (entity) is associated with a given key (entity type and database identifier).

    An entity can be reattached only if there is no other JVM object (matching the same database row) already associated with the current Hibernate Session.

  • Merging

    The merge is going to copy the detached entity state (source) to a managed entity instance (destination). If the merging entity has no equivalent in the current Session, one will be fetched from the database.

    The detached object instance will continue to remain detached even after the merge operation.

  • Remove

    Although JPA demands that managed entities only are allowed to be removed, Hibernate can also delete detached entities (but only through a Session#delete method call).

    A removed entity is only scheduled for deletion and the actual database DELETE statement will be executed during Session flush-time.

千里故人稀 2024-08-02 11:20:29

我注意到,当我使用 em.merge 时,即使 JPA 没有生成任何字段,我也会为每个 INSERT 获得一个 SELECT 语句对我来说——主键字段是我自己设置的 UUID。 我切换到 em.persist(myEntityObject) 并得到了 INSERT 语句。

I noticed that when I used em.merge, I got a SELECT statement for every INSERT, even when there was no field that JPA was generating for me--the primary key field was a UUID that I set myself. I switched to em.persist(myEntityObject) and got just INSERT statements then.

吹梦到西洲 2024-08-02 11:20:29

JPA 规范对 persist() 有如下规定。

如果X是一个分离的对象,则持久化时可能会抛出EntityExistsException
操作被调用,或者 EntityExistsException 或另一个 PersistenceException 可能会在刷新或提交时抛出。

因此,当对象不应该是分离对象时,使用 persist() 是合适的。 您可能更愿意让代码抛出PersistenceException,这样它就能快速失败。

尽管规范不清楚persist() 可能会为对象设置@GenerateValue @Id。 但是,merge() 必须具有已生成的带有 @Id 的对象。

The JPA specification says the following about persist().

If X is a detached object, the EntityExistsException may be thrown when the persist
operation is invoked, or the EntityExistsException or another PersistenceException may be thrown at flush or commit time.

So using persist() would be suitable when the object ought not to be a detached object. You might prefer to have the code throw the PersistenceException so it fails fast.

Although the specification is unclear, persist() might set the @GeneratedValue @Id for an object. merge() however must have an object with the @Id already generated.

甜宝宝 2024-08-02 11:20:29

有关合并的更多详细信息将帮助您使用合并而不是持久化:

返回原始实体以外的托管实例是合并的关键部分
过程。 如果持久化上下文中已经存在具有相同标识符的实体实例,则
提供者将用正在合并的实体的状态覆盖其状态,但托管
已经存在的版本必须返回给客户端以便可以使用。 如果提供商没有
更新持久化上下文中的 Employee 实例,对该实例的任何引用都将变为
与合并的新状态不一致。

当对新实体调用 merge() 时,其行为与 persist() 操作类似。 它添加了
将实体添加到持久化上下文中,但它不是添加原始实体实例,而是创建一个新的实体实例
而是复制并管理该实例。 由 merge() 操作创建的副本将被保留
就像对其调用 persist() 方法一样。

在存在关系的情况下,merge() 操作将尝试更新托管实体
指向分离实体引用的实体的托管版本。 如果该实体有
与没有持久标识的对象的关系,合并操作的结果是
不明确的。 某些提供程序可能允许托管副本指向非持久对象,
而其他人可能会立即抛出异常。 merge() 操作可以是可选的
在这些情况下级联以防止发生异常。 我们将介绍 merge() 的级联
本节后面的操作。 如果正在合并的实体指向已删除的实体,则
将抛出 IllegalArgumentException 异常。

延迟加载关系是合并操作中的一种特殊情况。 如果是懒加载
在实体分离之前,关系未在实体上触发,该关系将被
合并实体时被忽略。 如果在托管时触发关系,然后在分离实体时将其设置为 null,则实体的托管版本同样会在合并期间清除关系。”

所有上述信息均取自“Pro JPA 2 Mastering the Java” ™ Persistence API” 作者:Mike Keith 和 Merrick Schnicariol。第 6 章。章节分离和合并。这本书实际上是作者专门讨论 JPA 的第二本书。这本新书比前一本有很多新信息。我真的推荐阅读这本书对于那些认真参与 JPA 的人,我很抱歉匿名发布我的第一个答案。

Some more details about merge which will help you to use merge over persist:

Returning a managed instance other than the original entity is a critical part of the merge
process. If an entity instance with the same identifier already exists in the persistence context, the
provider will overwrite its state with the state of the entity that is being merged, but the managed
version that existed already must be returned to the client so that it can be used. If the provider did not
update the Employee instance in the persistence context, any references to that instance will become
inconsistent with the new state being merged in.

When merge() is invoked on a new entity, it behaves similarly to the persist() operation. It adds
the entity to the persistence context, but instead of adding the original entity instance, it creates a new
copy and manages that instance instead. The copy that is created by the merge() operation is persisted
as if the persist() method were invoked on it.

In the presence of relationships, the merge() operation will attempt to update the managed entity
to point to managed versions of the entities referenced by the detached entity. If the entity has a
relationship to an object that has no persistent identity, the outcome of the merge operation is
undefined. Some providers might allow the managed copy to point to the non-persistent object,
whereas others might throw an exception immediately. The merge() operation can be optionally
cascaded in these cases to prevent an exception from occurring. We will cover cascading of the merge()
operation later in this section. If an entity being merged points to a removed entity, an
IllegalArgumentException exception will be thrown.

Lazy-loading relationships are a special case in the merge operation. If a lazy-loading
relationship was not triggered on an entity before it became detached, that relationship will be
ignored when the entity is merged. If the relationship was triggered while managed and then set to null while the entity was detached, the managed version of the entity will likewise have the relationship cleared during the merge."

All of the above information was taken from "Pro JPA 2 Mastering the Java™ Persistence API" by Mike Keith and Merrick Schnicariol. Chapter 6. Section detachment and merging. This book is actually a second book devoted to JPA by authors. This new book has many new information then former one. I really recommed to read this book for ones who will be seriously involved with JPA. I am sorry for anonimously posting my first answer.

偏爱自由 2024-08-02 11:20:29

mergepersist 之间还有一些不同之处(我将再次列举此处已发布的内容):

D1。 merge 不会使传递的实体受到管理,而是返回另一个受管理的实例。 另一侧的persist将使传递的实体受到管理:

//MERGE: passedEntity remains unmanaged, but newEntity will be managed
Entity newEntity = em.merge(passedEntity);

//PERSIST: passedEntity will be managed after this
em.persist(passedEntity);

D2。 如果您删除一个实体,然后决定保留该实体,则只能使用 persist() 来执行此操作,因为 merge 会抛出 IllegalArgumentException

D3。 如果您决定手动处理您的 ID(例如通过使用 UUID),则合并
操作将触发后续的 SELECT 查询,以查找具有该 ID 的现有实体,而 persists 可能不需要这些查询。

D4。 在某些情况下,您根本不信任调用您的代码的代码,并且为了确保不更新数据而是插入数据,您必须使用 persist

There are some more differences between merge and persist (I will enumerate again those already posted here):

D1. merge does not make the passed entity managed, but rather returns another instance that is managed. persist on the other side will make the passed entity managed:

//MERGE: passedEntity remains unmanaged, but newEntity will be managed
Entity newEntity = em.merge(passedEntity);

//PERSIST: passedEntity will be managed after this
em.persist(passedEntity);

D2. If you remove an entity and then decide to persist the entity back, you may do that only with persist(), because merge will throw an IllegalArgumentException.

D3. If you decided to take care manually of your IDs (e.g by using UUIDs), then a merge
operation will trigger subsequent SELECT queries in order to look for existent entities with that ID, while persist may not need those queries.

D4. There are cases when you simply do not trust the code that calls your code, and in order to make sure that no data is updated, but rather is inserted, you must use persist.

壹場煙雨 2024-08-02 11:20:29

JPA 无疑是企业领域的一个巨大简化
构建在 Java 平台上的应用程序。 作为一名开发人员必须
应对 J2EE 中旧实体 bean 的复杂性我看到
将 JPA 纳入 Java EE 规范是一个巨大的飞跃
向前。 然而,在深入研究 JPA 细节时,我发现
事情并不那么容易。 在这篇文章中,我将比较
EntityManager 的 merge 和 persist 方法重叠
行为不仅会让新手感到困惑。 此外我
提出一种概括,将两种方法视为特定情况
更通用的方法结合起来。

持久化实体

与合并方法相比,持久化方法非常简单直观。 persist方法最常见的使用场景可以总结如下:

“一个新创建的实体类实例被传递给persist方法。该方法返回后,该实体被管理并计划插入数据库。它可能在事务提交时或之前发生,或者在调用刷新方法时发生,如果实体通过标有 PERSIST 级联策略的关系引用另一个实体,则该过程也适用于它。

输入图像描述这里

该规范更详细地介绍了细节,但是,记住它们并不重要,因为这些细节仅涵盖或多或少的奇异情况。

合并实体

与持久化相比,合并行为的描述并不那么简单。 没有主要场景,就像持久化的情况一样,程序员必须记住所有场景才能编写正确的代码。 在我看来,JPA 设计者希望有一些方法,其主要关注的是处理分离的实体(与主要处理新创建的实体的持久方法相反)。合并方法的主要任务是将状态从非托管实体(作为参数传递)到持久性上下文中其托管对应实体。 然而,这项任务进一步分为几个场景,这会恶化整个方法行为的可理解性。

我没有重复 JPA 规范中的段落,而是准备了一个流程图,示意性地描述了合并方法的行为:

< img src="https://i.sstatic.net/9QaeA.png" alt="在此处输入图像描述">

那么,什么时候应该使用持久,什么时候应该使用合并?

< strong>persist

  • 您希望该方法始终创建一个新实体并且从不更新实体。 否则,该方法将因违反主键唯一性而引发异常。
  • 批处理,以有状态的方式处理实体(请参阅网关模式)。
  • 性能优化

合并

  • 您希望该方法在数据库中插入或更新实体。
  • 您希望以无状态方式处理实体(服务中的数据传输对象)
  • 您希望插入一个新实体,该实体可能引用另一个可能但尚未创建的实体(关系必须标记为 MERGE)。 例如,插入一张新照片并引用新相册或预先存在的相册。

JPA is indisputably a great simplification in the domain of enterprise
applications built on the Java platform. As a developer who had to
cope up with the intricacies of the old entity beans in J2EE I see the
inclusion of JPA among the Java EE specifications as a big leap
forward. However, while delving deeper into the JPA details I find
things that are not so easy. In this article I deal with comparison of
the EntityManager’s merge and persist methods whose overlapping
behavior may cause confusion not only to a newbie. Furthermore I
propose a generalization that sees both methods as special cases of a
more general method combine.

Persisting entities

In contrast to the merge method the persist method is pretty straightforward and intuitive. The most common scenario of the persist method's usage can be summed up as follows:

"A newly created instance of the entity class is passed to the persist method. After this method returns, the entity is managed and planned for insertion into the database. It may happen at or before the transaction commits or when the flush method is called. If the entity references another entity through a relationship marked with the PERSIST cascade strategy this procedure is applied to it also."

enter image description here

The specification goes more into details, however, remembering them is not crucial as these details cover more or less exotic situations only.

Merging entities

In comparison to persist, the description of the merge's behavior is not so simple. There is no main scenario, as it is in the case of persist, and a programmer must remember all scenarios in order to write a correct code. It seems to me that the JPA designers wanted to have some method whose primary concern would be handling detached entities (as the opposite to the persist method that deals with newly created entities primarily.) The merge method's major task is to transfer the state from an unmanaged entity (passed as the argument) to its managed counterpart within the persistence context. This task, however, divides further into several scenarios which worsen the intelligibility of the overall method's behavior.

Instead of repeating paragraphs from the JPA specification I have prepared a flow diagram that schematically depicts the behaviour of the merge method:

enter image description here

So, when should I use persist and when merge?

persist

  • You want the method always creates a new entity and never updates an entity. Otherwise, the method throws an exception as a consequence of primary key uniqueness violation.
  • Batch processes, handling entities in a stateful manner (see Gateway pattern).
  • Performance optimization

merge

  • You want the method either inserts or updates an entity in the database.
  • You want to handle entities in a stateless manner (data transfer objects in services)
  • You want to insert a new entity that may have a reference to another entity that may but may not be created yet (relationship must be marked MERGE). For example, inserting a new photo with a reference to either a new or a preexisting album.
想你的星星会说话 2024-08-02 11:20:29

我在实体上遇到了惰性加载异常,因为我试图访问会话中的惰性加载集合。

我要做的是在一个单独的请求中,从会话中检索实体,然后尝试访问我的 jsp 页面中的集合,这是有问题的。

为了缓解这种情况,我更新了控制器中的相同实体并将其传递给我的 jsp,尽管我想象当我在会话中重新保存时,它也可以通过 SessionScope 访问,并且不会抛出 LazyLoadingException,示例 2 的修改:

以下内容对我有用:

// scenario 2 MY WAY
// tran starts
e = new MyEntity();
e = em.merge(e); // re-assign to the same entity "e"

//access e from jsp and it will work dandy!!

I was getting lazyLoading exceptions on my entity because I was trying to access a lazy loaded collection that was in session.

What I would do was in a separate request, retrieve the entity from session and then try to access a collection in my jsp page which was problematic.

To alleviate this, I updated the same entity in my controller and passed it to my jsp, although I imagine when I re-saved in session that it will also be accessible though SessionScope and not throw a LazyLoadingException, a modification of example 2:

The following has worked for me:

// scenario 2 MY WAY
// tran starts
e = new MyEntity();
e = em.merge(e); // re-assign to the same entity "e"

//access e from jsp and it will work dandy!!
波浪屿的海角声 2024-08-02 11:20:29

我发现 Hibernate 文档中的这个解释很有启发性,因为它们包含一个用例:

merge() 的用法和语义似乎让新用户感到困惑。 首先,只要您不尝试在另一个新实体管理器中使用一个实体管理器中加载的对象状态,您就应该根本不需要使用 merge() 。 一些整个应用程序永远不会使用此方法。

通常merge()用于以下场景:

  • 应用程序在第一个实体管理器中加载一个对象
  • 对象被传递到表示层
  • 对对象进行了一些修改
  • 对象被传递回业务逻辑层
  • 应用程序通过在第二个实体管理器中调用 merge() 来保留这些修改

这是 merge() 的确切语义:

  • 如果当前存在与持久性上下文关联的具有相同标识符的托管实例,则将给定对象的状态复制到托管实例上
  • 如果当前没有与持久性上下文关联的托管实例,请尝试从数据库加载它,或创建一个新的托管实例
  • 返回托管实例
  • 给定实例不会与持久性上下文关联,它保持分离状态并且通常会被丢弃

来自: http://docs.jboss.org /hibernate/entitymanager/3.6/reference/en/html/objectstate.html

I found this explanation from the Hibernate docs enlightening, because they contain a use case:

The usage and semantics of merge() seems to be confusing for new users. Firstly, as long as you are not trying to use object state loaded in one entity manager in another new entity manager, you should not need to use merge() at all. Some whole applications will never use this method.

Usually merge() is used in the following scenario:

  • The application loads an object in the first entity manager
  • the object is passed up to the presentation layer
  • some modifications are made to the object
  • the object is passed back down to the business logic layer
  • the application persists these modifications by calling merge() in a second entity manager

Here is the exact semantic of merge():

  • if there is a managed instance with the same identifier currently associated with the persistence context, copy the state of the given object onto the managed instance
  • if there is no managed instance currently associated with the persistence context, try to load it from the database, or create a new managed instance
  • the managed instance is returned
  • the given instance does not become associated with the persistence context, it remains detached and is usually discarded

From: http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html

疏忽 2024-08-02 11:20:29

浏览答案时,缺少一些有关“级联”和 id 生成的细节。 查看问题

另外,值得一提的是,您可以使用单独的 Cascade 注释来进行合并和持久化:Cascade.MERGECascade .PERSIST 将根据使用的方法进行处理。

规格是你的朋友;)

Going through the answers there are some details missing regarding `Cascade' and id generation. See question

Also, it is worth mentioning that you can have separate Cascade annotations for merging and persisting: Cascade.MERGE and Cascade.PERSIST which will be treated according to the used method.

The spec is your friend ;)

短叹 2024-08-02 11:20:29

场景 X:

表:Spitter(一个),表:Spittles(许多)(Spittles 是与 FK:spitter_id 的关系的所有者)

此场景导致保存:Spitter 和两个 Spittles 就好像由同一 Spitter 拥有一样。

        Spitter spitter=new Spitter();  
    Spittle spittle3=new Spittle();     
    spitter.setUsername("George");
    spitter.setPassword("test1234");
    spittle3.setSpittle("I love java 2");       
    spittle3.setSpitter(spitter);               
    dao.addSpittle(spittle3); // <--persist     
    Spittle spittle=new Spittle();
    spittle.setSpittle("I love java");
    spittle.setSpitter(spitter);        
    dao.saveSpittle(spittle); //<-- merge!!

场景 Y:

这将保存 Spitter,将保存 2 个 Spittle,但它们不会引用同一个 Spitter!

        Spitter spitter=new Spitter();  
    Spittle spittle3=new Spittle();     
    spitter.setUsername("George");
    spitter.setPassword("test1234");
    spittle3.setSpittle("I love java 2");       
    spittle3.setSpitter(spitter);               
    dao.save(spittle3); // <--merge!!       
    Spittle spittle=new Spittle();
    spittle.setSpittle("I love java");
    spittle.setSpitter(spitter);        
    dao.saveSpittle(spittle); //<-- merge!!

Scenario X:

Table:Spitter (One) ,Table: Spittles (Many) (Spittles is Owner of the relationship with a FK:spitter_id)

This scenario results in saving : The Spitter and both Spittles as if owned by Same Spitter.

        Spitter spitter=new Spitter();  
    Spittle spittle3=new Spittle();     
    spitter.setUsername("George");
    spitter.setPassword("test1234");
    spittle3.setSpittle("I love java 2");       
    spittle3.setSpitter(spitter);               
    dao.addSpittle(spittle3); // <--persist     
    Spittle spittle=new Spittle();
    spittle.setSpittle("I love java");
    spittle.setSpitter(spitter);        
    dao.saveSpittle(spittle); //<-- merge!!

Scenario Y:

This will save the Spitter, will save the 2 Spittles But they will not reference the same Spitter!

        Spitter spitter=new Spitter();  
    Spittle spittle3=new Spittle();     
    spitter.setUsername("George");
    spitter.setPassword("test1234");
    spittle3.setSpittle("I love java 2");       
    spittle3.setSpitter(spitter);               
    dao.save(spittle3); // <--merge!!       
    Spittle spittle=new Spittle();
    spittle.setSpittle("I love java");
    spittle.setSpitter(spitter);        
    dao.saveSpittle(spittle); //<-- merge!!
节枝 2024-08-02 11:20:29

另一个观察结果:

当记录具有这样的 id 时,merge() 只会关心自动生成的 id(在 IDENTITYSEQUENCE 上测试)已存在于您的表中。 在这种情况下,merge() 将尝试更新记录。
但是,如果 id 不存在或不匹配任何现有记录,merge() 将完全忽略它并要求数据库分配一个新记录。 这有时是许多错误的根源。 不要使用 merge() 强制为新记录指定 ID。

另一方面,persist() 永远不会让你向它传递 id。 它会立即失败。 就我而言,它是:

由以下原因引起:org.hibernate.PersistentObjectException:分离的实体
传递到持久化

hibernate-jpa javadoc 有一个提示:

抛出:javax.persistence.EntityExistsException - 如果实体
已经存在。 (如果该实体已经存在,则
持久化操作时可能会抛出EntityExistsException
调用,或者 EntityExistsException 或另一个 PersistenceException
可能会在刷新或提交时抛出。)

Another observation:

merge() will only care about an auto-generated id(tested on IDENTITY and SEQUENCE) when a record with such an id already exists in your table. In that case merge() will try to update the record.
If, however, an id is absent or is not matching any existing records, merge() will completely ignore it and ask a db to allocate a new one. This is sometimes a source of a lot of bugs. Do not use merge() to force an id for a new record.

persist() on the other hand will never let you even pass an id to it. It will fail immediately. In my case, it's:

Caused by: org.hibernate.PersistentObjectException: detached entity
passed to persist

hibernate-jpa javadoc has a hint:

Throws: javax.persistence.EntityExistsException - if the entity
already exists. (If the entity already exists, the
EntityExistsException may be thrown when the persist operation is
invoked, or the EntityExistsException or another PersistenceException
may be thrown at flush or commit time.)

书间行客 2024-08-02 11:20:29

您可能来这里是为了寻求有关何时使用坚持以及何时使用合并的建议。 我认为这取决于具体情况:您需要创建新记录的可能性有多大以及检索持久数据有多困难。

我们假设您可以使用自然键/标识符。

  • 数据需要持久化,但偶尔会有一条记录存在并需要更新。 在这种情况下,您可以尝试持久化,如果它抛出 EntityExistsException,您可以查找它并合并数据:

    尝试{entityManager.persist(entity)}

    catch(EntityExistsException exception) { /* 检索和合并 */ }

  • 持久化的数据需要更新,但有时还没有该数据的记录。 在这种情况下,您会查找它,如果实体丢失,则执行持久操作:

    实体=entityManager.find(key);

    if (entity == null) {entityManager.persist(entity); }

    else { /* merge */ }

如果你没有自然键/标识符,你将很难确定实体是否存在,或者如何查找它。

合并也可以通过两种方式处理:

  1. 如果更改通常很小,则将它们应用到托管实体。
  2. 如果更改很常见,请从持久化实体中复制 ID 以及未更改的数据。 然后调用EntityManager::merge()来替换旧的内容。

You may have come here for advice on when to use persist and when to use merge. I think that it depends the situation: how likely is it that you need to create a new record and how hard is it to retrieve persisted data.

Let's presume you can use a natural key/identifier.

  • Data needs to be persisted, but once in a while a record exists and an update is called for. In this case you could try a persist and if it throws an EntityExistsException, you look it up and combine the data:

    try { entityManager.persist(entity) }

    catch(EntityExistsException exception) { /* retrieve and merge */ }

  • Persisted data needs to be updated, but once in a while there is no record for the data yet. In this case you look it up, and do a persist if the entity is missing:

    entity = entityManager.find(key);

    if (entity == null) { entityManager.persist(entity); }

    else { /* merge */ }

If you don't have natural key/identifier, you'll have a harder time to figure out whether the entity exist or not, or how to look it up.

The merges can be dealt with in two ways, too:

  1. If the changes are usually small, apply them to the managed entity.
  2. If changes are common, copy the ID from the persisted entity, as well as unaltered data. Then call EntityManager::merge() to replace the old content.
清眉祭 2024-08-02 11:20:29

persist(entity) 应该与全新的实体一起使用,将它们添加到数据库中(如果数据库中已经存在实体,则会抛出 EntityExistsException )。

如果实体已分离并发生更改,则应使用 merge(entity) 将实体放回持久化上下文。

可能持久正在生成 INSERT sql 语句并合并 UPDATE sql 语句(但我不确定)。

persist(entity) should be used with totally new entities, to add them to DB (if entity already exists in DB there will be EntityExistsException throw).

merge(entity) should be used, to put entity back to persistence context if the entity was detached and was changed.

Probably persist is generating INSERT sql statement and merge UPDATE sql statement (but i'm not sure).

青春有你 2024-08-02 11:20:29

合并不会更新传递的实体,除非该实体受到管理。 即使实体 ID 设置为现有数据库记录,也会在数据库中创建新记录。

Merge won't update a passed entity, unless this entity is managed. Even if entity ID is set to an existing DB record, a new record will be created in a database.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文