Skip to content

Commit f50c43e

Browse files
committed
HHH-19878 explain how to use find() to control merge()
1 parent fc0d11b commit f50c43e

File tree

1 file changed

+40
-6
lines changed

1 file changed

+40
-6
lines changed

documentation/src/main/asciidoc/introduction/Interacting.adoc

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -643,23 +643,57 @@ Notice that this code fragment is completely typesafe, again thanks to <<metamod
643643
[[controlling-merge]]
644644
=== Controlling state retrieval during merge
645645

646-
When <<persistence-operations,`merge()`>> is used with cascading, that is, when the `merge()` operation is applied to a root entity with associations mapped `cascade=MERGE`, Hibernate issues a single `select` statement to retrieve the current database state associated with the entity and its associated entities.
647-
In certain circumstances, this query might be suboptimal.
648-
However, this query does not occur if the root entity was already loaded into the persistence context before `merge()` is called.
646+
The <<persistence-operations,`merge()`>> operation is usually used when some interaction with a user or automated system spans multiple transactions, with each transaction having its own persistence context:
649647

650-
Therefore, we may gain control over the way state is loaded before a `merge()` just by calling `find()` before calling `merge()`.
648+
1. an entity `e` is retrieved in one persistence context, and then the current transaction ends, resulting in destruction of the persistence context and in the entity becoming detached, then
649+
2. `e` is modified in some way while detached, and then
650+
3. finally, the modification is made persistent by merging the detached instance in a second transaction, with a new persistence context, by calling `session.merge(e)`.
651+
652+
In step 3, the original entity instance `e` remains detached, but `merge()` returns a distinct instance `f` representing the same row of the database and associated with the new persistence context.
653+
That is, `merge()` trades a detached instance for a <<persistence-contexts,persistent instance>> representing the same row.
654+
655+
To determine the nature of the modification held in `e` and to guarantee correct semantics with respect to optimistic locking, Hibernate first ``select``s the current persistent state of the entity from the database before applying the modification to `f`.
656+
657+
<<cascade,Cascade merge>> allows multiple modifications held in a graph of entity instances to be made persistent in one call to `merge()`.
658+
When `merge()` is used with cascading -- that is, when the `merge()` operation is applied to a root entity with associations mapped `cascade=MERGE` -- Hibernate issues a single `select` statement to retrieve the current database state of the root entity and all its associated entities.
659+
This behavior avoids the use of N+1 select statements for state retrieval during cascade merge but, in certain circumstances, the query might be suboptimal.
660+
On the other hand, this query does not occur if the root entity was already loaded into the persistence context before `merge()` is called.
661+
662+
Therefore, when using the `EntityManager` API, we may gain control over the way state is loaded before a `merge()` just by calling `find()` before calling `merge()`.
651663

652664
[source,java]
653665
----
654666
Book book = ... ;
655-
var graph = session.createEntityGraph(Book.class);
667+
var graph = entityManager.createEntityGraph(Book.class);
656668
graph.addSubgraph(Book_.chapters); // Book.chapters mapped cascade=MERGE
657669
entityManager.find(graph, book.getIsbn()); // force loading of the book
658-
entityManager.merge(book);
670+
entityManager.merge(book); // merge the detached objet
671+
----
672+
673+
The `Session` interface provides a streamlined alternative:
674+
675+
[source,java]
676+
----
677+
Book book = ... ;
678+
var graph = session.createEntityGraph(Book.class);
679+
graph.addSubgraph(Book_.chapters);
680+
session.merge(book, graph); // equivalent to find() then merge()
659681
----
660682

661683
When merging multiple root entities, `findMultiple()` may be used instead of `find()`.
662684

685+
[source,java]
686+
----
687+
List<Book> books = ... ;
688+
var isbns = books.stream().map(Book::getIsbn).toList();
689+
var graph = session.createEntityGraph(Book.class);
690+
graph.addSubgraph(Book_.chapters); // Book.chapters mapped cascade=MERGE
691+
session.findMultiple(graph, isbns); // force loading of the books
692+
for (var book in books) {
693+
session.merge(book);
694+
}
695+
----
696+
663697
TIP: In some cases, `merge()` is much less efficient than the `upsert()` operation of <<stateless-sessions,`StatelessSession`>>.
664698

665699
[[flush]]

0 commit comments

Comments
 (0)