Skip to content

Commit 5fca525

Browse files
committed
Update Interacting.adoc for JPA 3.2
Signed-off-by: Gavin King <gavin@hibernate.org>
1 parent 63d3d32 commit 5fca525

File tree

1 file changed

+134
-32
lines changed

1 file changed

+134
-32
lines changed

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

Lines changed: 134 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,19 @@ finally {
172172
}
173173
----
174174

175-
Using Hibernate's native APIs we might write something really similar,
175+
But this code is extremely tedious, so there's a cleaner option:
176+
177+
[source,java]
178+
----
179+
entityManagerFactory.runInTransaction(entityManager -> {
180+
// do the work
181+
...
182+
});
183+
----
184+
185+
When we need to return a value from within the anonymous function, we use `callInTransaction()` instead of `runInTransaction()`.
186+
187+
Using Hibernate's native APIs we can write something very similar:
176188
// [source,java]
177189
// ----
178190
// Session session = sessionFactory.openSession();
@@ -191,8 +203,6 @@ Using Hibernate's native APIs we might write something really similar,
191203
// session.close();
192204
// }
193205
// ----
194-
but since this sort of code is extremely tedious, we have a much nicer option:
195-
196206
[source,java]
197207
----
198208
sessionFactory.inTransaction(session -> {
@@ -207,13 +217,22 @@ In a container environment, the container itself is usually responsible for mana
207217
In Java EE or Quarkus, you'll probably indicate the boundaries of the transaction using the `@Transactional` annotation.
208218
****
209219

210-
JPA doesn't have a standard way to set the transaction timeout, but Hibernate does:
220+
The `EntityTransaction` interface provides a standard way to set the transaction timeout:
211221

212222
[source,java]
213223
----
214-
session.getTransaction().setTimeout(30); // 30 seconds
224+
entityManager.getTransaction().setTimeout(30); // 30 seconds
215225
----
216226

227+
`EntityTransaction` also provides a way to set the transaction to rollback-only mode:
228+
229+
[source,java]
230+
----
231+
entityManager.getTransaction().setRollbackOnly();
232+
----
233+
234+
A transaction in rollback-only mode will be rolled back when it completes.
235+
217236
[[persistence-operations]]
218237
=== Operations on the persistence context
219238

@@ -256,6 +275,8 @@ On the other hand, except for `getReference()`, the following operations all res
256275
| Obtain a persistent object given its type and its id
257276
| `find(Class,Object,LockModeType)`
258277
| Obtain a persistent object given its type and its id, requesting the given <<optimistic-and-pessimistic-locking,optimistic or pessimistic lock mode>>
278+
| `find(EntityGraph,Object)`
279+
| Obtain a persistent object given its id and an `EntityGraph` specifying its type and associations which should be eagerly fetched
259280
| `getReference(Class,id)`
260281
| Obtain a reference to a persistent object given its type and its id, without actually loading its state from the database
261282
| `getReference(Object)`
@@ -264,7 +285,7 @@ On the other hand, except for `getReference()`, the following operations all res
264285
| Refresh the persistent state of an object using a new SQL `select` to retrieve its current state from the database
265286
| `refresh(Object,LockModeType)`
266287
| Refresh the persistent state of an object using a new SQL `select` to retrieve its current state from the database, requesting the given <<optimistic-and-pessimistic-locking,optimistic or pessimistic lock mode>>
267-
| `lock(Object, LockModeType)`
288+
| `lock(Object,LockModeType)`
268289
| Obtain an <<optimistic-and-pessimistic-locking,optimistic or pessimistic lock>> on a persistent object
269290
|===
270291

@@ -280,6 +301,46 @@ The persistence context is fragile.
280301
If you receive an exception from Hibernate, you should immediately close and discard the current session. Open a new session if you need to, but throw the bad one away first.
281302
====
282303

304+
Four of these operations accept _options_, allowing influence over their behavior.
305+
306+
[%breakable,cols="50,~"]
307+
|===
308+
| Method name and parameters | Effect
309+
310+
| `find(Class,Object,FindOption...)`
311+
| Obtain a persistent object given its type and its id, using the specified options
312+
| `find(EntityGraph,Object,FindOption...)`
313+
| Obtain a persistent object given its id and an `EntityGraph` specifying its type and associations which should be eagerly fetched, using the specified options
314+
| `refresh(Object,LockModeType,RefreshOption...)`
315+
| Refresh the persistent state of an object using a new SQL `select` to retrieve its current state from the database, requesting the given <<optimistic-and-pessimistic-locking,optimistic or pessimistic lock mode>>, using the specified options
316+
| `lock(Object,LockModeType,LockOption...)`
317+
| Obtain an <<optimistic-and-pessimistic-locking,optimistic or pessimistic lock>> on a persistent object, using the specified options
318+
|===
319+
320+
For example, JPA provides the `Timeout` class which is a `FindOption`, a `RefreshOption`, and a `LockOption`.
321+
322+
[source,java]
323+
----
324+
var book = entityManger.find(Book.class, isbn, Timeout.ms(100), CacheStoreMode.BYPASS);
325+
----
326+
327+
Finally, the Hibernate `Session` offers the following method, which is capable of efficiently loading multiple entity instances in parallel:
328+
329+
[%breakable,cols="50,~"]
330+
|===
331+
| Method name and parameters | Effect
332+
333+
| `findMultiple(Class,List<Object>,FindOption...)`
334+
| Obtain a list of persistent objects given their type and their ids, using the specified options
335+
|===
336+
337+
The following code results in a single SQL `select` statement:
338+
339+
[source,java]
340+
----
341+
List<Book> books = session.findMultiple(Book.class, bookIds);
342+
----
343+
283344
Each of the operations we've seen so far affects a single entity instance passed as an argument.
284345
But there's a way to set things up so that an operation will propagate to associated entities.
285346

@@ -380,29 +441,36 @@ Hibernate has a slightly easier way to do it:
380441
boolean authorsFetched = Hibernate.isInitialized(book.getAuthors());
381442
----
382443

383-
But the static methods of the link:{doc-javadoc-url}org/hibernate/Hibernate.html[`Hibernate`] class let us do a lot more, and it's worth getting a bit familiar with them.
444+
Similarly, `PersistenceUnitUtil.load()` force-fetches a proxy or collection:
384445

385-
Of particular interest are the operations which let us work with unfetched collections without fetching their state from the database.
386-
For example, consider this code:
446+
[source,java]
447+
----
448+
Book book = session.find(Book.class, bookId); // fetch just the Book, leaving authors unfetched
449+
entityManagerFactory.getPersistenceUnitUtil().load(book.getAuthors());
450+
----
451+
452+
Again, `Hibernate.initialize()` is slightly more convenient:
387453

388454
[source,java]
389455
----
390456
Book book = session.find(Book.class, bookId); // fetch just the Book, leaving authors unfetched
391-
Author authorRef = session.getReference(Author.class, authorId); // obtain an unfetched proxy
392-
boolean isByAuthor = Hibernate.contains(book.getAuthors(), authorRef); // no fetching
457+
Hibernate.initialize(book.getAuthors()); // fetch the Authors
393458
----
394459

395-
This code fragment leaves both the set `book.authors` and the proxy `authorRef` unfetched.
460+
On the other hand, the above code is very inefficient, requiring two trips to the database to obtain data that could in principle be retrieved with just one query.
396461

397-
Finally, `Hibernate.initialize()` is a convenience method that force-fetches a proxy or collection:
462+
The static methods of the link:{doc-javadoc-url}org/hibernate/Hibernate.html[`Hibernate`] class let us do a lot more, and it's worth getting a bit familiar with them.
463+
Of particular interest are the operations which let us work with unfetched collections without fetching their state from the database.
464+
For example, consider this code:
398465

399466
[source,java]
400467
----
401468
Book book = session.find(Book.class, bookId); // fetch just the Book, leaving authors unfetched
402-
Hibernate.initialize(book.getAuthors()); // fetch the Authors
469+
Author authorRef = session.getReference(Author.class, authorId); // obtain an unfetched proxy
470+
boolean isByAuthor = Hibernate.contains(book.getAuthors(), authorRef); // no fetching
403471
----
404472

405-
But of course, this code is very inefficient, requiring two trips to the database to obtain data that could in principle be retrieved with just one query.
473+
This code fragment leaves both the set `book.authors` and the proxy `authorRef` unfetched.
406474

407475
It's clear from the discussion above that we need a way to request that an association be _eagerly_ fetched using a database `join`, thus protecting ourselves from the infamous N+1 selects.
408476
One way to do this is by passing an `EntityGraph` to `find()`.
@@ -413,24 +481,20 @@ One way to do this is by passing an `EntityGraph` to `find()`.
413481
When an association is mapped `fetch=LAZY`, it won't, by default, be fetched when we call the `find()` method.
414482
We may request that an association be fetched eagerly (immediately) by passing an `EntityGraph` to `find()`.
415483

416-
The JPA-standard API for this is a bit unwieldy:
417-
418484
[source,java]
419485
----
420486
var graph = entityManager.createEntityGraph(Book.class);
421487
graph.addSubgraph(Book_.publisher);
422-
Book book = entityManager.find(Book.class, bookId, Map.of(SpecHints.HINT_SPEC_FETCH_GRAPH, graph));
488+
Book book = entityManager.find(graph, bookId);
423489
----
424490

425-
This is untypesafe and unnecessarily verbose.
426-
Hibernate has a better way:
427-
428-
[source,java]
429-
----
430-
var graph = session.createEntityGraph(Book.class);
431-
graph.addSubgraph(Book_.publisher);
432-
Book book = session.byId(Book.class).withFetchGraph(graph).load(bookId);
433-
----
491+
//
492+
// [source,java]
493+
// ----
494+
// var graph = session.createEntityGraph(Book.class);
495+
// graph.addSubgraph(Book_.publisher);
496+
// Book book = session.byId(Book.class).withFetchGraph(graph).load(bookId);
497+
// ----
434498

435499
This code adds a `left outer join` to our SQL query, fetching the associated `Publisher` along with the `Book`.
436500

@@ -441,10 +505,17 @@ We may even attach additional nodes to our `EntityGraph`:
441505
var graph = session.createEntityGraph(Book.class);
442506
graph.addSubgraph(Book_.publisher);
443507
graph.addPluralSubgraph(Book_.authors).addSubgraph(Author_.person);
444-
Book book = session.byId(Book.class).withFetchGraph(graph).load(bookId);
445-
508+
Book book = entityManager.find(graph, bookId);
446509
----
447510

511+
// [source,java]
512+
// ----
513+
// var graph = session.createEntityGraph(Book.class);
514+
// graph.addSubgraph(Book_.publisher);
515+
// graph.addPluralSubgraph(Book_.authors).addSubgraph(Author_.person);
516+
// Book book = session.byId(Book.class).withFetchGraph(graph).load(bookId);
517+
// ----
518+
448519
This results in a SQL query with _four_ ``left outer join``s.
449520

450521
[NOTE]
@@ -460,8 +531,13 @@ JPA specifies that any given `EntityGraph` may be interpreted in two different w
460531
Any association not belonging to the entity graph is proxied and loaded lazily only if required.
461532
- A _load graph_ specifies that the associations in the entity graph are to be fetched in addition to the associations mapped `fetch=EAGER`.
462533

534+
An `EntityGraph` passed directly to `find()` is always interpreted as a load graph.
535+
536+
[TIP]
537+
====
463538
You're right, the names make no sense.
464539
But don't worry, if you take our advice, and map your associations `fetch=LAZY`, there's no difference between a "fetch" graph and a "load" graph, so the names don't matter.
540+
====
465541

466542
[NOTE]
467543
====
@@ -580,6 +656,13 @@ A second way to reduce the cost of flushing is to load entities in _read-only_ m
580656
- `SelectionQuery.setReadOnly(true)` specifies that every entity returned by a given query should be loaded in read-only mode, and
581657
- `Session.setReadOnly(Object, true)` specifies that a given entity already loaded by the session should be switched to read-only mode.
582658

659+
Hibernate's `ReadOnlyMode` is a custom `FindOption`:
660+
661+
[source,java]
662+
----
663+
var book = entityManager.find(Book.class, isbn, ReadOnlyMode.READ_ONLY);
664+
----
665+
583666
It's not necessary to dirty-check an entity instance in read-only mode.
584667

585668
[[queries]]
@@ -1224,6 +1307,11 @@ Therefore, Hibernate has some APIs that streamline certain more complicated look
12241307
| `byMultipleIds()` | Lets us load a _batch_ of ids at the same time
12251308
|===
12261309

1310+
[WARNING]
1311+
====
1312+
Since the introduction of `FindOption` in JPA 3.2, `byId()` is now much less useful.
1313+
====
1314+
12271315
Batch loading is very useful when we need to retrieve multiple instances of the same entity class by id:
12281316

12291317
[source,java]
@@ -1270,22 +1358,36 @@ Notice that this code fragment is completely typesafe, again thanks to the <<met
12701358
=== Interacting directly with JDBC
12711359

12721360
From time to time we run into the need to write some code that calls JDBC directly.
1273-
Unfortunately, JPA offers no good way to do this, but the Hibernate `Session` does.
1361+
The `EntityManager` now offers a convenient way to do this:
12741362

12751363
[source,java]
12761364
----
1277-
session.doWork(connection -> {
1365+
entityManager.runWithConnection((Connection connection) -> {
12781366
try (var callable = connection.prepareCall("{call myproc(?)}")) {
12791367
callable.setLong(1, argument);
12801368
callable.execute();
12811369
}
12821370
});
12831371
----
12841372

1285-
The `Connection` passed to the work is the same connection being used by the session, and so any work performed using that connection occurs in the same transaction context.
1373+
To return a value, use `callWithConnection()` instead of `runWithConnection()`.
1374+
1375+
The Hibernate `Session` has an older, slightly simpler API:
1376+
1377+
[source,java]
1378+
----
1379+
session.doWork(connection -> {
1380+
try (var callable = connection.prepareCall("{call myproc(?)}")) {
1381+
callable.setLong(1, argument);
1382+
callable.execute();
1383+
}
1384+
});
1385+
----
12861386

12871387
If the work returns a value, use `doReturningWork()` instead of `doWork()`.
12881388

1389+
The `Connection` passed to the work is the same connection being used by the session, and so any work performed using that connection occurs in the same transaction context.
1390+
12891391
[TIP]
12901392
====
12911393
In a container environment where transactions and database connections are managed by the container, this might not be the easiest way to obtain the JDBC connection.

0 commit comments

Comments
 (0)